Every Helix function execution receives a ctx object as its handler argument. In the shipped product it carries the project-scoped key-value store, outbound HTTP including authenticated calls, logging, the validated request input, response helpers, execution metadata, and caller identity; db(), queue(), files, ai(), and config are platform design and cannot be called today. Shared code outside the handler can reach the same object with getContext().
Surface
| Field | Type | Status | Reference |
|---|---|---|---|
config | Record<string, string> | Design | Configuration |
log | ContextLogger | Shipped | ctx.log |
http | KyInstance | Shipped | ctx.http |
auth(alias) | Promise<Record<string, string>> | Shipped | ctx.auth() |
db(alias?) | DrizzleInstance | Design | Limits and roadmap |
kv() | KeyValueStore | Shipped | ctx.kv() |
files | FileSystemClient | Design | Limits and roadmap |
queue(name) | QueueClient | Design | Limits and roadmap |
ai(model?) | AIClient | Design | Limits and roadmap |
input | Validated merged input | Shipped | Request and response |
method, headers, path | string, Headers, string | Shipped | Request and response |
request | Raw request accessor | Shipped | Request and response |
status(code) | void | Shipped | Request and response |
error(status, message) | HttpError (throw it) | Shipped | Request and response |
trigger | Trigger data by function type | Design | Request and response |
identity | IdentityContext | null | Shipped | ctx.identity |
state | Record<string, any> | Shipped | Request and response |
executionId | string | Shipped | This page, below |
userId | string | null | Shipped | This page, below |
projectId | string | null | Shipped | This page, below |
workspaceId | string | null | Shipped | This page, below |
organizationId | string | null | Shipped | This page, below |
kv() takes no argument in the shipped product and is always project-scoped; the scope keywords and external aliases on ctx.kv() are design. identity ships, but only user.id populates reliably right now, so treat email, name, groups, roles, and org as arriving with the full Helix Identity rollout. The four fields marked Design are not on the context object today: reaching for ctx.db(), ctx.files, ctx.queue(), or ctx.ai() in a function will not work, and this site does not document them until they ship.
Execution metadata
Five fields identify where an execution runs and who invoked it. Each is null when unknown, for example in an unprovisioned local project.
| Field | Meaning |
|---|---|
executionId | UUID per invocation, used for correlation and tracing |
userId | The invoking user, mirrors identity.user.id |
projectId | The project this deployment belongs to |
workspaceId | The workspace this deployment belongs to |
organizationId | The organization this deployment belongs to |
The SDK resolves these from different sources per environment, so function code never cares:
| Field | Deployed (Lambda) | Local (helix dev) |
|---|---|---|
executionId | Invocation payload root | x-tray-execution-id header, else generated per request |
userId | Invocation payload root, HelixExecutionPrincipal JWT claim as fallback | x-tray-user-id header (null if unset) |
projectId | Invocation payload root | projectId in helix.config.ts (null if unprovisioned) |
workspaceId | Invocation payload root | workspaceId in helix.config.ts (null if unset) |
organizationId | Invocation payload root | ctx.identity.org.id (default dev identity: dev-org; null when identity is null) |
In production the claims come from the execution JWT minted by the platform. The JWT is verified upstream; the SDK decodes it without re-verifying. projectId, workspaceId, and organizationId identify the deployment; userId identifies the caller. The JWT carries exactly these five claims and nothing else about the user, which is why ctx.identity populates incrementally. See ctx.identity.
getContext()
Handlers receive ctx as a parameter. Shared utilities deeper in the call chain can access the same context without threading it through every call:
import { getContext } from '@trayai/helix-sdk';
function getContext(): FunctionContext
The context is stored in Node.js AsyncLocalStorage, scoped per execution, so concurrent requests never bleed into each other. Calling getContext() outside a function execution throws: “getContext() called outside of a Helix function execution.”
// functions/_shared/salesforce.ts
import { getContext } from '@trayai/helix-sdk';
const AUTH = 'salesforce_prod';
const API = '{{instance_url}}/services/data/v59.0';
export async function query<T = any>(soql: string): Promise<T[]> {
const { http } = getContext(); // no ctx parameter needed
const result = await http
.authed(AUTH)
.get(`${API}/query`, { searchParams: { q: soql } })
.json<{ records: T[] }>();
return result.records;
}
| Situation | Use |
|---|---|
| Function handler (top level) | The ctx parameter |
Shared utilities in _shared/ | getContext() |
| Unit testing shared code | runWithContext() from the testing module |
| Code outside a function execution | Neither works; the error message says so |
runWithContext() for tests
To unit test shared code that calls getContext(), wrap the call in a mock context:
// tests/shared/salesforce.test.ts
import { runWithContext } from '@trayai/helix-sdk/testing';
import { query } from '../functions/_shared/salesforce';
test('query returns contacts', async () => {
const result = await runWithContext(
{ http: mockKyInstance, kv: mockKvStore },
() => query('SELECT Id FROM Contact'),
);
expect(result).toEqual([{ Id: '003xx...' }]);
});
Availability by function type
Not every field exists on every function type. Accessing an unavailable field throws a clear runtime error naming the function types that support it. defineFunction is the only function type in the shipped product, so the last three columns describe platform design.
| Context field | defineFunction | defineSchedule | defineAppTrigger | defineQueueConsumer |
|---|---|---|---|---|
config | ✓ | ✓ | ✓ | ✓ |
log | ✓ | ✓ | ✓ | ✓ |
http | ✓ | ✓ | ✓ | ✓ |
auth() | ✓ | ✓ | ✓ | ✓ |
db() | ✓ | ✓ | ✓ | ✓ |
kv() | ✓ | ✓ | ✓ | ✓ |
files | ✓ | ✓ | ✓ | ✓ |
queue() | ✓ | ✓ | ✓ | ✓ |
input | ✓ | ✗ | ✗ | ✓ |
method | ✓ | ✗ | ✗ | ✗ |
headers | ✓ | ✗ | ✗ | ✗ |
request | ✓ | ✗ | ✗ | ✗ |
status() | ✓ | ✗ | ✗ | ✗ |
error() | ✓ | ✓ | ✓ | ✓ |
trigger.data | ✗ | ✗ | ✓ | ✗ |
trigger.scheduledAt | ✗ | ✓ | ✗ | ✗ |
message | ✗ | ✗ | ✗ | ✓ |
identity | ✓ (when enabled) | ✗ | ✗ | ✗ |
state | ✓ | ✗ | ✗ | ✗ |
For how each function type is declared and routed, see the functions and routing guide.