snoozestackdocs

Functions

Functions are your project's server-side code: TypeScript (or JavaScript) handlers on one contract — export default (req, capabilities) — declared in project.toml and run by the runtime. Each lives in its own directory under snoozestack/functions/, with shared code in _shared/. req is a web-standard Request; the return value is a web-standard Response; capabilities is everything the function declared it may reach.

Why use it

Every app needs backend code here, in a way that isn't true elsewhere: a client never talks to your tables directly — there is no such route — so a function is what reads or writes them, full stop. Beyond that baseline, the classic tests still apply: does this need a secret? (anything shipped to a browser or app binary is public) and does this enforce a rule the caller can't be allowed to skip? (a rule the client enforces isn't enforced at all).

The capability declaration is what makes a function reviewable at a glance. The diff that adds send-digest shows it can read the database and send push — and therefore that it cannot touch storage or read the payment key, because undeclared capabilities don't exist inside the handler. See Permissions.

And because the runtime runs whole on your machine, a function isn't “cloud code” you test by deploying: it's code you run, with its queues, schedules, and failure paths, before anything is published.

Writing one#

snoozestack/project.toml
[[functions]]
name = "chat"
entry = "chat/index.ts"
auth = "required"
capabilities = ["db", "auth", "secrets"]
[[secrets]]
name = "OPENAI_API_KEY"
functions = ["chat"] # only this function may read it
snoozestack/functions/chat/index.ts
export default async function handler(req: Request, capabilities) {
const user = capabilities.auth.user; // auth = "required" guarantees one
const { messages } = await req.json();
const key = capabilities.secrets.OPENAI_API_KEY; // declared above, injected here
// … call your model, stream a response, etc.
capabilities.db.query(
"insert into chats (user_id, at) values (?, ?)",
[user.id, new Date().toISOString()],
);
return Response.json({ ok: true });
}
terminal
snoozestack dev # POST http://localhost:8787/chat — live-reloads on edit
snoozestack publish # POST https://<ref>.snoozestack.com/api/functions/v1/chat

Editing the file takes effect on the very next local request. Streaming and SSE responses work; console output and errors land in logs, and anything you capabilities.signals.emit() lands in Signals.

Calling a third-party API that needs a secret
An LLM, a payment provider, an email service. The key is a declared [[secrets]] entry readable only by the functions you name — the client calls your function, your function calls the vendor.
Business rules that must be trusted
Applying a discount code, awarding credits, moving money. The client asks; the function decides, validates, and writes through capabilities.db.
Reacting to events
A queue message, a cron schedule, or an inbound webhook triggers the same handler — nightly cleanups, digest emails, syncing an external system.
Giving AI clients tools
An MCP endpoint (below) is a function that exposes your product's tools to Claude and other MCP clients, with your auth rules in front of it.
SurfaceAvailability
Portal UIRead-only code view per function with its auth mode and capabilities; Secrets and MCP sub-pages
CLIsnoozestack check validates; snoozestack dev runs; snoozestack publish ships; secrets set/list/unset manages values
SDK / HTTPsnoozestack.functions.invoke() / POST /api/functions/v1/<name>

The capabilities#

  • dbquery(sql, params) against the project database; see Database.
  • authuser ({ id, email } or null) and anonymous, from the verified session; see Authentication.
  • storageput/get/list/delete/signedUrl/signedUploadUrl on declared buckets; see Storage.
  • signalsemit(type, name, props); see Signals.
  • pushsend(message) through a declared notification service; see Push Notifications.
  • secrets — the declared values this function is allowed to read, as plain properties.

Secrets and schedules#

Two things a function commonly needs are declared beside it rather than inside it. Secrets name the credentials it may read — the value is set with the CLI and never committed — and schedules put it on a cron timer, with the timing versioned alongside the code it triggers.

MCP endpoints#

A function with mcp = true on its [[functions]] entry speaks the Model Context Protocol over Streamable HTTP, so AI clients — Claude Code, Claude Desktop, anything MCP-capable — can call tools backed by your project. It is an ordinary function: same contract, same capabilities, same local run under snoozestack dev; the flag serves it at /api/mcp/v1/<name> and lists it on the console's Functions → MCP page.

This is how you expose your product's tools — the ones only your app knows how to provide. To let an AI client manage the project instead — edit schema, publish, set secrets — you don't write anything: every project already has a management MCP endpoint with a token you create on the project Overview.

HTTP API#

functions — /api/functions/v1
curl -X POST "https://<ref>.snoozestack.com/api/functions/v1/chat" \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" -d '{"messages": []}'

See snoozestack-js for snoozestack.functions.invoke(), which attaches the signed-in session for you.