Skip to content
Last updated

FunctionContext cheat sheet

A quick lookup of everything hanging off context (Node) / Context (C#). For the conceptual tour, see context object. For method-level detail, trust TypeScript IntelliSense or dotnet's IntelliSense — this page is the "what can I type" reference.

At a glance

CapabilityNode.jsC#
Key-value cachecontext.cacheContext.Cache
Blob / file storagecontext.storageContext.Storage
SQLite databasecontext.database (path)Context.Database.ConnectionString
Structured loggerconsole / context.logContext.Logger
Project + function infocontext.configContext.Configuration
Env varscontext.env / process.envContext.Configuration
Request IDcontext.requestIdHttpContext.TraceIdentifier
Timestampcontext.timestamp(see request headers)
Voice SDK clientcontext.voiceContext.Voice
Conversation SDKcontext.conversationContext.Conversation
SMS SDKcontext.smsContext.Sms
Numbers SDKcontext.numbersContext.Numbers
Verification SDK (C#)Context.Verification
Private assetscontext.assets(filename)(package helpers)

SDK client properties are null / undefined if the required environment variables are missing — always null-check. See SDK env vars for which variables unlock each client.

Cache

// Node
await context.cache.set('session:abc', { userId: 'u1' }, 1800);  // TTL seconds
const session = await context.cache.get<{ userId: string }>('session:abc');
const exists = await context.cache.has('session:abc');
await context.cache.extend('session:abc', 600);
await context.cache.delete('session:abc');
const keys = await context.cache.keys('session:*');
const many = await context.cache.getMany<Session>(keys);
// 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");
var keys = await Context.Cache.GetKeys();

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

Storage

// 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');
// C#
await Context.Storage.WriteAsync("reports/daily.json", JsonSerializer.Serialize(data));
var text = await Context.Storage.ReadTextAsync("reports/daily.json");
var bytes = await Context.Storage.ReadAsync("reports/daily.json");
var files = await Context.Storage.ListAsync("reports/");

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

Keys can include / — treat them as folder paths.

Database

// Node — context.database is a file path
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)');
writeFileSync(context.database, Buffer.from(db.export()));
// C# — Context.Database.ConnectionString is an ADO.NET connection string
using var conn = new SqliteConnection(Context.Database.ConnectionString);
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)";
cmd.ExecuteNonQuery();

In production, the platform automatically replicates and backs up the database. No code changes required.

Logger

// Node
console.log('Request received', { callId: data.callid });
context.log?.info('Request received', { callId: data.callid });
// C#
Logger.LogInformation("Request received from {Cli}", data.Cli);

Output is captured and streamed via sinch functions logs.

Config and variables

// Node — raw
context.config.projectId
context.config.functionName
context.config.environment     // 'development' | 'production'
context.config.variables       // object of sinch.json variables

// Node — UniversalConfig helper
import { createConfig } from '@sinch/functions-runtime';
const config = createConfig(context);
config.requireSecret('STRIPE_SECRET_KEY');           // throws if missing
config.getVariable('COMPANY_NAME', 'Acme Corp');
config.getApplicationCredentials();                   // voice app key/secret
config.isDevelopment();
config.isProduction();
// C#
var companyName = Configuration["COMPANY_NAME"] ?? "Acme Corp";
var apiKey = Configuration["STRIPE_SECRET_KEY"]
    ?? throw new InvalidOperationException("STRIPE_SECRET_KEY is required");

See configuration & secrets for how the layers fit together.

Pre-wired SDK clients

Initialized by the runtime when the required env vars are present. See SDK env vars for the full list.

// Node — always null-check
if (context.voice) {
  await context.voice.callouts.custom({ ... });
}

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

if (context.sms) {
  await context.sms.batches.send({ ... });
}
// C# — same pattern
if (Context.Voice is not null)
    await Context.Voice.Callouts.CustomAsync(...);

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

Assets

Private files you ship alongside your code. Do not use fs.readFileSync('./assets/...') — the deployed artifact structure differs from dev.

// Node
const greeting = await context.assets('greetings/en.txt');
const audioBuffer = await context.assets('prompts/welcome.wav');
// C# — context.Assets helper
var greeting = await Context.Assets("greetings/en.txt");

For public static files (images, CSS), use the public/ directory — the runtime serves it at /.

Request metadata

// Node
context.requestId   // tracing ID unique to this invocation
context.timestamp   // ISO 8601 when the request entered the runtime

Include context.requestId in logs and upstream calls for cross-service debugging.

Custom HTTP request and response (Node custom endpoints only)

For non-voice custom endpoints, the second argument is a FunctionRequest:

interface FunctionRequest {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  path: string;
  query?: Record<string, string | string[]>;
  headers?: Record<string, string>;
  body?: unknown;
  params?: Record<string, string>;
}

And you return a plain { statusCode, body, headers? } object:

return { statusCode: 200, body: { status: 'healthy' } };
return { statusCode: 400, body: { error: 'Missing field: name' } };
return { statusCode: 404, body: { error: 'Not found' } };

C# custom endpoints use regular ASP.NET MVC action results (Ok, NotFound, BadRequest, StatusCode, etc.).