# Context object

Every handler receives a `FunctionContext` as its first argument (Node) or as an injected dependency (C#). Think of it as the standard library of the Sinch runtime — everything you need to interact with state, configuration, and the rest of Sinch is hanging off of it.

## What's in it

Both runtimes expose the same capabilities with idiomatic names.

| Capability | Node.js | C# |
|  --- | --- | --- |
| Key-value cache | `context.cache` | `Context.Cache` |
| Blob / file storage | `context.storage` | `Context.Storage` |
| SQLite database | `context.database` (path) | `Context.Database` (conn) |
| Structured logger | `console` | `Context.Logger` |
| Project + function info | `context.config` | `Context.Configuration` |
| Env vars | `context.env` | `Context.Configuration` |
| Request metadata | `context.requestId`, `.timestamp` | Available via `HttpContext` |
| Pre-wired Voice SDK | `context.voice` | `Context.Voice` |
| Pre-wired Conversation SDK | `context.conversation` | `Context.Conversation` |
| Pre-wired SMS SDK | `context.sms` | `Context.Sms` |
| Pre-wired Numbers SDK | `context.numbers` | `Context.Numbers` |
| Pre-wired Verification SDK | — | `Context.Verification` |
| Read private assets | `context.assets(filename)` | Package-level helpers |


The SDK client properties are `null` / `undefined` if the corresponding environment variables are not set — see [configuration & secrets](/docs/functions/functions/concepts/configuration-secrets) for which vars unlock which client.

## Cache

Key-value store with TTL. Use it for per-call state (keyed by `callid`), rate limiting, session data, and anything you want to survive between callbacks without a full database trip.

- **Dev:** in-memory. Lost on restart.
- **Prod:** persistent, shared across invocations, distributed.


```typescript
// Node
await context.cache.set('session:abc', { userId: 'u1' }, 1800);
const session = await context.cache.get<{ userId: string }>('session:abc');
await context.cache.extend('session:abc', 600);
await context.cache.delete('session:abc');
const keys = await context.cache.keys('session:*');
```

```csharp
// C#
await Context.Cache.Set("session:abc", new { UserId = "u1" }, 1800);
var session = await Context.Cache.Get<MySession>("session:abc");
if (await Context.Cache.Exists("session:abc"))
    await Context.Cache.Delete("session:abc");
```

Default TTL is 3600 seconds. Values are JSON-serialized.

## Storage

Blob storage for persistent files — call recordings, reports, user uploads, arbitrary data.

- **Dev:** local filesystem in `./storage/`.
- **Prod:** durable cloud storage with a local read cache.


```typescript
// Node
await context.storage.write('reports/daily.json', JSON.stringify(data));
const buf = await context.storage.read('reports/daily.json');
const files = await context.storage.list('reports/');
const exists = await context.storage.exists('reports/old.json');
await context.storage.delete('reports/old.json');
```

```csharp
// C#
await Context.Storage.WriteAsync("reports/daily.json", JsonSerializer.Serialize(data));
var text = await Context.Storage.ReadTextAsync("reports/daily.json");
var files = await Context.Storage.ListAsync("reports/");

// Streaming for big files
using var stream = await Context.Storage.ReadStreamAsync("large-file.bin");
```

Keys can include path separators — treat them like folder paths.

## Database

A per-function SQLite database. You bring your own SQLite library; the runtime manages the file location and replication.

- **Dev:** plain local SQLite file in `./data/`.
- **Prod:** SQLite with automatic durable replication. The database survives restarts and scale events, with no code changes required.


### Node — `sql.js` (recommended)

Pure JavaScript SQLite compiled to WebAssembly. No native compilation.

```typescript
import initSqlJs from 'sql.js';
import { readFileSync, writeFileSync, existsSync } from 'fs';

const SQL = await initSqlJs();
const buf = existsSync(context.database) ? readFileSync(context.database) : undefined;
const db = new SQL.Database(buf);

db.run('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)');
db.run('INSERT OR REPLACE INTO kv VALUES (?, ?)', ['greeting', 'hello']);

writeFileSync(context.database, Buffer.from(db.export()));
db.close();
```

`better-sqlite3` is faster but needs native compilation — use it when you can guarantee the build environment has `python3` / `make` / `g++`.

### C# — `Microsoft.Data.Sqlite`

```csharp
using var conn = new SqliteConnection(Context.Database.ConnectionString);
conn.Open();

using var cmd = conn.CreateCommand();
cmd.CommandText = "CREATE TABLE IF NOT EXISTS call_log (id INTEGER PRIMARY KEY, caller TEXT, ts INTEGER)";
cmd.ExecuteNonQuery();
```

Or with Dapper:

```csharp
using var conn = new SqliteConnection(Context.Database.ConnectionString);
var calls = await conn.QueryAsync<CallRecord>("SELECT * FROM call_log ORDER BY ts DESC LIMIT 10");
```

## Config and variables

`context.config` exposes project and function metadata — useful for logging, feature flags by environment, and constructing return URLs.

```typescript
context.config.projectId; // your Sinch Project ID
context.config.functionName; // e.g. 'my-ivr'
context.config.environment; // 'development' | 'production'
context.config.variables; // object of sinch.json variables
```

For a richer API, use `createConfig(context)` (aka `createUniversalConfig`):

```typescript
import { createConfig } from '@sinch/functions-runtime';

const config = createConfig(context);
const apiKey = config.requireSecret('STRIPE_SECRET_KEY'); // throws if missing
const company = config.getVariable('COMPANY_NAME', 'Acme Corp');
if (config.isProduction()) {
  /* ... */
}
```

In C#, use the injected `IConfiguration`:

```csharp
var companyName = Configuration["COMPANY_NAME"] ?? "Acme Corp";
var apiKey = Configuration["STRIPE_SECRET_KEY"]
    ?? throw new InvalidOperationException("STRIPE_SECRET_KEY is required");
```

## Pre-wired Sinch SDK clients

The runtime instantiates Sinch SDK clients for you and attaches them to the context. No credential management in your function.

| Client | Node | C# | Env vars required |
|  --- | --- | --- | --- |
| Voice | `context.voice` | `Context.Voice` | `VOICE_APPLICATION_KEY`, `VOICE_APPLICATION_SECRET` |
| Conversation | `context.conversation` | `Context.Conversation` | `CONVERSATION_APP_ID` |
| SMS | `context.sms` | `Context.Sms` | `SMS_SERVICE_PLAN_ID` |
| Numbers | `context.numbers` | `Context.Numbers` | `ENABLE_NUMBERS_API=true` |
| Verification (C#) | — | `Context.Verification` | `VERIFICATION_APPLICATION_ID`, `VERIFICATION_APPLICATION_SECRET` |


All clients require the base Project credentials: `PROJECT_ID`, `PROJECT_ID_API_KEY`, `PROJECT_ID_API_SECRET`.

If a required variable is missing, the property is `null` — always null-check before using:

```typescript
if (context.conversation) {
  await context.conversation.messages.send({ ... });
}
```

```csharp
if (Context.Conversation is not null)
    await Context.Conversation.Messages.Send(...);
```

## Assets

Private files you ship alongside your code — prompts, JSON data, templates. Keep them in the `assets/` directory and read them with `context.assets(filename)`. Do **not** use `fs.readFileSync('./assets/...')` — the deployed artifact structure is different from your dev tree, and the runtime handles the translation for you.

```typescript
const greetingText = await context.assets('greetings/en.txt');
```

For public static files (images, CSS, public JSON), use the `public/` directory instead. The runtime serves it at `/`.

## Logger

In Node, use `console.log`, `console.warn`, and `console.error` — the runtime captures and forwards all three with per-request context. In C#, use `Context.Logger` (standard `ILogger`). In production, output is indexed and streamed via `sinch functions logs`.

```typescript
console.log('Call received', { callId: data.callid, cli: data.cli });
```

```csharp
Logger.LogInformation("Call received from {Cli}", data.Cli);
```

## Request metadata

- `context.requestId` — a tracing ID unique to this invocation. Include it in logs and when calling out to other services; it makes cross-service debugging bearable.
- `context.timestamp` — ISO 8601 timestamp when the request entered the runtime.


## Related

- [Configuration & secrets](/docs/functions/functions/concepts/configuration-secrets) — how env vars, `sinch.json` variables, and secrets flow
- [Local vs prod](/docs/functions/functions/concepts/local-vs-prod) — what changes between dev and deployed environments
- [Function context reference](/docs/functions/reference/function-context) — Node + C# side-by-side cheat sheet