# 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.

`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

```typescript
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.

| 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](/documentation/guides/key-value-store/).

## KeyValueStore

```typescript
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. |

:::roadmap
`getItems` (batch read of known keys, following the unstorage signature) is not in v1. Use `getKeys` for keys, then `getItem` per value. External `memcached` and `dynamodb` store types are also planned but not yet supported; `redis` is available today.
:::

## Example

```typescript
// 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`:

```typescript
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);
```

---

Canonical: https://helix.tray.ai/documentation/reference/context/kv/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt