ctx.identity carries the authenticated user for HTTP-triggered functions. On paths excluded from authentication it is null instead of throwing, so handlers can serve both authenticated and anonymous requests. Schedules run without a user, and their context has no identity field.
When a request is made by a signed-in user, or with a project API token, ctx.identity describes that user (for a token, the API user the token belongs to). ctx.identity is null when there is no user: runs started with ctx.callFunctionAsync, and anonymous requests on paths excluded from authentication.
IdentityContext
interface IdentityContext {
user: {
id: string; // Helix user ID
email: string | null; // null when the platform has no email for the caller
name: string | null; // null when the platform has no name for the caller
};
}
ctx.identity is typed IdentityContext | null.
| Field | Type | Description |
|---|---|---|
user.id | string | Helix user ID. Also mirrored on the top-level userId execution metadata field. |
user.email | string | null | The user’s email address. null when the platform has no email for the caller. |
user.name | string | null | Display name. null when the platform has no name for the caller. |
Example
// functions/api/v1/dashboard.get.ts
import { defineFunction } from '@trayai/helix-sdk';
export default defineFunction(async (ctx) => {
if (!ctx.identity) {
// Reachable only on paths excluded from authentication
throw ctx.error(401, 'Sign-in required');
}
const { user } = ctx.identity;
// email and name can be null, so fall back to the id
const label = user.name ?? user.email ?? user.id;
ctx.log.info('Dashboard accessed', { user: label });
return getDashboard(ctx, { greeting: `Welcome back, ${label}` });
});
Identity in local development
helix dev has no sign-in flow, and the dev server does not supply an identity, so ctx.identity is null locally. The one exception is helix agent chat --user <id>: tools run by that agent see { user: { id, email: null, name: null } } (see helix agent). Handle null the way you handle an excluded path: branch on it instead of assuming a user.
To test code that reads the identity, pass one to runWithContext from the testing module:
import { runWithContext } from '@trayai/helix-sdk/testing';
const identity = { user: { id: 'u_1', email: 'ada@example.com', name: 'Ada' } };
const result = await runWithContext({ identity }, () => greetUser());
expect(result).toBe('Hello, Ada');
Who can open a published app is set on the project’s Access Control tab, not in code. See identity and roles.