# The ctx object

> Field-by-field reference for the ctx object Helix functions receive, plus getContext() for shared code and availability by function type.

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()`.

:::roadmap{title="Five ctx fields are design, not product"}
`ctx.db()`, `ctx.queue()`, `ctx.files`, `ctx.ai()`, and `ctx.config` are documented here so the intended shape is on record, and each is marked `Design` in the table below. None of them exists in the shipped SDK. `defineSchedule`, `defineAppTrigger`, and `defineQueueConsumer` do not exist either, because HTTP is the only trigger a function can have. The shipped data service is the project-scoped [key-value store](/documentation/guides/key-value-store/). See [limits and roadmap](/documentation/reference/limits/).
:::

## Surface

| Field | Type | Status | Reference |
|---|---|---|---|
| `config` | `Record<string, string>` | Design | [Configuration](/documentation/reference/configuration/) |
| `log` | `ContextLogger` | Shipped | [ctx.log](/documentation/reference/context/log/) |
| `http` | `KyInstance` | Shipped | [ctx.http](/documentation/reference/context/http/) |
| `auth(alias)` | `Promise<Record<string, string>>` | Shipped | [ctx.auth()](/documentation/reference/context/http/) |
| `db(alias?)` | `DrizzleInstance` | Design | [Limits and roadmap](/documentation/reference/limits/) |
| `kv()` | `KeyValueStore` | Shipped | [ctx.kv()](/documentation/reference/context/kv/) |
| `files` | `FileSystemClient` | Design | [Limits and roadmap](/documentation/reference/limits/) |
| `queue(name)` | `QueueClient` | Design | [Limits and roadmap](/documentation/reference/limits/) |
| `ai(model?)` | `AIClient` | Design | [Limits and roadmap](/documentation/reference/limits/) |
| `input` | Validated merged input | Shipped | [Request and response](/documentation/reference/context/request/) |
| `method`, `headers`, `path` | `string`, `Headers`, `string` | Shipped | [Request and response](/documentation/reference/context/request/) |
| `request` | Raw request accessor | Shipped | [Request and response](/documentation/reference/context/request/) |
| `status(code)` | `void` | Shipped | [Request and response](/documentation/reference/context/request/) |
| `error(status, message)` | `HttpError` (throw it) | Shipped | [Request and response](/documentation/reference/context/request/) |
| `trigger` | Trigger data by function type | Design | [Request and response](/documentation/reference/context/request/) |
| `identity` | `IdentityContext \| null` | Shipped | [ctx.identity](/documentation/reference/context/identity/) |
| `state` | `Record<string, any>` | Shipped | [Request and response](/documentation/reference/context/request/) |
| `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()](/documentation/reference/context/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](/documentation/reference/context/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:

```typescript
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."

```typescript
// 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:

```typescript
// 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](/documentation/guides/functions-and-routing/).

---

Canonical: https://helix.tray.ai/documentation/reference/context/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt