Functions and routes
Functions
Put functions in flatstack/functions/*.ts. Each export must be an fn():
import { fn, z } from "@flatstack/server";
export const createTodo = fn({
input: z.object({ title: z.string().min(1).max(200) }),
auth: "required",
handler: async ({ input, ctx }) => {
// ...
},
});
Call it with POST /fn/createTodo and a JSON body. If the input doesn’t match the schema, the call returns 400 validation_failed with each field’s problem in details.fieldErrors, and your handler doesn’t run. If the handler returns nothing, the response is 204.
auth |
Who can call it | ctx.user |
|---|---|---|
required |
Signed-in users | Always set |
admin |
Signed-in users with the admin role |
Always set |
optional |
Anyone | Set when signed in |
none |
Anyone | Not looked up |
A caller who isn’t allowed gets 401 unauthenticated or 403 forbidden before the input is checked.
If your handler throws, the caller gets 500 internal_error and the details go to your project’s logs, not to the caller.
Routes
Put HTTP routes, such as webhooks, in flatstack/routes/*.ts. They’re served under /api:
import { router } from "@flatstack/server";
export const webhooks = router().post("/webhooks/shop", async (c) => {
const ctx = c.get("ctx");
return c.json({ ok: true });
});
/api/data/* and /api/stripe/* are reserved for Flatstack’s data API and payments helper. A project with routes there won’t deploy.
ctx
| Field | What it is |
|---|---|
ctx.db |
Your database (Drizzle). ctx.db.asUser() applies your tables’ access rules for the caller (see Data and access rules); ctx.db.asAdmin() ignores them. |
ctx.user |
The signed-in user, by auth mode. |
ctx.env |
Your environment variables. |
ctx.secrets.get(name) |
A secret’s value. Never logged. |
ctx.log |
info, warn and error, written as JSON lines to your project’s logs. Fields named like secrets are redacted. |
ctx.email.send(message) |
Sends a transactional email. See Sending email. |
ctx.waitUntil(promise) |
Keeps work running after the response is sent. |
ctx.request |
The incoming request. |
ctx.cron |
While a cron job runs the function: { schedule, scheduledFor, attempt }. Otherwise null. |
ctx.storage is your project’s file storage, and ctx.email sends email.
These aren’t available yet and answer 501 feature_unavailable if used: ctx.queue, ctx.realtime, ctx.vector, ctx.ai and ctx.org. Sign-in for your app’s users (/auth/*) isn’t available yet either.
Each request can use up to 50 ms of CPU on Hobby, 200 ms on Pro and 500 ms on Studio.
As Markdown: apps/functions.md