API
BackendBuild site backend workflows with Func

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 from talizen/func-runtime when needed.
  • Access every platform capability through ctx. Do not use legacy globals such as data, db, auth, or cache. 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/setInterval are 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: both AES-GCM and AES-CBC are supported, and AES-CBC applies compatible PKCS#7 padding automatically — see the Alipay integration. Never import node: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 →

Email

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 TalizenFuncError instance, but inside Func platform capabilities such as ctx.db throw strings, not Error objects. In a server-side catch (e), e.message is undefined — use String(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

  1. Confirm CMS or talizen/form is insufficient.
  2. Create or verify the required JSON tables under /platform/table.
  3. Write ESM exports in /backend/func, validate input, and read secrets from process.env.
  4. Use invoke('key.method', input) from pages; use native Fetch/SSE only for streaming.
  5. Run sample backend tests with run_func or talizen func run. Remember that the test timeout applies only to that run.
  6. 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 use user.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.

Render diagnostics