Skip to content

Pre-GA Design Partner and Early Access only. Request access

Helix Docs

Function definition reference

Exact signatures for defineFunction and defineSchedule, plus the named exports the runtime reads.

For developers Updated Sep 18, 2026
View as Markdown

A Helix function is a TypeScript file that default-exports defineFunction or defineSchedule from @trayai/helix-sdk. The file’s location and suffix determine its trigger and route, and named exports (input, output) are read by the runtime for validation. This page gives exact signatures, every option with its default, and a summary of the routing rules.

HelperTriggerLocation
defineFunctionIncoming HTTP requestAnywhere routable under functions/
defineScheduleCron schedulefunctions/_scheduled/

App access is set per project on the dashboard’s Access Control tab; see identity and roles.

defineFunction

Handles one HTTP method (or all methods, for files without a method suffix). Generics give compile-time types; the named exports give runtime validation. You write each schema once and use it for both.

// @trayai/helix-sdk

// Sentinel type: use instead of `undefined` when skipping input
type NoInput = typeof NoInput;

// No validation, bare handler
function defineFunction(
  handler: (ctx: FunctionContext) => any
): FunctionDef;

// Input only
function defineFunction<TInput extends z.ZodType>(
  handler: (ctx: FunctionContext & { input: z.infer<TInput> }) => any
): FunctionDef;

// Input + output (use NoInput to skip input validation)
function defineFunction<
  TInput extends z.ZodType | NoInput,
  TOutput extends z.ZodType,
>(
  handler: (ctx: FunctionContext & {
    input: TInput extends z.ZodType ? z.infer<TInput> : any;
  }) => MaybePromise<z.infer<TOutput>>
): FunctionDef;

The first generic types ctx.input from the input export; the second types the return value from the output export. NoInput fills the first slot when a function validates output only:

// functions/api/v1/stats.get.ts
import { z } from 'zod';
import { defineFunction, NoInput } from '@trayai/helix-sdk';

export const output = z.object({
  totalUsers: z.number(),
  activeToday: z.number(),
});

export default defineFunction<NoInput, typeof output>(async (ctx) => {
  return { totalUsers: 1234, activeToday: 56 };
});

A typical function with input and output validation:

// functions/api/v1/users.post.ts
import { z } from 'zod';
import { defineFunction } from '@trayai/helix-sdk';

export const input = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(['admin', 'member', 'viewer']).default('member'),
});

export default defineFunction<typeof input>(async (ctx) => {
  const kv = ctx.kv();
  const user = { id: crypto.randomUUID(), ...ctx.input };
  await kv.setItem(`user:${user.id}`, user);
  ctx.status(201);
  return user;
});

Return an object and the runtime sends it as JSON with status 200. Use ctx.status() to change the status, throw ctx.error(status, message) for errors, or return a Response object for full control (custom headers, non-JSON bodies, streaming).

defineSchedule

Runs on a cron schedule. Files live in functions/_scheduled/ and are never routable. A schedule exists whenever its file exists; there is no enabled flag, so remove the file to delete the schedule on the next deploy. Pausing and resuming without a deploy happens platform-side, and paused state survives redeploys.

// functions/_scheduled/daily-report.ts
import { defineSchedule } from '@trayai/helix-sdk';

export default defineSchedule({
  cron: '0 9 * * MON-FRI',
  timezone: 'Europe/London',
  handler: async (ctx) => {
    ctx.log.info('Report run', { scheduledAt: ctx.trigger.scheduledAt });
  },
});
OptionTypeRequiredDefaultDescription
cronstringYesnoneFive-field standard cron expression: minute, hour, day, month, weekday. Month and weekday names are accepted; ? is treated as *.
timezonestringNoUTCNamed IANA timezone, for example 'Europe/London'. Offset forms like '+05:00' are rejected.
handlerfunctionYesnoneReceives ctx with all platform services but no request data: ctx.input and ctx.method are not available. The return value is discarded.

Schedules are validated at build time, fail-closed: an invalid file fails the whole build with every problem listed. A cron expression cannot constrain day-of-month and day-of-week together, six-field expressions are rejected, and a project can hold at most 100 schedule files.

ctx.trigger carries scheduledAt (when the cron said to fire), firedAt (when it actually started), and isManual (always false today). A failed run is retried once without being marked as a retry, and overlapping runs are allowed, so write handlers to be idempotent. See the scheduled functions guide.

Named exports read by the runtime

ExportWhen it runsOn failure
inputBefore the handler; merged request data is validated400
outputAfter the handler; the return value is validated500

If a file declares none of these, no validation occurs and the handler receives raw, unvalidated data.

input

A Zod schema validating everything coming into the function. The runtime merges three sources into one object, validates it, and puts the result on ctx.input:

Path params   { userId: "abc-123" }
Query params  { page: "2" }
Request body  { name: "Alice" }
Merged        { userId: "abc-123", page: "2", name: "Alice" }

Merge priority on key conflicts: path params beat body, body beats query params. Query params arrive as strings, so use z.coerce.number() where numbers are expected. Validation failure returns 400 before the handler runs.

output

A Zod schema validating the handler’s return value after it completes. Failure returns 500, since an invalid response indicates a bug in the function, not the caller. The output schema is not applied to streaming responses: a Response with a ReadableStream body can’t be validated before it has been produced, so validation only runs on plain objects and JSON.

Routing rules

The file system is the router: file path becomes URL path, file suffix becomes HTTP method, and directory nesting creates path segments.

RuleBehavior
Method suffix (.get.ts, .post.ts, .put.ts, .patch.ts, .delete.ts)The file handles that HTTP method only. functions/api/v1/users.post.ts handles POST /api/v1/users.
No method suffix (.ts)Catch-all: handles every method on the path. Branch on ctx.method. Prefer method-specific files except for webhooks.
[param] in a file or folder nameDynamic segment. functions/api/v1/users/[userId].get.ts handles GET /api/v1/users/:userId; the value arrives merged into ctx.input.
Underscore prefix (_)Excluded from routing: _shared/ (importable utilities), _scheduled/ (cron functions).
Defined path, undefined methodAutomatic 405 Method Not Allowed. If only users.get.ts and users.post.ts exist, DELETE /api/v1/users returns 405.
users.get.ts and users/index.get.ts both existindex.get.ts takes priority.
Route priorityFunction routes match first, then static files from app/public/ or built SPA assets, then the SPA fallback (app/index.html). Functions always win over SPA routes.

Loading search…

Jump to a section

tab to move · esc to close