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:
- Scope keyword.
'execution','function','project', or'workspace'selects the managed store at that scope. - External alias. A key from
kvStoresinhelix.config.tsconnects to that external store through the Helix proxy service. - 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.
| Scope | Visibility | Lifecycle |
|---|---|---|
execution (default) | Current execution only | Deleted when the execution completes |
function | All executions of the same function | Deleted when the function is deleted |
project | All functions in this project | Deleted when the project is deleted |
workspace | All projects in the workspace | Deleted 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
}
| Method | Returns | Notes |
|---|---|---|
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);