Skip to content
Discord Get Started

Runtime & ctx

Every function receives two arguments: input (the JSON payload) and ctx (the runtime context). The ctx object is the primary way functions interact with your database.

JavaScript
module.exports = {
handler: async (input, ctx) => {
// ctx.db — SQL query interface
// ctx.fs9 — filesystem read/write interface
// ctx.self — metadata about the current function run
}
};

Metadata about the current execution:

PropertyTypeDescription
functionIdstringUUID of the function
versionIdstringUUID of the active version being executed
runIdstringUUID of the current run
triggerTypestringHow the function was invoked: invoke, cron, or api
JavaScript
module.exports = {
handler: async (input, ctx) => {
console.log(`Run ${ctx.self.runId} triggered via ${ctx.self.triggerType}`);
return { runId: ctx.self.runId };
}
};

SQL query interface. Has a single method:

Execute a SQL statement with optional parameterized values.

JavaScript
const result = await ctx.db.query(
"SELECT id, name, email FROM users WHERE active = $1 LIMIT $2",
[true, 10]
);

Parameters:

  • sql — SQL string with $1, $2, etc. for parameter placeholders
  • params — optional array of parameter values

Returns a QueryResult:

FieldTypeDescription
columnsArray<{name, type}>Column metadata
rowsunknown[][]Row tuples, ordered to match columns
row_countnumberNumber of rows affected or returned
commandstringSQL command tag: SELECT, INSERT, UPDATE, DELETE, etc.

Rows are returned as arrays of values (not objects). Map them yourself if you need named fields:

JavaScript
module.exports = {
handler: async (input, ctx) => {
const result = await ctx.db.query("SELECT id, name, email FROM users");
const users = result.rows.map(([id, name, email]) => ({ id, name, email }));
return { users, total: result.row_count };
}
};
JavaScript
module.exports = {
handler: async (input, ctx) => {
const result = await ctx.db.query(
"SELECT id, title, status FROM tasks WHERE assignee = $1 ORDER BY created_at DESC",
[input.userId]
);
return {
tasks: result.rows.map(([id, title, status]) => ({ id, title, status })),
count: result.row_count
};
}
};
JavaScript
module.exports = {
handler: async (input, ctx) => {
await ctx.db.query(
"INSERT INTO events (type, payload, created_at) VALUES ($1, $2, NOW())",
[input.eventType, JSON.stringify(input.data)]
);
return { recorded: true };
}
};
JavaScript
module.exports = {
handler: async (input, ctx) => {
const result = await ctx.db.query(`
SELECT
date_trunc('day', created_at) as day,
count(*) as total,
count(DISTINCT user_id) as unique_users
FROM events
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY 1 ORDER BY 1 DESC
`);
return {
metrics: result.rows.map(([day, total, unique_users]) => ({
day, total, unique_users
}))
};
}
};

Functions run as the authenticated role. Tables created by the admin role need explicit grants:

SQL
GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO authenticated;

Filesystem interface for reading and writing files in your database’s storage. Use --fs9-scope at deploy time to declare which paths your function accesses.

MethodDescription
read(path)Read file contents as UTF-8 string
readBase64(path)Read file contents as base64-encoded string
write(path, content)Write or overwrite a file
list(path?)List directory entries with metadata
stat(path)Get file metadata (size, type, mtime, generation, mode, sealed, storage)
delete(path)Delete a file or directory
JavaScript
module.exports = {
handler: async (input, ctx) => {
// Write a file
await ctx.fs9.write("/reports/summary.txt", "Report content here");
// Read it back
const content = await ctx.fs9.read("/reports/summary.txt");
// List directory contents
const entries = await ctx.fs9.list("/reports/");
// Get file metadata
const stat = await ctx.fs9.stat("/reports/summary.txt");
// stat: { path, type: "file", size: 19, mtime: "2026-04-01T...",
// generation: 2, mode: 420, sealed: false, storage: "inline" }
return { content, fileCount: entries.length, stat };
}
};

The list() and stat() responses include full metadata for each entry:

FieldTypeDescription
pathstringFull path of the entry
typestringfile or dir
sizenumberSize in bytes
mtimestringLast modified time (ISO 8601)
generationnumberVersion generation counter
modenumberUnix file mode
sealedbooleanWhether the file is sealed (immutable)
storagestringStorage backend (e.g., inline)

For binary files (images, xlsx, zip), use readBase64:

JavaScript
const base64 = await ctx.fs9.readBase64("/data/image.png");
const buffer = Buffer.from(base64, "base64");

The default deployment mode is a single file. If your function needs npm packages or multiple source files, bundle them with esbuild before deploying.

  • The runtime does not have access to node_modules
  • require() of external packages fails at runtime
  • Multi-file import ./utils is not supported

1. Initialize a project:

Terminal
mkdir my-function && cd my-function
npm init -y
npm install --save-dev esbuild

2. Install dependencies you need:

Terminal
npm install xlsx csv-stringify

3. Write your function:

src/index.js
const XLSX = require("xlsx");
module.exports = {
handler: async (input, ctx) => {
const base64 = await ctx.fs9.readBase64(input.path);
const workbook = XLSX.read(Buffer.from(base64, "base64"), { type: "buffer" });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const csv = XLSX.utils.sheet_to_csv(sheet);
const outPath = input.path.replace(/\.xlsx?$/, ".csv");
await ctx.fs9.write(outPath, csv);
return { output: outPath, rows: csv.split("\n").length };
}
};

4. Add a build script to package.json:

package.json (scripts section)
{
"scripts": {
"build": "esbuild src/index.js --bundle --platform=node --target=es2020 --outfile=dist/index.js"
}
}

5. Build and deploy:

Terminal
npm run build
cat dist/index.js | db9 functions create xlsx-to-csv --db myapp -f - \
--fs9-scope /data:ro --fs9-scope /output:rw

Keep bundles under 5 MB for reliable deployments. Use esbuild’s tree-shaking and --external flag to minimize size.