JavaScript Sandbox
Write custom actions, condition providers, event handlers, recurring jobs, sidebar widgets and prompt sections in TypeScript instead of Python.
These features — custom actions, condition providers, event handlers, recurring jobs, sidebar widgets and their actions, and prompt sections — accept TypeScript as well as Python. Pick the language in the editor (or pass language: "javascript" through the API/MCP); everything else about the feature is the same.
Custom HTTP endpoints use JavaScript/TypeScript only, with no language selector. Export handle(context), type its argument as HttpEndpointContext, and return JSON or the synchronous httpResponse(body, { status, headers }) helper from "octocom". The request is available at context.request; endpoints have a 30-second execution deadline.
The two languages have the same helpers, the same context, the same entry-point names and the same security model. This page covers what is different.
The shape of a JavaScript automation
import { addConversationTag, getSecret } from "octocom";
import type { ExecutionContext } from "octocom";
export async function execute_action(context: ExecutionContext) {
const email = context.args?.email as string | undefined;
if (!email) return { error: "email is required" };
const key = await getSecret("CRM_API_KEY");
const res = await fetch(
`https://crm.example.com/customers?email=${encodeURIComponent(email)}`,
{
headers: { Authorization: `Bearer ${key}` },
},
);
if (!res.ok) return { error: `CRM returned ${res.status}` };
await addConversationTag(context, "crm-matched");
return await res.json();
}Three rules:
- Export the entry function. The name is the same as in Python (
execute_action,evaluate_conditions,handle_event,run_job,get_sidebar_data,build_prompt_sections) and it must beexported. It may beasyncand normally is, because every helper returns a Promise. - Import helpers from
"octocom". Nothing is global. Helper names are the camelCase form of the Python ones:add_conversation_tag→addConversationTag,get_secret→getSecret. - Return a JSON-serialisable value.
undefinedbecomesnull. Anything that can't be serialised (aResponse, a cyclic object) fails the run with your code on the stack.
Only two kinds of import exist: "octocom" and your organization's own modules as "modules/<name>". There is no npm; an import of anything else is rejected before the code runs.
The context object
Identical fields to the Python context, with two spelling differences: keys that were snake_case in Python are camelCase (browser_session → browserSession), and "absent" is null, never undefined.
| Field | Type | Notes |
|---|---|---|
conversation | ConversationContext | null | null outside a conversation (recurring jobs, the test harness without a conversation, some events) |
business | { name, slug } | Always present |
customer | CustomerContext | null | null until the customer is identified |
args | Record<string, unknown> | Action / condition-provider arguments, event payload (event_type and friends) |
workflow | { id, slug, title } | Condition providers only |
job | { id, name, schedule, ... } | Recurring jobs only; keys are snake_case exactly as documented for Python |
browserSession | unknown | Web chat only |
ExecutionContext, ConversationContext and CustomerContext are exported from "octocom" for typing.
Helpers
Every helper takes the same arguments as its Python counterpart, in the same order, and returns a Promise of the same value. Optional trailing keyword arguments become an options object.
Errors are thrown, not returned: a helper whose REST call fails throws OctocomApiError (with .status, .url, .body); getSecret throws SecretNotFound; the organization store throws OrganizationStoreConflictError (with .currentRevision) on a revision mismatch. All three are exported from "octocom" for instanceof checks.
Organization modules
Shared code lives in the same place as Python modules — Settings → Modules — with the language set to TypeScript. Unlike Python modules, which become globals, JavaScript modules are imported explicitly and must export what they share:
// module "crm"
export async function lookupCustomer(email: string) {
/* ... */
}import { lookupCustomer } from "modules/crm";A module is only visible to code in its own language, and a broken JavaScript module only breaks the code that imports it.
Logging and errors
console.log / console.warn / console.error output is captured and shown in the test harness and execution logs, in place of Python's stdout. Stored secret values are stripped from it before anything is persisted, exactly as for Python — but do not print them on purpose.
A thrown error fails the run. The error type, message and the line in your code are shown in the editor; the stack trace is available in the details view.
Limits and behaviour
| JavaScript | Python | |
|---|---|---|
| Runtime | Cloudflare Workers (V8 isolate) | Azure Dynamic Sessions (container) |
| Wall-clock timeout | 60 s (prompt sections: a few seconds) | same |
| CPU time | 30 s per run; time spent awaiting fetch is free | counts toward wall clock |
| Memory | 128 MB | more |
| Concurrency | unlimited — every run is its own isolate | runs of one organization queue behind a small number of containers |
| Outbound HTTP | fetch; up to 1000 requests per run | requests; unlimited |
| Module-level state | may survive between runs of the same code; never rely on it, never store per-conversation data there | same caveat, and shared across all of an organization's code |
| Packages | none beyond "octocom" and organization modules | Python standard library + requests |
JavaScript is the better fit for anything that is mostly I/O — calling APIs, reading and writing conversation state — and for anything that runs often or in bursts (condition providers, prompt sections, event handlers). Python remains the choice for CPU- or memory-heavy work and for product-sync parsers, which are Python-only.
Security
The same guarantees apply in both languages:
- Code can only reach your organization's data. Python runs with your organization's API key; JavaScript runs with a short-lived token minted for that one execution and revoked when it returns. The token is held by the runtime, not your code — helpers and
octocomApiuse it on your behalf. - Secret values never leave the sandbox in logs, stack traces or return values.
- Helpers that change conversation state are restricted to the execution types where that is safe (
handOffConversationin bot actions;sendBotMessage/closeConversationin event handlers, recurring jobs and sidebar actions). The restriction is enforced by the runtime from the real execution type, not from anything your code passes. - Each piece of code runs in its own isolate. No other organization's code ever shares a process with yours.
- Requests through
proxyFetchcannot target private or internal addresses.