Creating custom tools
Write JavaScript or Python code that Agents can execute during a run. Use custom tools for validation, transforms, and business logic that don't need an external API.
Create a custom tool
Click Tools in the sidebar
Click Create Tool
Select Custom Code
Configure the tool:
Name: Unique identifier (e.g.,
calculate_discount)Description: What the tool does and when to use it
Sandbox: Execution environment — see below
Parameters: Define the input schema
Code: Your implementation
Click Create
Pick a sandbox
Runtype runs your code in an isolated environment. Choose the one that matches your needs: | Sandbox | Language | Network | Best for | |---------|----------|---------|----------| | Cloudflare Worker (default) | JavaScript | Blocked | Fast, zero-latency transforms | | Daytona | JavaScript, TypeScript, Python | Configurable | Heavy compute, language flexibility, external packages | | QuickJS (legacy) | JavaScript | Blocked | Minimal footprint, strictest isolation |
Cloudflare Worker runs in-process inside Runtype's edge workers. It is the fastest option and requires no setup.
Daytona is useful when you need Python, TypeScript, or a containerized environment with configurable outbound access. To use Daytona, add your API key in Settings → Integrations. If Daytona is not configured, tool execution fails and the Agent receives a null result.
QuickJS is still available for backwards compatibility but is not recommended for new tools.
Writing tool code
Your code receives input via an injected parameters object. The shape is determined by the parameter schema you define.
JavaScript (Cloudflare Worker / QuickJS)
const { orderAmount, customerTier } = parameters
let discount = 0
if (customerTier === 'premium') {
discount = orderAmount > 1000 ? 0.20 : 0.15
} else if (customerTier === 'standard') {
discount = orderAmount > 1000 ? 0.10 : 0.05
}
const discountedAmount = orderAmount * (1 - discount)
return {
originalAmount: orderAmount,
discount: discount * 100,
finalAmount: discountedAmount
}Python (Daytona)
import json, statistics
data = parameters['numbers']
result = {
'sum': sum(data),
'mean': statistics.mean(data),
'median': statistics.median(data),
'min': min(data),
'max': max(data)
}
print(json.dumps(result))In Python, write the result to stdout as JSON. Runtype captures the output and parses it.
Runtime behavior
Return values. In JavaScript, the returned object is passed directly to the Agent. In Python, the last line of stdout must be valid JSON.
Exceptions. Unhandled exceptions fail the tool and return an error to the Agent. Use structured error returns (see below) if you want the Agent to recover.
Secrets and network. Cloudflare Worker tools have no network access and cannot read managed secrets. Daytona tools inherit network and secret policy from your container configuration.
Available features
Cloudflare Worker / QuickJS:
Standard JS built-ins (
Array,Object,Math,Date,JSON)async/await, arrow functions, destructuring, template literalsRegex and string manipulation
Helper utilities:
parseHTML,extractEmails,formatDate,stripTags,querySelectorconsole.logoutput is captured in tool execution logsDaytona:
Standard library modules available in your Daytona container image
pippackages (Python) ornpmpackages (JS/TS) — availability depends on your Daytona configurationfetchand outbound network calls — allowed or blocked based on your Daytona sandbox policy
Defining parameters
Click + Add Parameter and configure each field: | Field | Purpose | |-------|---------| | Name | Parameter identifier in parameters | | Type | string, number, boolean, object, array | | Description | What this parameter is for — Agents read this | | Required | Whether the parameter is mandatory | | Default | Fallback value if not provided |
Example: | Name | Type | Required | Description | |------|------|----------|-------------| | orderAmount | number | Yes | Total order value in USD | | customerTier | string | Yes | standard, premium, or enterprise |
Execution limits
Set timeout and memory per tool:
Timeout: 1,000 ms to 300,000 ms. Default is 30,000 ms (30 s).
Memory: 8 MB, 16 MB, or 32 MB. Default is 16 MB.
If your code exceeds memory, the tool fails and returns an error. Cloudflare Workers return a timeout error to the Agent when the limit is exceeded. Daytona behavior depends on your container policy—typically a hard kill.
Code validation
Runtype runs a static AST check before saving any custom tool. It blocks:
eval()— error codeUSE_OF_EVALnew Function()— error codeUSE_OF_FUNCTION_CTORUnbounded
while (true)loops — prevents runaway executionMissing
returnstatements (JS/TS) or missingstdoutoutput (Python) — warning codeRETURN_UNDEFINEDSyntax errors
Warnings are surfaced in the dashboard after save. The check runs again on every update.
Error handling
Return structured errors so the Agent can recover:
const { email } = parameters
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
return { success: false, error: 'Invalid email format' }
}
return { success: true, isValid: true, email }Testing tools
Use the Test panel on the tool page:
Enter sample values for your parameters
Click Run Test
Review the output, execution time, and console logs
Iterate until correct
Test output includes the returned value and any
console.logcalls.
Best practices
Single responsibility. Each tool should do one thing well.
Validate inputs. Check parameter values before processing.
Clear naming.
calculate_shipping_costis better thanprocess.Return structured data. Objects are easier for Agents to work with than raw strings.
Handle errors gracefully. Return
{ success: false, error: "..." }instead of throwing.One operation per tool. Agents struggle to debug multi-step logic inside a single tool call.
Write explicit descriptions. Agents use the tool and parameter descriptions to decide when to invoke the tool and what values to pass.
Examples
Email validator
const { email } = parameters
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return {
isValid: emailRegex.test(email),
email
}Parameters: email (string, required)
Date formatter
const { isoDate, format } = parameters
const date = new Date(isoDate)
if (format === 'short') {
return { formatted: date.toLocaleDateString() }
} else if (format === 'long') {
return {
formatted: date.toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
})
}
}
return { formatted: date.toISOString() }Parameters: isoDate (string, required), format (string, default: "iso")
Array aggregator (Python)
import json, statistics
numbers = parameters['numbers']
result = {
'sum': sum(numbers),
'average': statistics.mean(numbers),
'min': min(numbers),
'max': max(numbers),
'count': len(numbers)
}
print(json.dumps(result))Parameters: numbers (array of numbers, required)
Next steps
Creating external tools — for HTTP API integrations
Agent tools — how Agents select and invoke tools
Built-in tools — ready-to-use integrations (Exa, Firecrawl, DALL-E)
What are Tools? — conceptual background