Handlers
Server-side functions with a scoped SDK, governed by the same policies as everything else.
A handler is a JavaScript function that runs on the server. Use one when logic belongs near the data: a multi-step workflow, a calculation, an atomic operation across tables, a call to an external service.
The shape
export default async function handler(sdk, ctx) {
const open = await sdk.readList("tickets", {
filters: { status: "open" },
limit: 50,
});
return { count: open.length };
}Two arguments, and both matter:
sdk— your database and services, already scoped to this app and this caller. There is no app id to pass because there is no way to address another app.ctx— who is calling and in what circumstances.
Whatever you return becomes the response body.
The SDK is pre-scoped
sdk.readList("tickets", …) reads this app's tickets, as this principal, with their
policies and column redaction applied. You cannot widen that from inside a handler, and
there is no unscoped variant to reach for by mistake.
This is why handlers are safe to expose as agent tools: a handler written for a person behaves identically when an agent calls it, because the scoping travels with the caller rather than being decided by the code.
Capabilities
Ordinary reads and writes need nothing. Reaching further — raw SQL, schema changes, sending email, cross-organization reads — requires a capability granted to that specific handler.
Capabilities are checked when you save, frozen into the version's manifest when you publish, and enforced again at runtime. A handler cannot acquire one by editing itself. See Capabilities & governance.
Testing before you ship
A handler can be run in dry-run mode: it executes for real against a transaction that is then rolled back, and reports what it would have done. You see the actual writes without performing them.