Creating custom tools
Create a custom Tool to run JavaScript, TypeScript, or Python without calling an external API. Use it for validation, data transformation, or business logic. The Tool receives your defined parameters and returns a result to the Agent.
Before you begin
Make sure that you have the following:
- A Runtype account with permission to create Tools.
- To use Daytona Sandbox, a Daytona API key. For setup steps, see Configure Daytona.
- To create a Tool with the API or the CLI, a Runtype API key.
Create a custom Tool
To create a custom Tool in the dashboard, follow these steps:
- In the sidebar, click Tools.
- Click Create Tool.
- Enter the Tool name and description.
- Select Custom Code.
- Click Create Tool. The tool editor opens.
To configure the code and the parameter schema, follow these steps:
- In Configuration, choose an Execution Environment.
- If you chose Daytona Sandbox, choose a Language.
- Enter the implementation in the code editor.
- Open Parameters and click Add Parameter.
- Configure each parameter, then click Save.
Create a custom Tool with the API
Send a POST request to /v1/tools with toolType set to custom. The following request creates the discount Tool from Write Tool code:
Replace YOUR_API_KEY with your Runtype API key.
To create the same Tool with the CLI, save the request body as tool.json and run the following command:
The name must be 1 to 100 characters. The description is required and must be 1 to 500 characters. The following table describes the config fields for a custom Tool:
For the full request and response schema, see Create tool. To keep Tool definitions in your repository, see Manage tools as code.
Pick an execution environment
Choose an execution environment that supports the language and network behavior that your code needs. The following table compares the environments:
Use Cloudflare Worker (Default) for JavaScript that uses async/await, standard built-ins, or the helpers namespace. Runtype captures console.log output in execution logs. A network call such as fetch fails with an error unless the Tool sets networkAccess. For details, see Call an API from custom code.
Use Daytona Sandbox for TypeScript or Python in the dashboard. Runtype injects each parameter as a variable in your code.
Use QuickJS (Legacy) to maintain existing JavaScript tools. QuickJS runs JavaScript synchronously and exposes the helper functions as global functions instead of the helpers namespace.
The tool editor doesn’t list Runtype Sandbox. To use it, set sandboxProvider to runtype-sandbox and set language when you create or update the Tool with the API. Your code reads the parameters object. Runtype Sandbox code can import preinstalled packages such as numpy and pandas. For the full list, see Preinstalled sandbox software.
Call an API from custom code
Custom code has no network access by default. This includes every Tool that existed before the networkAccess setting. Tool parameters can carry text from a model or a user, so a Tool gets network access only when you turn it on for that Tool.
To let the code call an API, set networkAccess in the Tool config:
"off"blocks all requests. This is the default.{ "allowedHostnames": ["api.github.com"] }allows requests to the listed hosts only. A*.prefix, such as*.github.com, matches subdomains. Prefer this form."on"allows requests to any public host.
To send a managed secret, declare it in secrets. Your code never receives the secret value. It receives a placeholder token as secrets.NAME, and the egress proxy replaces the placeholder with the real value only on requests to the declared host:
Each secrets entry takes these fields:
A Tool can declare up to 10 secrets, each secret at most once. The proxy blocks a request that sends the placeholder to any other host or path. networkAccess and secrets work with the Cloudflare Worker and Runtype Sandbox environments. QuickJS has no network access, and Daytona Sandbox can’t inject secrets, so Runtype rejects these settings for those environments. Creating or dispatching a Tool with secrets requires the SECRETS:READ permission.
You can’t write {{secret:NAME}} in custom code. Use secrets instead.
Configure Daytona
To add a Daytona API key, follow these steps:
- Open Settings and select Integrations.
- Under Code Execution, enter the Daytona API key.
- Click Save Key.
If you don’t add a key, Runtype uses a platform key when one is available for your organization. If no Daytona API key is available, either your own or a platform key, the Tool returns an error.
Write Tool code
The runtime provides input in different ways. Cloudflare Worker, QuickJS, and Runtype Sandbox code reads the parameters object. Daytona Sandbox injects each parameter as a variable.
A code Tool reads its arguments from parameters. For a Tool with an orderId parameter, read parameters.orderId.
In Cloudflare Worker and QuickJS JavaScript, the input variable is not the arguments object. It is a wrapper that holds parameters (and secrets when the Tool has secret bindings), so input.parameters.orderId returns the same value as parameters.orderId, and input.orderId is undefined. The shape is the same for every caller: the execute endpoint, the MCP execute_tool tool, the SDK, the CLI, the dashboard Test panel, an Agent, and a Flow.
This differs from a Flow’s transform-data step. In a transform-data step, input is the flat bag of Flow variables, so input.orderId works there. Do not copy that pattern into a Tool.
Use this example with Cloudflare Worker, QuickJS, or Runtype Sandbox:
Use this Python example with Daytona to aggregate an array of numbers. Define a required parameter named numbers with type array before you run the example:
Use the language runtime and packages that Daytona Sandbox provides. Outbound network access follows its sandbox policy.
Runtime behavior
How your code sends a result depends on the environment and the language. The following table shows the rule for each combination:
Runtype parses printed output as JSON when possible and otherwise passes the raw text to the Agent. In Daytona Sandbox, return doesn’t send a result. Print the value instead.
The runtime handles errors and logs as follows:
- Handle expected failures with a structured error object. An unhandled exception fails the Tool and returns an error to the Agent.
- Use
console.login Cloudflare Worker or QuickJS code when you need output in execution logs.
Available JavaScript features
Cloudflare Worker provides these JavaScript features:
- Standard built-ins such as
Array,Object,Math,Date, andJSON. async/await, arrow functions, destructuring, and template literals.- Regular expressions and string manipulation.
- The
helpersnamespace. - Captured
console.logoutput in execution logs.
QuickJS provides the following JavaScript features:
- Standard built-ins such as
Array,Object,Math,Date, andJSON. - Regular expressions and string manipulation.
- Helper functions such as
parseHTML()andextractEmails()as global functions. - Synchronous JavaScript execution.
Daytona Sandbox provides the language runtime and container capabilities in its configuration. Use the selected language’s standard library and available packages.
Cloudflare Worker helper functions
Cloudflare Worker exposes the following functions in the helpers namespace:
In QuickJS, call these helper functions without the helpers. prefix.
Define parameters
Open Parameters, then click Add Parameter to define the input schema. Configure each field as follows:
Runtype doesn’t fill in a default value when the Agent omits a parameter. Handle a missing value in your code, for example const format = parameters.format ?? 'iso'.
For structured input, use type object or array, not a string that holds JSON. Models often drop the escape characters in a JSON string, and the call fails. When a string parameter looks like a JSON payload, the API create and update responses include a TOOL_PARAM_EMBEDDED_JSON_STRING entry in validation.recommendations.
For Daytona Sandbox, name each parameter with letters, digits, and underscores, and start the name with a letter or an underscore. Don’t use a Python built-in name such as open or eval, or a name that starts and ends with two underscores. Runtype doesn’t inject a parameter with any other name, such as order-id, so your code fails when it reads that name.
Use this parameter schema with the discount example:
Set execution limits
Set Timeout (ms) between 1,000 and 300,000 in the tool editor. A Tool created in the dashboard starts at 30,000 milliseconds. The Tool stops when it reaches the timeout. These limits apply to saved Tools. A runtime custom Tool that you define in a dispatch request has a maximum of 30 seconds.
When you create a Tool with the API and don’t set timeout, the limit depends on the environment:
- Cloudflare Worker:
600,000milliseconds. - QuickJS:
5,000milliseconds. - Runtype Sandbox:
30,000milliseconds.
Timeout (ms) doesn’t apply to Daytona Sandbox, which uses its own sandbox execution limit.
The tool editor also shows a Memory Limit control. This setting doesn’t change how the Tool runs.
Validate Tool code
Runtype validates custom code before it saves a Tool. JavaScript and TypeScript use syntax and semantic checks. Python uses syntax checks.
Validation errors block a save. The following checks produce errors:
eval(), with the error codeUSE_OF_EVAL.new Function(), with the error codeUSE_OF_FUNCTION_CTOR.- A top-level declaration named
inputin Cloudflare Worker or Daytona Sandbox JavaScript or TypeScript, with the error codeRESERVED_NAME_REDECLARED. - Syntax errors.
Validation warnings don’t block a save. The following checks produce warnings:
- A loop with a constant-true condition and no
breakorawait, such aswhile (true)orfor (;;), with the warning codeINFINITE_LOOP. - A dynamic
import()expression, with the warning codeDYNAMIC_IMPORT. - A missing top-level
returnstatement in Cloudflare Worker or QuickJS code, with the warning codeRETURN_UNDEFINED. - A declaration that hides a name the runtime provides, such as
helpers, with the warning codeSHADOWED_WRAPPER_NAME.
The tool editor shows errors and warnings under the code. The API returns them as follows:
- A blocked save returns HTTP
400witherrorset toCustom tool code failed validation, pluserrorsandwarningsarrays. - A successful create or update that has warnings returns them in
validation.warnings.
Runtype runs the validation again whenever you update the Tool.
Handle errors
Return a structured error object when the Agent can recover from an expected input error. Use this JavaScript example:
Test a Tool
To run a test from the tool editor, follow these steps:
- Open Test Tool.
- Enter sample values in the Params tab.
- Click Run Test.
- Review the status, result, error, and execution time in the Result tab.
- Optional: Open the History tab to review previous tests.
To test a Tool from the command line, run the following command:
Replace <tool-id> with the ID of the Tool. The command sends a POST request to /v1/tools/{id}/execute with the input in a parameters object. For the request schema, see Execute tool.
The following table describes the fields in the result:
Troubleshoot
The following table lists common failures, their causes, and fixes:
Apply best practices
Use these practices when you create a custom Tool:
- Give each Tool one responsibility so you can isolate failures.
- Validate parameter values before you process them.
- Name each Tool after its operation, such as
calculate_shipping_cost. - Return structured objects so the Agent can use individual fields.
- Describe the Tool purpose, usage condition, output, limitations, and side effects.
Examples
Email validator
Use this JavaScript example to validate an email address:
Define one required email parameter with type string.
Date formatter
Use this JavaScript example to format an ISO date. When the Agent omits format, the code uses iso:
Define isoDate as a required string parameter and format as an optional string parameter.
Next steps
Use these links to continue:
- Creating external tools: call an HTTP API from an Agent.
- Manage tools as code: keep custom Tool definitions in your repository.
- Runtime tools: define a custom Tool inline in a dispatch request.
- Agent tools: attach and configure Tools.
- Built-in tools: add ready-to-use integrations.
- What are Tools?: compare Tool types.
- Integrations (PostHog, Weaviate, Daytona): configure a Daytona API key.
- Designing tools for agents: write tool names, descriptions, and parameters that Agents use well.