Skip to content
Discord Get Started

Configuration

Secrets let you pass sensitive values (API keys, tokens, connection strings) to functions without embedding them in code.

Terminal
# From stdin (recommended for CI/CD)
echo -n "sk-abc123" | db9 functions secrets set MY_API_KEY --db myapp --value-stdin
# Inline (visible in shell history — not recommended)
db9 functions secrets set MY_API_KEY --db myapp --value "sk-abc123"

The --secret flag maps an alias (used in your code) to a secret name (stored in the database):

Terminal
db9 functions create my-func --db myapp -f index.js \
--secret API_KEY=MY_API_KEY \
--secret DB_TOKEN=external_db_token
JavaScript
module.exports = {
handler: async (input, ctx) => {
const apiKey = ctx.secrets.get("API_KEY");
const response = await fetch("https://api.example.com/data", {
headers: { "Authorization": `Bearer ${apiKey}` },
});
return await response.json();
}
};
Terminal
# List secrets (names only — values are never shown)
db9 functions secrets list --db myapp
# Update (set uses create-or-update semantics)
db9 functions secrets set MY_API_KEY --db myapp --value "new-value"
# Delete
db9 functions secrets delete MY_API_KEY --db myapp

Functions can read and write files in the database’s filesystem. Access is controlled by --fs9-scope flags at deploy time:

Terminal
db9 functions create my-func --db myapp -f index.js \
--fs9-scope /data:ro \
--fs9-scope /output:rw
ModeDescription
roRead-only access to the path and its children
rwRead-write access to the path and its children

Scopes are enforced, and they are the only way in: a function created without any --fs9-scope cannot use ctx.fs9 at all. Every call — even reading a file that exists — fails with FS9 resource was not found, the same message as a genuinely missing path. With scopes declared:

CallError
Path outside every scopePath '/reports/a.txt' is outside all declared fs9 scopes
Write under an ro scopeOperation 'write' is not allowed on scope '/data' (mode: ro)

In the CLI only db9 functions create sets scopes; db9 functions update has no --fs9-scope flag and keeps the scopes the function already has. To change them later, send fs9_scope with PUT /customer/databases/{db_id}/functions/{fn_id}, which also needs the source as bundle_ref (see REST API — Functions).

JavaScript
// Read as UTF-8 string
const text = await ctx.fs9.read("/data/config.json");
const config = JSON.parse(text);
// Read as base64 (for binary files like images, xlsx)
const base64 = await ctx.fs9.readBase64("/data/image.png");
const buffer = Buffer.from(base64, "base64");
JavaScript
await ctx.fs9.write("/output/report.csv", csvContent);
await ctx.fs9.write("/output/data.json", JSON.stringify(results, null, 2));

write() replaces an existing file and does not create missing parent directories: writing into a directory that does not exist fails with FS9 resource was not found, and ctx.fs9 has no mkdir. Create the directory once before the first run:

Terminal
db9 db sql myapp -q "SELECT fs9_mkdir('/output', true)"

db9 fs cp creates parent directories as it uploads, so uploading an input file to /data/... also creates /data.

JavaScript
const entries = await ctx.fs9.list("/data/");
// Returns: [{ path, type, size, mtime, ... }, ...]
const stat = await ctx.fs9.stat("/data/file.txt");
// Returns: { path, type: "file", size: 1024, mtime: "2026-04-01T...",
// generation: ..., mode: 420, sealed: false }
JavaScript
await ctx.fs9.delete("/output/old-report.csv");

Functions have access to the global fetch() API for outbound HTTP requests. Network access is blocked by default — only URLs on the allowlist can be reached.

JavaScript
module.exports = {
handler: async (input, ctx) => {
const response = await fetch("https://api.example.com/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: input.query }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
};

If a URL is not in the allowlist, fetch() throws with:

Output
EACCES: permission denied, connect 'https://...': url not in allowlist

A function can run on a schedule, which is useful for periodic data cleanup, report generation, and automated maintenance. Set it with the cron_schedule field of the REST API; the CLI has no flag for it.

A scheduled function must use "run_as": "service" — with invoker, or without run_as, the request fails with 400 cron_requires_service. Set both on create, or on an update of an existing function (an update must resend the source as bundle_ref):

Terminal
curl -X PUT \
-H "Authorization: Bearer $DB9_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bundle_ref": "inline:module.exports = { handler: async (input, ctx) => ({ ok: 1 }) }",
"run_as": "service", "cron_schedule": "0 3 * * *"}' \
https://api.db9.ai/customer/databases/{db_id}/functions/{function_id}

The scheduler checks every minute and skips a run while the previous one is still going. Each scheduled run is recorded with trigger_type cron and run_as service.

A service function runs SQL as the service role, not authenticated, so it needs its own grants — without them its queries fail with permission denied for table ... (42501):

SQL
GRANT ALL ON ALL TABLES IN SCHEMA public TO service;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO service;

cron.schedule() can also invoke a function’s REST endpoint on a recurring schedule, once job scheduling ships:

SQL
SELECT cron.schedule(
'nightly-cleanup',
'0 3 * * *',
$$SELECT http_post(
'https://api.db9.ai/customer/databases/{db_id}/functions/{function_id}/invoke',
'{"input": {}}',
'application/json',
jsonb_build_object('Authorization', 'Bearer ' || current_setting('app.api_token'))
)$$
);

cron_schedule takes five fields — minute, hour, day of month, month, day of week (0 is Sunday) — evaluated in UTC. Each field accepts *, a number, a range (1-5), a step (*/5, 1-30/5) or a comma-separated list of these. Names such as MON are rejected with 400 invalid_cron_schedule.

ExpressionMeaning
*/5 * * * *Every 5 minutes
0 * * * *Every hour
0 3 * * *Daily at 3:00 AM UTC
0 0 * * 0Weekly on Sunday at midnight
0 12 1 * *Monthly on the 1st at noon

See Scheduled Jobs with pg_cron for the complete scheduling guide.

Every db9 functions update creates a new version. The active version is updated immediately — there is no staged deployment.

Terminal
# Deploy version 1
db9 functions create my-func --db myapp -f v1.js
# Deploy version 2 (replaces version 1 immediately)
db9 functions update my-func --db myapp -f v2.js

Each run record stores the version_id that was used, so you can trace which version produced a given result:

Terminal
db9 functions history my-func --db myapp -n 5 --json

There is currently no built-in rollback command. To revert, re-deploy the previous version’s code:

Terminal
db9 functions update my-func --db myapp -f previous-version.js

Each version also records its result format. An update without --db-api keeps the active version’s format, so when you re-deploy code written for the other format, pass --db-api as well.

LimitDefaultMaxDescription
timeout_ms30,000 (30s)300,000 (5 min)Maximum execution time
memory_mbSystem default—Memory limit
max_db_queriesSystem default—SQL queries per invocation
max_db_rowsSystem default—Total rows returned across queries
max_outbound_requestsSystem default—Outbound HTTP requests
max_response_bytesSystem default—Max size per HTTP response
network_timeout_msSystem default—Per-request network timeout
max_fs9_requestsSystem default—fs9 API calls per invocation
max_fs9_read_bytesSystem default—Total bytes read from fs9
max_fs9_write_bytesSystem default—Total bytes written to fs9

Set limits via flags or JSON:

Terminal
# Convenience flag (timeout only)
db9 functions create my-func --db myapp -f index.js --timeout 60000
# Full limits JSON
db9 functions create my-func --db myapp -f index.js \
--limits-json '{"timeout_ms":60000,"memory_mb":256,"max_db_queries":100}'
# From file
db9 functions create my-func --db myapp -f index.js \
--limits-file ./limits.json

Functions have a visibility field that defaults to private. This controls whether the function can be invoked via a publishable key (public endpoint) without authentication.

Set it with --visibility private|public on db9 functions create or db9 functions update.

--db-api (CLI 2.2.5) on db9 functions create or db9 functions update sets what ctx.db.query() returns, and the API takes the same choice as the db_api field:

Valuectx.db.query() returns
sql-api (default){ columns, rows, row_count, command }, with rows as arrays
pgA node-postgres result (rows as objects, rowCount, fields), and node-postgres errors with err.code
Terminal
db9 functions create my-func --db myapp -f index.js --db-api pg

The CLI rejects any other value as a usage error, and the API with 400 invalid_db_api. The db9 functions list output does not show the format; the version.db_api field of the create/update --json output does. See Runtime & ctx for the value types in each format.