helix.config.ts declares a Helix project’s identity and its authentication aliases. The CLI reads it at build time, the dev server at startup, and the SDK at runtime. A project has one config file: it names the project, the workspace and project IDs the CLI works against, and the environment those IDs live in.
Type definition
// @trayai/helix-sdk
interface HelixConfig {
name: string; // project name, used when the first deploy provisions
workspaceId: string; // UUID of the Tray workspace
projectId?: string; // UUID of the provisioned project
environment?: string; // Helix environment, for example 'us1'
authentications?: Record<string, string>; // alias to auth UUID
}
Export the object with defineConfig so the compiler checks the shape:
// helix.config.ts
import { defineConfig } from '@trayai/helix-sdk';
export default defineConfig({
name: 'customer-health',
workspaceId: 'f8e7d6c5-4b3a-2190-8765-abcdef012345',
});
Keys
| Key | Type | Required | Purpose |
|---|---|---|---|
name | string | Yes | The project name. helix init writes it, and the first helix deploy uses it when it provisions the project. |
workspaceId | string (UUID) | Yes | Ties the project to a Tray workspace. Determines which authentications resolve, locally and on deploy. |
projectId | string (UUID) | To deploy only | The provisioned project this checkout points at. Names the production subdomain and the resources a deploy provisions. |
environment | string | No | The Helix environment the CLI talks to, for example us1. Written by helix env set. |
authentications | Record<string, string> | No | Maps human-readable aliases to auth UUIDs. Used as ctx.http.authed('alias'). |
name
The project’s name. helix init [project-name] writes it into the scaffold. On the first helix deploy the platform provisions a project under this name, so it’s the label you see in the dashboard’s Projects list. The deploy also generates an AI description of the project, and regenerates it on every later deploy.
helix deploy --name <name> overrides the config value for that one deploy.
workspaceId
Required UUID. The workspace matters both locally and on deploy: ctx.http.authed('alias') resolves against the workspace’s auth service even during helix dev, and agent tooling asks the workspace which authentications are available.
Set it with helix workspace select (interactive picker) or helix workspace set <id> (when you already know the ID), or pass helix init --workspace-id <id> at scaffold time.
projectId
Optional UUID until you deploy. Nothing is provisioned when you run helix init, so a fresh project has no ID: the first helix deploy provisions one, and later deploys update that project in place. Set it yourself with helix project set <id> to point a checkout at an existing project, or pass helix init --project-id <id>.
The project ID names the production subdomain, https://{project-id}.helix-app.ai, and the resources provisioned for the project. It’s also what helix dev needs to reach persistent key-value storage: with no project ID, local KV is in-memory and resets when you restart the dev server.
environment
The Helix environment the CLI talks to, written by helix env set <environment> and persisted for later commands. us1 is the environment shown in that command’s help text. This is separate from the --env <us1|staging> flag on login, logout, and whoami, which selects the environment for a single session command and doesn’t read the config file. See the configuration model.
authentications
Maps aliases to auth UUIDs stored in Helix. Your code references the alias (ctx.http.authed('salesforce_prod')); the UUID points to a credential record on Helix’s servers, and all credential injection happens server-side through the auth proxy. Credentials themselves never appear in this file, which is why the mapping is safe to commit.
Write entries with helix auth connect (interactive) or helix auth add <alias> <uuid> (when you know the UUID). Authentications are created in Tray iPaaS today, not in Helix. Calling authed() with an alias missing from this map throws immediately, and the error lists the aliases that are available.
Complete example
// helix.config.ts
import { defineConfig } from '@trayai/helix-sdk';
export default defineConfig({
// Project name. The first deploy provisions a project under this name.
name: 'customer-health',
// Workspace UUID (required). Auth aliases resolve against this
// workspace, locally and on deploy.
workspaceId: 'f8e7d6c5-4b3a-2190-8765-abcdef012345',
// Project UUID. Written by the first deploy, or by helix project set.
projectId: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
// Helix environment. Written by helix env set.
environment: 'us1',
// Alias to auth UUID. Use as ctx.http.authed('salesforce_prod').
authentications: {
salesforce_prod: '1a0fbf5c-2c9e-4aa1-ada3-ccbba65019ab',
openai: 'ee41bc90-1234-5678-9abc-def012345678',
},
});
What does not go in this file
| Concern | Where it lives |
|---|---|
| Credential values and injection rules | Helix’s servers, alongside the credential records. Config holds only alias-to-UUID mappings |
| Your Tray session token | ~/.tray.config, written by helix login and shared with connector-cli |
| Function-level settings (input schemas, handlers) | The function files, via defineFunction |
| Who can open the deployed app | The project’s Access Control tab in the dashboard, not a code or config construct |
| Infrastructure settings (region, runtime, memory) | The workspace, not the project |
TypeScript path aliases
Use tsconfig path mapping so imports work from any directory depth. The canonical convention is @project/*:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@project/*": ["./*"]
}
},
"exclude": ["node_modules", ".helix"]
}
// functions/api/v1/contacts/index.get.ts
import { lookupContact } from '@project/functions/_shared/contacts';
The SDK and CLI respect any path mapping configured in tsconfig.json; other conventions such as @/lib/* work too. Pick one and apply it consistently.
Roadmap: keys the shipped product does not accept
The designed shape adds six more keys, plus a ConfigValue type that allows a thunk anywhere a string is accepted:
// Design only. Not accepted by the shipped CLI.
// A literal string, or a thunk evaluated once when the config file loads.
type ConfigValue = string | (() => string);
interface HelixConfig {
databases?: Record<string, {
type: 'postgres' | 'mysql' | 'mssql';
auth: string; // UUID of a credential record
schema?: string; // path to a Drizzle schema file
}>;
kvStores?: Record<string, {
type: 'redis';
auth: string;
}>;
queues?: Record<string,
{} | // managed queue (empty object)
{
type: 'redis' | 'rabbitmq' | 'kafka';
auth: string;
}
>;
roles?: Record<string, string>; // role ID to display name
mcp?: McpConfig; // MCP server configuration
config?: Record<string, ConfigValue>; // values surfaced on ctx.config
}
databases
Named connections to databases outside Helix’s managed infrastructure, used as ctx.db('alias'). Helix ships no managed database and no ctx.db(), so nothing in this block resolves today.
| Field | Type | Required | Description |
|---|---|---|---|
type | 'postgres' | 'mysql' | 'mssql' | Yes | Database engine. The design marks mssql as a later addition than the other two. |
auth | string (UUID) | Yes | Credential record in the Helix auth service. |
schema | string | No | Path to a Drizzle schema file (for example 'db/customer-pg.schema.ts'). Providing it turns on typed queries and Drizzle Kit migrations for this connection. |
The managed database has no entry in the file. Its schema lives in db/schema.ts using Drizzle ORM.
kvStores
Named connections to external key-value stores, used as ctx.kv('alias'). Each entry has a type ('redis', with memcached and dynamodb designed for later) and an auth UUID pointing at a credential record. Aliases must not collide with the managed scope keywords, because ctx.kv() resolves scope keywords first.
The shipped key-value store is project-scoped, takes no alias, and has no external backends. See the key-value store guide.
queues
Every queue the project touches is declared here, managed and external, so the CLI has a complete inventory for provisioning and a typo can’t silently create a queue. Calling ctx.queue('name') with an undeclared name throws at startup.
| Entry shape | Meaning |
|---|---|
{} (empty object) | Managed queue. Helix provisions it: in-memory during helix dev, SQS in production. |
{ type, auth } | External queue. The Helix proxy resolves credentials from the auth service and connects to the external backend. redis first, with rabbitmq and kafka designed for later. |
Consumers live in functions/_queues/<name>.ts, where the filename matches the queue name. HTTP is the only trigger a shipped function can have, so no queue consumer runs today.
roles
A flat map declaring the project’s authorization vocabulary. The key is the role ID, referenced in per-function access exports and surfaced in ctx.identity.user.roles. The value is the display name shown in the dashboard.
In the design, helix deploy registers the map with the platform and the dashboard offers exactly these roles for assignment, validating role IDs at build time. In the shipped product, app access is a dashboard setting on the project (Workspace, Only people invited, or Organization) and there is no role map and no code construct. See identity and roles.
mcp
Exposes selected functions as Model Context Protocol tools that AI agents can discover and call, served per project. Helix ships an MCP server for the CLI, which agents use to work on a project; exposing a project’s own functions as tools is design only.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Turns the MCP server on or off for this project. |
tools | string[] | Yes | Function paths to expose, relative to functions/, without a method suffix. 'tools/lookup-customer' maps to functions/tools/lookup-customer.ts. |
server | object | No | Server metadata shown to MCP clients. |
server.name | string | No | Display name of the MCP server. |
server.description | string | No | What the toolset does. |
server.version | string | No | Semantic version of the toolset. |
auth | object | No | Authentication requirements for tool calls. |
auth.required | boolean | No | When true, every tool call must authenticate. |
auth.methods | ('helix-identity' | 'api-key' | 'oauth')[] | No | Accepted authentication methods. |
rateLimit | object | No | Rate limits applied to MCP tool calls. |
rateLimit.requestsPerMinute | number | No | Maximum tool calls per minute. |
rateLimit.requestsPerHour | number | No | Maximum tool calls per hour. |
rateLimit.concurrent | number | No | Maximum concurrent tool invocations. |
Each exposed function also declares a tool named export with its MCP name, description, and input schema. Individual tools can override the server-level rate limit with a rateLimit export.
config
Key-value pairs surfaced on ctx.config in every function. Values resolve to a flat Record<string, string>: ctx.config.maxRetries is always '3', never 3. All-string values mean a secret from the process environment flows through without a coercion step, and a value behaves the same whether it came from a literal or a thunk. There is no ctx.config in the shipped SDK.
ConfigValue and secrets
ConfigValue is string | (() => string). Thunks are evaluated once, when the config file is loaded, so ctx.config and ctx.http.authed() only ever see resolved strings. The design pairs them with requireEnv to keep secrets out of the committed file:
// Design only. Not accepted by the shipped CLI.
import { defineConfig, requireEnv } from '@trayai/helix-sdk';
export default defineConfig({
workspaceId: 'f8e7d6c5-4b3a-2190-8765-abcdef012345',
config: {
internalSecret: () => requireEnv('INTERNAL_SECRET'),
},
});
requireEnv throws if the variable is missing, so a forgotten secret fails fast at load time instead of becoming a silent empty string. The underlying values are set per project with helix vars set, which the shipped CLI does not have either. The shipped config accepts plain strings only.
Per-target config files
The design gives a project one config file per deploy target, with no environment map, no merging, and no active-environment state: a different target is a different file.
helix.config.ts default; the target you develop against
helix.staging.config.ts
helix.production.config.ts
Resolution precedence, highest first:
| Precedence | Source | Example |
|---|---|---|
| 1 | --config <path> flag | helix deploy --config helix.production.config.ts |
| 2 | HELIX_CONFIG environment variable | HELIX_CONFIG=helix.staging.config.ts helix build |
| 3 | ./helix.config.ts | The default when neither is set |
Config files are ordinary TypeScript modules, so shared values can be exported from a shared module and spread into each target file.
The shipped CLI has no --config flag and reads no HELIX_CONFIG variable. One project has one helix.config.ts, and helix env set chooses the environment it points at.