Skip to content

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

Helix Docs

ctx.kv()

Reference for ctx.kv(), covering scope keywords, external aliases, and the full key-value API including getItem, setItem with TTL, getKeys, and incr.

For developers Updated Aug 5, 2026
View as Markdown

ctx.kv() returns a key-value store. Call it with no arguments for execution scope (the default, cleaned up when the execution ends), with a scope keyword for wider managed scopes, or with an alias to connect to an external store declared in helix.config.ts. All variants expose the same KeyValueStore API.

Signature

kv(scopeOrAlias?: string): KeyValueStore

The string argument resolves in this order:

  1. Scope keyword. 'execution', 'function', 'project', or 'workspace' selects the managed store at that scope.
  2. External alias. A key from kvStores in helix.config.ts connects to that external store through the Helix proxy service.
  3. Error. Anything else throws a clear error at startup, not at runtime.

External aliases must not collide with the four scope keywords. External stores don’t participate in the scope model; key namespacing there is your responsibility.

ScopeVisibilityLifecycle
execution (default)Current execution onlyDeleted when the execution completes
functionAll executions of the same functionDeleted when the function is deleted
projectAll functions in this projectDeleted when the project is deleted
workspaceAll projects in the workspaceDeleted when the workspace is deleted

Scope behavior, cleanup, and driver details live in the key-value store guide.

KeyValueStore

interface KeyValueStore {
  getItem<T = unknown>(key: string): Promise<T | null>;
  setItem(key: string, value: unknown, opts?: { ttl?: number }): Promise<void>;
  removeItem(key: string): Promise<void>;
  hasItem(key: string): Promise<boolean>;
  getKeys(base?: string, opts?: HelixGetKeysOptions): Promise<HelixGetKeysResult>;
  incr(key: string, delta?: number): Promise<number>;
}

interface HelixGetKeysOptions {
  maxDepth?: number;  // max colon-segment depth when filtering keys
  limit?: number;     // page size (default 100)
  cursor?: string;    // opaque cursor from a previous result
}

interface HelixGetKeysResult {
  keys: string[];
  cursor: string | null; // null = last page
}
MethodReturnsNotes
getItem<T>(key)Promise<T | null>null when the key is missing. A stored JSON null also reads as null; use hasItem to disambiguate.
setItem(key, value, opts?)Promise<void>Accepts any JSON-serializable value. opts.ttl is in seconds and behaves identically on every driver.
removeItem(key)Promise<void>Deletes the key.
hasItem(key)Promise<boolean>Existence check without fetching the value.
getKeys(base?, opts?)Promise<{ keys, cursor }>Prefix listing, keys only. Omit base (or pass "") for all keys in scope. Meta keys ($ suffix) are excluded. Fetch values with getItem per key.
incr(key, delta?)Promise<number>Atomic increment, returns the new value. Omit delta to increment by one.

Example

// functions/api/v1/flags.get.ts
import { defineFunction } from '@trayai/helix-sdk';

export default defineFunction(async (ctx) => {
  const kv = ctx.kv('project'); // shared across all functions in the project

  let flags = await kv.getItem<Record<string, boolean>>('feature_flags');
  if (!flags) {
    flags = { beta: false };
    await kv.setItem('feature_flags', flags, { ttl: 3600 });
  }

  const reads = await kv.incr('flags_read_count');
  return { flags, reads };
});

To paginate a large key set, loop until the cursor comes back null:

let cursor: string | null = null;
const allKeys: string[] = [];
do {
  const result = await kv.getKeys('session:', { limit: 100, cursor });
  allKeys.push(...result.keys);
  cursor = result.cursor;
} while (cursor);

Loading search…

Jump to a section

tab to move · esc to close