Build site backend workflows with Func
The Func overview for AI coding agents: when to use it, files and keys, runtime rules, the whole ctx surface, results and errors, auth and secrets, the SSR boundary, and the acceptance workflow. Tables, uploads, timeouts, and streaming each have a dedicated page.
Func is the project-scoped backend runtime for Talizen / Talizen. Use it for small server-side workflows such as protected writes, bookings and waitlists, profile updates, third-party APIs, webhooks, payment integrations, and AI requests that need secrets or bounded streaming.
This page is the overview: when to use Func, files and methods, runtime rules, the whole ctx surface, results and errors, auth and secrets, the SSR boundary, and the acceptance workflow. Topics that need real depth — tables, uploads, timeouts and streaming — each have their own page, listed under Deep dives.
When to use Func
Good Func workloads
Bookings, waitlists, RSVP, lead routing, profile updates, availability checks, signed-in actions, third-party APIs, webhooks, payments, and simple JSON data reads/writes.
Prefer built-ins
Use CMS for ordinary content, talizen/form for static contact forms, and talizen/auth for login UI and session state.
Not a Func workload
Detached background jobs, heavy file processing, unbounded streams, timer polling, custom identity/session systems, OAuth callbacks, or token exchange.
Project isolation
Func is already scoped to the current project. Inputs, source code, and branches must not contain project_id, site_id, or internal table IDs.
Files, keys, and methods
Func files live in /backend/func. The extensionless file path is the Func key: /backend/func/booking.ts maps to booking, and /backend/func/profile/settings.ts maps to profile/settings.
Dots are reserved for exported methods. Export main for one operation, or export related operations directly from one file. Do not write a manual dispatcher.
// /backend/func/booking.ts
import type { TalizenFuncContext } from 'talizen/func-runtime'
export function create(input, ctx: TalizenFuncContext) {
if (!input?.startAt) throw new Error('startAt is required')
const user = ctx.auth.requireUser()
return ctx.db.insert('appointments', {
startAt: input.startAt,
userId: user.id,
})
}
export function availability(input, ctx: TalizenFuncContext) {
return ctx.db.query('appointments', { where: { day: input.day } })
}
invoke('booking.create', input) // key booking, method create
invoke('profile/settings.update', input)
invoke('booking', input) // key booking, method main
Runtime and code rules
- Use ESM exports:
export function method(input, ctx). Import only TypeScript types fromtalizen/func-runtimewhen needed. - Access every platform capability through
ctx. Do not use legacy globals such asdata,db,auth, orcache. The sandbox has no module loader, so any value import fails. - Validate, trim, and normalize all input inside the Func. Return structured JSON for expected business states; throw for invalid requests or unexpected failures.
- Func is not a full Node.js runtime. Do not depend on Node built-ins.
setTimeout/setIntervalare unsupported and must not be used for delays, polling, or retries. - Standard
fetch,Response,TextDecoder, and Web Crypto are available for upstream HTTP, response reading, and signature verification. - Symmetric encryption goes through Web Crypto on the global
crypto: bothAES-GCMandAES-CBCare supported, andAES-CBCapplies compatible PKCS#7 padding automatically — see the Alipay integration. Never importnode:crypto.
ctx capability reference
Data
ctx.db.get/query/insert/update/delete operates on project JSON tables, returning { total, list, limit } or a single record. Query syntax and write rules →
Authentication
ctx.auth.currentUser() reads the current user; ctx.auth.requireUser() rejects unauthenticated requests.
User directory
ctx.users.find/query resolves one user or pages through the list. Its scope is the whole project, not the caller. Querying users and the gate you must write →
Cache
ctx.cache.get/set/del/incr/expire supports short-lived results, counters, and expiring state. It is not persistent storage.
Request and response
ctx.request.host/ip/method/path exposes request metadata; raw bodies follow Fetch reading semantics. Use ctx.response.status(code) to set status.
Cookies
Read, set, or delete cookies through ctx.cookies. After the first SSE event, headers are committed and cookies cannot change.
Assets
ctx.assets.upload({ filename, mimeType, base64 }) uploads files generated inside Func. Choosing between the two upload paths →
ctx.email.send/sendCode/verifyCode requires a connected email integration; credentials never reach the sandbox. Send email through an integration →
Streaming
ctx.sse.send(event, data) sends bounded SSE events. The platform supplies the final done or error event. Timeouts and streaming →
Diagnostics
Use console.log/warn/error and correlate one call with ctx.trace_id. Never log secrets or sensitive bodies.
Deep dives
These topics carry enough implementation detail to warrant their own pages:
JSON tables: definition, reads, and queries →
The definition file format and its validation, record shape, where and filter operators and their traps, paging and ordering, merge-style updates, and the boundary people miss: tables belong to the project, not to a site version.
Uploads: signed direct upload and Func-generated files →
Browser files go straight to the CDN, Func-generated bytes go through ctx.assets.upload, and why a base64 relay is never the answer.
Timeouts and streaming responses →
Where timeoutMs belongs, how to diagnose context deadline exceeded in order, and the complete native Fetch + SSE parsing loop.
Integrate Alipay PC web payment →
A complete example: public-key mode, RSA2, optional AES content encryption, the order table, asynchronous notification verification, and idempotent updates.
Results and errors
The HTTP layer returns { "result": ... } or { "error": "..." }. Browser invoke() unwraps successful results and throws TalizenFuncError on failure. Return explicit objects for expected states such as no availability or duplicate submission.
import { invoke, TalizenFuncError } from 'talizen/func'
try {
const booking = await invoke('booking.create', input)
} catch (error) {
const message = error instanceof TalizenFuncError
? error.message
: 'Unable to submit. Try again later.'
}
The two directions are not symmetric: the browser receives a
TalizenFuncErrorinstance, but inside Func platform capabilities such asctx.dbthrow strings, notErrorobjects. In a server-sidecatch (e),e.messageisundefined— useString(e).
Return a raw HTTP Response
Plain return values keep the JSON envelope. For webhooks and payment callbacks that require an exact acknowledgement, return the global Response provided by the Func runtime. It bypasses { result: ... } and sends the selected status, headers, and body.
export async function notify(_input, ctx) {
await verifyWebhook(await ctx.request.text())
return new Response('success', {
status: 200,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}
The Response constructor is global and needs no import. For explicit annotations, talizen/func-runtime exports type-only Response and ResponseInit. Call a raw-Response method with native fetch(), not JSON-unwrapping invoke().
JSON, form, text, and binary bodies can reach Func. A non-JSON request receives {} as input; read the exact one-shot body through ctx.request.text() or arrayBuffer() and do not re-encode it before signature verification.
Auth, secrets, and external services
Use useAuth() for login UI; use Func auth only to protect backend actions. Read secrets with process.env.NAME. Users configure them in the Talizen Backend / Env panel at panel/backend/env. Agents must not claim to manage platform env vars or place secrets in config, Func source, components, examples, comments, or generated output.
export async function main(input, ctx) {
ctx.auth.requireUser()
const response = await fetch('https://api.example.com/v1/generate', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.EXAMPLE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
})
if (!response.ok) {
ctx.response.status(502)
throw new Error('Upstream request failed')
}
return response.json()
}
For webhooks, read the raw body as required by the provider and verify its signature with Web Crypto before parsing JSON.
Sign-in and user related server actions have their own pages: Sign In From a Func, Reset and Change Passwords, Require a Verified Email to Sign Up, and Query users from a Func.
Payment integrations
Func can integrate with providers such as Alipay, but the platform does not include a payment SDK. Server code must create orders, protect keys, verify signatures and merchant identity, and process asynchronous notifications idempotently. For a complete example see Integrate Alipay PC web payment with Func.
Browser calls and the SSR boundary
Call mutating Funcs from browser event handlers. Keep persistent state in Func/JSON tables rather than React state alone. The public HTTP path is /func/<key>; do not occupy /func/* with page routes.
Do not call Func from getServerSideProps. SSR exposes request/cookie helpers for public or cookie-vary-safe first-render data, but intentionally does not expose ctx.auth, ctx.func, ctx.db, or ctx.cache. Keep auth, private data, writes, and cache/database logic in Func/browser flows.
Development and acceptance workflow
- Confirm CMS or
talizen/formis insufficient. - Create or verify the required JSON tables under
/platform/table. - Write ESM exports in
/backend/func, validate input, and read secrets fromprocess.env. - Use
invoke('key.method', input)from pages; use native Fetch/SSE only for streaming. - Run sample backend tests with
run_funcortalizen func run. Remember that the test timeout applies only to that run. - Run lint after page/component edits and test success, business failure, unauthenticated, upstream failure, timeout, and stream completion paths in the real page.
- No project/site/internal table IDs enter browser payloads.
- No hard-coded secrets, legacy globals, manual dispatchers, or timers.
- Tables exist and input validation lives in the Func.
- Protected actions call
requireUser(); records useuser.id. - Large files use asset upload; third parties, webhooks, and payments validate errors and signatures.
- Ordinary calls return structured JSON; SSE buffers frames and handles
done/error. - Caller timeout matches workload duration and the production path is verified.