# Build site backend workflows with Func | Creght API for AI

> A complete Creght Func guide to backend functions, JSON tables, auth, secrets, third-party APIs, payments, asset uploads, timeouts, native Fetch SSE streaming, and SSR boundaries.

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[View llms.txt](/llms.txt)

Overview

- [Creght API for AI](/api)

Discoverability

- [How to optimize llms.txt](/api/optimize-llms-txt)

Site configuration

- [Implement domain-based locale routing](/api/domain-locale-routing)

Backend

- [Build site backend workflows with Func](/api/func-backend)

On this page

- [When to use Func](#when-to-use)
- [Good Func workloads](#good-func-workloads)
- [Prefer built-ins](#prefer-built-ins)
- [Not a Func workload](#not-a-func-workload)
- [Project isolation](#project-isolation)
- [Files, keys, and methods](#files-and-methods)
- [Runtime and code rules](#runtime-rules)
- [ctx capability reference](#context)
- [Data](#data)
- [Authentication](#authentication)
- [Cache](#cache)
- [Request and response](#request-and-response)
- [Cookies](#cookies)
- [Assets](#assets)
- [Streaming](#streaming)
- [Diagnostics](#diagnostics)
- [Results and errors](#responses)
- [JSON tables and persistence](#tables)
- [Auth, secrets, and external services](#auth-secrets-external)
- [Asset uploads](#assets-2)
- [Browser files: signed CDN upload](#browser-signed-upload)
- [Files generated inside Func](#func-generated-upload)
- [Timeout configuration and diagnosis](#timeout)
- [Native Fetch + SSE streaming](#streaming-2)
- [Browser calls and the SSR boundary](#browser-ssr)
- [Development and acceptance workflow](#workflow)

Backend/Build site backend workflows with Func

# Build site backend workflows with Func

A complete agent-oriented Func specification covering files and methods, runtime APIs, data, auth, secrets, third parties, payments, assets, timeouts, native SSE, and SSR boundaries.

Copy Markdown link

Func is the project-scoped backend runtime for Creght / 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.

**Agent objective**

Keep server code in `/backend/func`, use platform capabilities through `ctx`, and call stable Func keys from pages. Never leak project IDs, secrets, identity logic, or persistent writes into browser code.

## 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.

```typescript
// /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', { date: input.date })
}
```

```typescript
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`.
- 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.

## ctx capability reference

### Data

`ctx.db.get/query/insert/update/delete` operates on project JSON tables. Every write is validated against the table JSON Schema.

### Authentication

`ctx.auth.currentUser()` reads the current user; `ctx.auth.requireUser()` rejects unauthenticated requests.

### 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 runtime files and returns `{ fileUrl, url, size }`. Both URL fields contain the same value; internal storage paths are not exposed.

### Streaming

`ctx.sse.send(event, data)` sends bounded SSE events. The platform supplies the final `done` or `error` event.

### Diagnostics

Use `console.log/warn/error` and correlate one call with `ctx.trace_id`. Never log secrets or sensitive bodies.

## 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.

```typescript
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.'
}
```

## JSON tables and persistence

A table definition must exist before writes. Definitions live at `/platform/table/<key>.json`, where the filename is the table key. Create or edit them with normal file tools; there is no separate table-schema tool.

```json
{
  "name": "Appointments",
  "desc": "Booked appointment slots",
  "json_schema": {
    "type": "object",
    "properties": {
      "startAt": { "type": "string" },
      "userId": { "type": "string" },
      "status": { "type": "string" }
    },
    "required": ["startAt", "userId"]
  }
}
```

The schema must be an object with non-empty `properties`; `required` can only name declared fields, and a business field cannot be named `key`. Writes accept declared fields only. Renaming is refused, and deletion is refused while records exist. Manage records with tools such as `list_table_records` and `create_table_record`.

> Use the platform `user.id` as the ownership key. Do not use email as an identity primary key or create identity tables such as `users` or `auth_users`.

## 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 Creght 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.

```typescript
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. Payments are custom server-side Func integrations: keep provider secrets in env, verify callback signatures and idempotency keys, and return explicit order states. There is no built-in payment SDK.

## Asset uploads

### Browser files: signed CDN upload

For a `File` or `Blob` selected or dropped in the browser, use `talizen/assets`. Do not encode it as base64 and send it through `invoke()` just to upload it from Func.

```typescript
import { uploadAsset } from 'talizen/assets'

const asset = await uploadAsset(file, {
  onFileUploadProcess(fileName, progress) {
    console.log(fileName, progress)
  },
})

// asset.fileUrl === asset.url
```

`uploadAsset()` calls `POST /api/asset/file/preupload` to obtain a short-lived signed URL, uploads the file bytes directly from the browser with `PUT` to that CDN storage URL, then confirms the upload through `POST /api/asset/file/ack`. File bytes do not pass through Func, and matching content can reuse an existing object by hash.

Both `File` and `Blob` are accepted. For an unnamed `Blob`, call `uploadAsset(blob, { fileName: 'avatar.webp' })`. The result contains `{ fileUrl, url, fileName, mimeType, size, hash }`, with identical URL fields. Signed upload works on both preview and published site domains.

### Files generated inside Func

```typescript
const asset = ctx.assets.upload({
  filename: 'report.pdf',
  mimeType: 'application/pdf',
  base64: input.base64,
})
await ctx.db.insert('reports', {
  userId: ctx.auth.requireUser().id,
  url: asset.url,
  size: asset.size,
})
```

Use `ctx.assets.upload()` only for bytes generated inside Func that cannot originate in the browser. It synchronously returns `{ fileUrl, url, size }`, with identical URL fields. Persist the URL and size only; do not store internal paths or large base64 payloads in tables or Func JSON.

## Timeout configuration and diagnosis

`invoke` defaults to **5 seconds**; the Func runner's default maximum is **300 seconds**. A bounded model generation or upstream request may set a longer caller timeout. This does not enable detached background work.

```typescript
const result = await invoke('image.generate', input, {
  timeoutMs: 120000,
})
```

For `context deadline exceeded` or `context timeout`:

1. Inspect the actual `invoke(..., { timeoutMs })` value in the page.
2. Retry the same input and model with a larger timeout. `run_func.timeout_ms` controls only that self-test and does not prove the same platform hard limit.
3. If the larger value succeeds, raise the production caller's `timeoutMs` and verify the real path.

> Do not treat shorter output, lower `max_tokens`, a different model/provider, a new table, or a fake background job as the timeout fix.

## Native Fetch + SSE streaming

Ordinary `invoke()` waits for one complete JSON result. For bounded incremental output, send events with `ctx.sse.send` and use native browser `fetch` plus `ReadableStream`. No `invokeStream` wrapper is required.

```typescript
// /backend/func/writer.ts
export async function main(input, ctx) {
  ctx.sse.send('token', { text: 'Hello' })
  ctx.sse.send('token', { text: ' world' })
  return { ok: true }
}
```

```typescript
const response = await fetch('/func/writer?stream=1&timeout_ms=120000', {
  method: 'POST',
  headers: {
    Accept: 'text/event-stream',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(input),
})
if (!response.ok || !response.body) throw new Error('Func stream failed')

const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })

  const frames = buffer.split(/\r?\n\r?\n/)
  buffer = frames.pop() || ''
  for (const frame of frames) {
    const event = frame.match(/^event:\s*(.+)$/m)?.[1]
    const data = frame.match(/^data:\s*(.+)$/m)?.[1]
    if (event === 'token' && data) {
      output += JSON.parse(data).text
    }
  }
}
```

`reader.read()` yields arbitrary byte chunks, not complete events. Buffer across reads and split SSE frames only on a blank line. The platform finishes with `done` or `error`; timeout still applies. Cookies cannot change after the first event.

## 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 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 the Creght CLI Func command. 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 schemas match the actual write shape.
- 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.

**Completion criteria**

A correct Func has a stable key, minimum necessary access, validated input, predictable results, schema-constrained persistence, and error/timeout behavior reproducible through the real page call path.

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

This website is built with [Creght](/)

[Discord](https://discord.gg/Qvq2nmZNnb)

## Links

- [Pricing](/price)
- [Solutions](/solution)
- [Customers](/customers)
- [Help Center](/help)
- [Contact Us](/contact)
- [Blog](/blogs)
- [Refund Policy](/tuikuan)

## Resources

- [All Resources](/resources)
- [Templates](/templates)
- [Components](https://creghtlib.site.creght.com)
- [Animations](/effects)
- [Figma to Creght](/figma2creght)
- [API for AI](/api)

## Terms

- [Terms of Service](/legal/terms)
- [Privacy Policy](/legal/privacy)
- [Acceptable Use Policy](/legal/acceptable-use)

## Social Media

- [Twitter](https://twitter.com)
- [Facebook](https://www.facebook.com)
- [LinkedIn](https://www.linkedin.com)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
