Skip to content

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