Node.js
Node.js Function tools run on Node.js 24 in a container. Unlike the JavaScript runtime (a synchronous V8 sandbox), Node.js gives you the full platform: async/await, the standard Promise-based fetch, Node’s built-in modules, and npm packages declared in a package.json.
Handler signature
Your main file must be named cortexone_function.js and must export a function called cortexone_handler. The handler may be async:
async function cortexone_handler(event, context) { return { statusCode: 200, body: JSON.stringify({ message: "Hello, World!" }) };}
module.exports = { cortexone_handler };| Parameter | Type | Description |
|---|---|---|
event | object | The input payload sent by the caller |
context | object | Execution metadata - see The context object |
The return value should be an object with statusCode and body.
CommonJS or ES modules
cortexone_function.js is loaded as CommonJS (require / module.exports). To write ES modules instead, name the entry file cortexone_function.mjs and export the handler with export:
import { createHash } from "node:crypto";
export async function cortexone_handler(event, context) { const digest = createHash("sha256").update(String(event.text)).digest("hex"); return { statusCode: 200, body: JSON.stringify({ digest }) };}.cjs also resolves. TypeScript (.ts) is not loaded - compile to JavaScript first.
The event object
event is whatever JSON the caller passes:
async function cortexone_handler(event, context) { const numbers = event.num; if (!Array.isArray(numbers) || numbers.length === 0) { return { statusCode: 400, body: JSON.stringify({ error: "'num' must be a non-empty array of numbers." }) }; } const sum = numbers.reduce((a, b) => a + b, 0); return { statusCode: 200, body: JSON.stringify({ sum, mean: sum / numbers.length }) };}
module.exports = { cortexone_handler };When the caller uploads files, each one is written to a temporary directory on the container. event.files[i].path holds its local path, and so does event.<field_name> for the form field it was uploaded through. Read it with fs:
const fs = require("node:fs/promises");
async function cortexone_handler(event, context) { const text = await fs.readFile(event.files[0].path, "utf8"); return { statusCode: 200, body: JSON.stringify({ lines: text.split("\n").length }) };}
module.exports = { cortexone_handler };Multiple files
You can create and upload multiple files. The main entry file must be cortexone_function.js (or .mjs / .cjs) and must export cortexone_handler. Load your other files with relative paths:
const { slugify } = require("./utils");
async function cortexone_handler(event, context) { return { statusCode: 200, body: JSON.stringify({ slug: slugify(event.title) }) };}
module.exports = { cortexone_handler };function slugify(s) { return String(s).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");}
module.exports = { slugify };Supported file types: .js, .mjs, .cjs, .json, plus package.json and package-lock.json.
Dependencies
Add npm packages to package.json, alongside cortexone_function.js:
{ "dependencies": { "ms": "^2.1.3" }}Then require them as usual:
const ms = require("ms");
async function cortexone_handler(event, context) { return { statusCode: 200, body: JSON.stringify({ oneHour: ms("1h") }) };}
module.exports = { cortexone_handler };The platform installs your dependencies on the first run. If you ship a package-lock.json, it installs exactly those versions with npm ci; otherwise it runs npm install --omit=dev. New tools start with an empty package.json, and Studio keeps it - it can’t be deleted.
Node’s built-in modules (fs, path, crypto, url, and the rest) need no entry in package.json.
Secrets
Store credentials as Secrets at /user/secrets, attach them to your tool from the Code tab in Studio, and read them through process.env:
async function cortexone_handler(event, context) { const apiKey = process.env.MY_API_KEY; return { statusCode: 200, body: JSON.stringify({ ready: Boolean(apiKey) }) };}
module.exports = { cortexone_handler };See Secrets for the full workflow.
HTTP requests
Node.js tools have outbound internet access. fetch() is the standard WHATWG fetch - it returns a Promise of a Response, so await it:
async function cortexone_handler(event, context) { const response = await fetch("https://api.example.com/submit", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.MY_API_KEY}` }, body: JSON.stringify({ key: "value" }) }); const data = await response.json(); return { statusCode: response.status, body: JSON.stringify(data) };}
module.exports = { cortexone_handler };npm HTTP clients such as axios work too - add them to package.json.
The context object
| Field | Description |
|---|---|
context.functionName | The tool’s function identifier |
context.memoryLimitInMB | Allocated memory in MB |
context.getRemainingTimeInMillis() | Milliseconds remaining before timeout |
Cold starts and warm containers
The first run on a fresh container starts Node and installs your dependencies, so it takes noticeably longer than later runs. Later runs reuse the warm container and skip both steps.
Because containers are reused, module-level code (for example, creating an API client at the top of the file) runs once per container rather than once per call. That is a useful way to avoid repeating setup work, but don’t rely on it for state - a call can always land on a fresh container.
Resource limits
| Limit | Default |
|---|---|
| Max memory | 512 MB |
| Max runtime | 300 seconds |
| CPU limit | 2.0 cores |
GPU compute is only available on Python 3.13.
Error responses
If your handler throws, or its Promise rejects, the platform catches it and returns statusCode: 500 with the error in the body. You can also return non-200 status codes intentionally:
async function cortexone_handler(event, context) { if (!event.id) { return { statusCode: 400, body: JSON.stringify({ error: "id is required" }) }; } return { statusCode: 200, body: JSON.stringify({ ok: true }) };}
module.exports = { cortexone_handler };Node.js vs JavaScript (V8)
| JavaScript (V8) | Node.js | |
|---|---|---|
| Handler | Top-level function cortexone_handler | Must be exported (module.exports or ESM export) |
| async / await | Not supported | Supported |
fetch() | Synchronous, returns { status, body } | Standard fetch, returns a Promise of a Response |
| Packages | None | Built-in modules and npm packages via package.json |
| Files | Single file | Multiple files |
| Secrets | process.env | process.env |
context | Not populated | functionName, memoryLimitInMB, getRemainingTimeInMillis() |
| Cold start | Milliseconds | A container plus npm install on the first run; warm afterwards |
Pick JavaScript (V8) for small, self-contained logic where start-up time matters most. Pick Node.js when you need npm packages, async code, or more than one file.
Testing your code
Save and run sample payloads from the Preview tab in Studio. The panel shows the returned statusCode, body, and any console.log output from your handler. Expect the first run after creating the tool or changing package.json to be slower while dependencies install. See Testing & Executing for the full workflow.
Notes
- The main file must be named
cortexone_function.js(or.mjs/.cjs) and must exportcortexone_handler. - The handler may be
async;fetch()returns a Promise. - Declare npm packages in
package.json; they install on the first run. - The runtime is fixed when the tool is created - an existing JavaScript (V8) tool can’t be switched to Node.js. Create a new tool instead.