> ## Documentation Index
> Fetch the complete documentation index at: https://docs.telli.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom code execution

> Run your own JavaScript during a call with a code tool, or as a workflow step with a code node

A **code tool** runs your JavaScript while the agent is on a call, and the agent uses what the code returns. A **code node** runs your JavaScript as a step of a [workflow](/platform/workflows.md), and later blocks use what it returns. Use code when a [custom HTTP tool](/custom-tools.md) or an **HTTP request** block is not enough, for example to chain several requests, sign a request, or reduce a large response to the few values the agent needs.

> [!NOTE]
>
> Code tools and code nodes are available on Scale and Enterprise plans.

## Build it with Charlie

The fastest way is to ask [Charlie](/platform/charlie.md). Describe what the code should do, which API it calls, and which credential it should use. Charlie writes the code, sets up the inputs, and connects the credentials and Salesforce or HubSpot connections that already exist in your account.

For a code tool, Charlie then runs the code with test values and tells you what the agent would receive. The test requests are real. Charlie runs requests that only read data without asking, and asks you first before anything that creates, changes, or deletes data, sends a message, or calls a webhook.

![Charlie chat where a request for an order lookup code tool is followed by one successful test run and a summary of what Charlie changed](https://mintcdn.com/telli/6vlT00d9OvFrkRmN/images/custom-code/charlie-code-tool-light.png?fit=max\&auto=format\&n=6vlT00d9OvFrkRmN\&q=85\&s=481a314438cbbbe08ba3ae9eff5d8725)

Charlie adding an order lookup tool and testing it once

Charlie cannot run a code node. After a change, it asks you to publish the workflow and start a manual run, then reads the result of that run.

Charlie never asks for an API key or token in the chat. Add those yourself as a [secret input or a credential](#secrets-and-credentials).

## Write the code

The code is one JavaScript module that exports an async `run` function. telli calls it with one object:

| Field | What it contains |
| :- | :- |
| `inputs` | The inputs you configured, by name, such as `inputs.orderId` |
| `credentials` | The credentials you added, by name, such as `credentials.orders` |
| `timeoutMs` | How many milliseconds the run has left when it starts |

A code tool can return any JSON value; the agent receives it as the tool result. A code node must return a JSON object; later blocks read it as `result`. When something fails, throw an `Error` with a short message that says what went wrong.

```js wrap theme={null}
export async function run({
  inputs,
  credentials,
  timeoutMs,
}) {
  const { token } = credentials.orders;
  const id = encodeURIComponent(inputs.orderId);
  const url = `https://api.example.com/orders/${id}`;
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
    signal: AbortSignal.timeout(timeoutMs - 500),
  });
  if (response.status === 404) {
    return { found: false };
  }
  if (!response.ok) {
    throw new Error(
      `Order lookup failed (${response.status}). ` +
        "Tell the caller to try later.",
    );
  }
  const order = await response.json();
  return { found: true, status: order.status };
}
```

The code runs on telli's code runner, not in a browser or in Node.js:

* It can use standard Web APIs, such as `fetch`, `URL`, `URLSearchParams`, `Headers`, `AbortSignal`, `TextEncoder`, `TextDecoder`, `crypto.subtle`, `crypto.randomUUID`, `atob`, and `btoa`. Requests can reach any public address.
* It cannot import npm packages or other modules.
* The clock is UTC. Pass `timeZone` to `Intl.DateTimeFormat` when you format dates.
* Top-level variables can carry over from an earlier run of the same code and can also start fresh at any time. Keep values that change inside `run`.
* `console.log` output is not shown anywhere. Return or throw what you need to see.

## Limits

| Limit | Value |
| :- | :- |
| CPU time | 100 ms per run. Waiting for a response does not count |
| Run time | The tool's response timeout, or 28 seconds for a code node |
| Outbound requests | 50 per run |
| Result | 1 MiB |
| Code and inputs together | 1 MiB |
| Code length | 50,000 characters |
| Secret inputs | 20 per code tool or code node |

## Add a code tool

1. Open the agent and go to **Tools → Code tools**. Select **Add code tool...**.

2. Write the code in the **Code** editor on the left.

3. On the **Configure** tab, set:

   | Field | Description |
   | :- | :- |
   | **Name of the Tool** | Identifier the agent calls, such as `check_order_status`. Starts with a letter, then letters, numbers, and underscores, up to 64 characters |
   | **Description** | What the tool does and when the agent should use it |
   | **Inputs** | Values the code reads from `inputs`. They use the same value types as [custom HTTP tools](/custom-tools.md#value-types). Only **LLM Parameter** inputs are filled by the agent from the conversation |
   | **Credentials** | Credentials the code reads from `credentials`. See [Secrets and credentials](#secrets-and-credentials) |
   | **Response timeout (seconds)** | How long the code may run. The default is 5 seconds, the maximum 10, or 30 with a **Tool Message** |
   | **Tool Message** | What the agent says while the code runs |

4. Select **Done**, then publish the agent. Mention the tool in the prompt when the agent should use it under specific conditions.

An agent can have up to 5 code tools.

![Code tool dialog with JavaScript on the left and the Configure tab on the right showing the tool name, description, an orderId LLM parameter, the Shop API bearer token credential, and a response timeout of 8 seconds](https://mintcdn.com/telli/6vlT00d9OvFrkRmN/images/custom-code/code-tool-configure-light.png?fit=max\&auto=format\&n=6vlT00d9OvFrkRmN\&q=85\&s=2f31086161011ad0fbcc9b81acead871)

A code tool with an input, a credential, and an 8-second response timeout

### Test a code tool

Open the **Test** tab of the tool, fill in the **Test inputs**, and select **Run**. telli saves the draft and runs its code the way a call does. Secret inputs and credentials use their saved values. A test run sends real requests: anything the code creates, changes, or deletes in your systems really happens.

**What the agent receives** shows the result, or the message the agent would get on a failure. When the error names a line of your code, select **Go to line** to jump to it.

![Code tool Test tab after a failed run, showing the order lookup error message the agent would receive](https://mintcdn.com/telli/6vlT00d9OvFrkRmN/images/custom-code/code-tool-test-failed-light.png?fit=max\&auto=format\&n=6vlT00d9OvFrkRmN\&q=85\&s=fabc803d299c4ea918cbfe88787a7158)

A failed test run shows the message the agent would receive

### When a code tool fails in a call

The agent gets a message instead of a result. The call continues.

When your code throws, the agent receives `The code failed while running:` followed by the error, its line, and its stack, cut to 1,000 characters. The agent words your message for the caller, so write it for the agent, for example `The order system returned 503. Tell the caller you cannot check the order right now.` For expected outcomes, such as no matching record, return a result with a message instead of throwing.

When a required input has no value, the code does not run and the agent receives `Missing required value "<name>"`. This happens when the agent leaves out a required LLM parameter, or when the contact has no value for a required contact property or system variable. The caller cannot supply a value the contact lacks. If some contacts lack the value, clear **Required** on the input and handle the missing value in the code.

When the code hits a limit or a credential cannot be loaded, the caller never hears the technical details. The agent tells the caller the request is not possible right now and offers to help another way. The reason stays visible in the test result and in the call history:

| Failure | Reason shown |
| :- | :- |
| The response timeout runs out | `The code timed out after 5 seconds.`, with the tool's response timeout |
| The code uses more than its CPU time | `The code used more CPU time than allowed.` |
| `run` returns a promise that never settles | `run() returned a promise that never settles.` |
| The code runs out of memory | `The code ran out of memory.` |
| The result is larger than 1 MiB | `The code's result was too large. The limit is 1 MiB.` |
| The code and its inputs together are larger than 1 MiB | `The code's inputs were too large. The code and its inputs may be up to 1 MiB together.` |
| A credential cannot be loaded | The cause, such as `Credential "orders" no longer exists.` |

The agent reads at most about 100 KB of a result, so return only the values it needs.

In [simulations](/platform/simulations.md), code tools do not run. They answer with the answer set in the simulation's **Tools**, or with a generated answer.

## Add a code node

1. In the workflow editor, use **+** on the canvas and select **Utilities → Code**.
2. Under **Inputs**, select **Add input**. Give each input a name and map it to workflow data, a fixed value, or a secret.
3. Under **Credentials**, add the credentials the code needs.
4. Write the code in the **JavaScript** editor. Return a JSON object.
5. In a later block's field picker, select the code node's `result` or one of its fields.

![Workflow editor with a Contact created trigger and a Code block, and the Code panel showing a phone input mapped to the contact phone number, the Shop API credential, and JavaScript](https://mintcdn.com/telli/6vlT00d9OvFrkRmN/images/custom-code/code-node-light.png?fit=max\&auto=format\&n=6vlT00d9OvFrkRmN\&q=85\&s=1ffe1ca7d6d962b6e8cc974cb5a1cb36)

A code node that sends a new contact to an external system

Each run of a code node has 28 seconds, minus the time its credentials take to load. A workflow can contain up to 5 code nodes, and each code node up to 50 inputs.

### Test a code node

Publish the workflow, then open **Runs → Run manually** and run it. Manual runs execute real actions, including the code's requests. In the run details, the code step shows its **JavaScript**, its **Error** when the code threw, and its result under **Outputs → Load outputs**.

### When a code node fails

The code step is marked as failed, the blocks after it on that path do not run, and the run ends as failed. telli does not retry the step. The run details name the reason: the code threw, timed out, used too much CPU time, ran out of memory, did not return a JSON object, returned more than 1 MiB, had code and inputs larger than 1 MiB together, or a credential could not be loaded.

## Secrets and credentials

Never write an API key, token, or password into the code or into a constant input. Both are stored as plain text. Use a secret input or a credential instead.

* **Secret input:** Add an input of type **Secret**. The value is encrypted when saved, and the code reads it like any other input.
* **Credential:** Under **Credentials**, select **+ Add** and choose a credential from **Settings → Developer**, or your Salesforce or HubSpot connection from **Settings → Integrations**. Give it a name; the code reads it as `credentials.<name>`.

Each credential arrives in one of these shapes:

| Credential | `credentials.<name>` |
| :- | :- |
| [Bearer token](/credentials/bearer-token.md) | `{ type: "bearer_token", token }` |
| [HMAC-SHA256](/credentials/hmac-sha256.md) | `{ type: "hmac_sha256", secret }` |
| [Salesforce](/integrations/salesforce.md) connection or Salesforce credential | `{ type: "salesforce", accessToken, instanceUrl }` |
| [HubSpot](/integrations/hubspot.md) connection | `{ type: "hubspot", accessToken, apiBaseUrl }` |

telli gets a token for each Salesforce or HubSpot connection before the code runs. It stays valid for the whole run, so the code never refreshes it. A code tool or code node can use up to 10 credentials. If your Salesforce org restricts logins by IP range or locks sessions to an IP address, Salesforce rejects the token when the code uses it.

telli replaces every secret input value and every credential's token or secret with `[redacted]` in the result and in error messages. It only finds the exact value: a secret that the code shortens, encodes, or changes stays readable. Never return or throw a secret.


## Related topics

- [Bearer token](/credentials/bearer-token.md)
- [Workflows](/platform/workflows.md)
- [HMAC-SHA256](/credentials/hmac-sha256.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.