Functions
Run server-side logic next to your data: sync calls, background jobs, cron schedules and webhooks, with the rules, secrets and limits that keep them safe.
Server-side functions run code you write on the server, next to your collections, for the things a client must not do or be trusted with: computing what readers may not compute themselves, making privileged changes, calling payment or email services with a secret, working on a schedule, answering another service’s callbacks. Each function is a small JavaScript or TypeScript module in your CONFIG_DIR, next to the collections it works on. backd runs it in Deno, in its own process, with only the permissions and limits it declares.
They are a core part of backd, not an add-on: a function reaches data through the same access rules as any other caller (or, when you say so, with full access), and everything around it is built in: input and output schemas, per-caller rate limits, idempotent retries, encrypted secrets, a network allowlist, background jobs, cron schedules, invocation history and console logs.
What a function looks like
A function is a folder with a function.yaml (how it runs) and an index.ts (what it does). This one prices one of the caller’s orders. It runs as the caller, so the orders’ access rules decide which orders they can read:
examples/config/workshop/main/_functions/order_total/function.yaml
# A sync function that runs as the caller: ctx.db reads with the caller's
# own access rules, so a customer can only price their own orders.
invoke: "user != nil"examples/config/workshop/main/_functions/order_total/index.ts
// order_total: the amount a customer will be charged for one of their
// orders. The tax rule lives here, on the server, not in every client.
//
// ctx.db acts as the caller, so orders/collection.yaml decides what they may
// read: someone else's order (or one that doesn't exist) answers 404, the
// same as reading the collection directly would.
import { relay } from "../lib/relay.ts";
import type { Context, Doc } from "../lib/types.ts";
const TAX_RATE = 0.2;
export default async function handler(ctx: Context) {
const { order_id } = ctx.input as { order_id: string };
let order: Doc;
try {
order = await ctx.db("main").collection("orders").get(order_id);
} catch (err) {
relay(err, ctx.error);
}
if (order.status === "refunded") {
throw ctx.error(409, "order_refunded", "This order was refunded");
}
const subtotal = order.amount as number;
const tax = Math.round(subtotal * TAX_RATE);
return { order_id, subtotal, tax, total: subtotal + tax };
}curl -X POST https://api.example.com/v1/workshop/main/_func/order_total \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"order_id": "d3c9ljp8hc2g00b6s1m0"}'
# → 200 {"order_id": "d3c9ljp8hc2g00b6s1m0", "subtotal": 1000, "tax": 200, "total": 1200}
This function, and every other one quoted in this section, is in examples/config/workshop in the repository, where it is tested for real; the Cookbook walks through them all.
Which kind of function do I need?
| You need to… | Use | Page |
|---|---|---|
| Decide who may read or write which documents | Access rules — no function needed | |
| Compute something readers can’t, or check across several documents | A sync function | Writing, Calling |
| Make a change no user rule may allow (a refund, a role change) | A sync function with admin: true | Writing |
| Do work that takes more than a few seconds, or must survive the caller going away | An async job | Async jobs |
| Keep a function away from HTTP, or share a helper between functions | An internal function, called with ctx.call | Internal functions |
| Run something every night, hour or week | A scheduled function (schedule: on an async function) | Scheduled functions |
| Receive a callback from Stripe, GitHub or any other service | A webhook function | Webhooks |
| Call an outside API with a key | Any function, with secrets and a network allowlist |
The three modes, and cron
sync (default) | async | webhook | scheduled | |
|---|---|---|---|---|
| Who calls it | a client, over HTTP | a client, over HTTP | another service | a worker, on a cron schedule |
| The caller gets | the output, right away | 202 and a job to check later | the function’s own response | nothing (there is no caller) |
| Runs in | the request | a background worker | the request | a background worker |
| Time limit | up to 60 s (10 s default) | up to 24 h (15 min default) | up to 60 s | up to 24 h |
| Acts as | the caller (ctx.db), or the function (ctx.admin.db) | the caller who queued it | nobody: it verifies the sender itself | the function, with full access |
| Delivery | once | at least once | as often as the sender retries | once per scheduled time |
How it fits together
client ── HTTP ──▶ backd ── runs the function in ──▶ executor ─▶ one Deno process per call
│ ▲ │
│ └────── ctx.db / ctx.admin.db, over an ◀───────┘
│ internal listener (short-lived credentials)
│
worker (async jobs and cron schedules) ── also uses the executor
│ network: only what the function
▼ declares, through backd egress
backdauthenticates the caller, applies the function’sinvokerule, validates input and output, and answers.- The executor starts one Deno process per call and stops it at its deadline. It never sees
CONFIG_DIR, MongoDB or the signing keys. - A worker (
backd worker, orbackd serve --with-worker) claims async jobs and creates the runs of scheduled functions. Async jobs and cron only happen while at least one worker runs. backd egressis the only way a function reaches the outside network, and only the hosts it declares.
What backd enforces for you
- The caller’s rules apply to
ctx.db; full access needs an explicitadmin: true(or is the point of a scheduled run). - Time, memory, output size and concurrency limits per function, and a per-caller rate limit you can declare.
- Input and output JSON Schemas, checked on every call.
- Idempotency keys, so a retried request doesn’t do the work twice.
- Secrets encrypted at rest and delivered as
ctx.secrets, masked in logs. - No environment variables, no files but the function’s bundle, no processes, and a network limited to a declared allowlist.
- A record of every call (never its input or output) and the function’s own console output, kept for a week by default.
Where to go next
- New here? Start with the Quickstart, then Writing a function.
- Calling functions: Calling functions covers HTTP, the JavaScript client and the CLI, and the answers you can get.
- Background work: Async jobs (including how to know a job finished) and Scheduled functions (when to use cron).
- Recipes: the Cookbook has six complete, tested examples.
- Reference:
function.yaml, Secrets, Outbound network, Build, dev and test, Function logs. - Operating it: Running in production, and the production reference deployment.
- Worked examples with commentary: the blog example’s
statsfunction is the smallest possible one; Expenses with functions is a complete small app built around five functions, compared side by side with a version that doesn’t use them.
Your first function, from an empty directory to a call, its logs and a unit test, in about ten minutes.
Writing a functionA function’s layout, its handler, the ctx object, reading and writing data, errors and schemas.
Calling functionsCall a function over HTTP, from the JavaScript client or the CLI; the answers, limits, rate limits and idempotency.
function.yaml referenceEvery function.yaml key, the invoke rules, and what backd enforces.
Async jobsRun work in the background with mode: async, and know when it finished: polling, the JavaScript client, the CLI, and completion patterns inside the function.
Scheduled functionsRun a function on a cron schedule, with no caller: when to use it, the expression syntax, what a run can do, and how to check that it ran and finished.
Internal functionsFunctions with no HTTP route, for work that only backd, scheduled runs and other functions may start.
EmailSend transactional email through any provider: you write one function, backd renders the messages.
WebhooksReceive callbacks from other services and answer them yourself.
SecretsEncrypted values a function may read, managed by realm administrators.
Outbound networkWhat a function may reach: the per-function allowlist, the egress proxy and network placement.
Build, dev and testBundle functions, iterate in dev mode, call them through a running backd and unit-test their logic.
Running in productionThe executor, workers and egress: how to run them.
Function logsSee what your deployed functions did: invocation history, console logs, retention, and shipping logs to a long-term system.
CookbookSeven complete, tested recipes: a sync function as the caller, a privileged idempotent refund, a background report, a nightly cleanup, a daily digest, a payment webhook and an email delivery function for Postmark.
Files in functionsRead, write, remove and link a document’s files from a function with files(id, field), as the caller or as the function, and two recipes: a thumbnail and a counted download.