> ## Documentation Index
> Fetch the complete documentation index at: https://cactal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Server functions

> Run small pieces of website code on the server with the website's secrets: call them from pages over HTTP, or run them after every form submission. They never ship to the browser.

Everything else in a website's source ships to the browser. A server function is a source file that never does: it runs in an isolated sandbox on Cactal's servers with the website's [secrets](https://cactal.ai/docs/guides/manage-secrets.md) available as `env`. Use one to send form leads to a CRM, call an API with a private key, or check something the browser can't be trusted with.

## Writing a function

A function is a `server_function` source file whose name, such as `getSlots`, starts with a letter and uses letters, numbers, `-`, and `_`:

```ts theme={null}
import { defineServerFunction } from '@server'

export default defineServerFunction({
  on: 'http',
  handler: async (payload: { date: string }, { env }) => {
    const url = `https://api.example.com/slots?date=${encodeURIComponent(payload.date)}`
    const response = await fetch(url, {
      headers: { authorization: `Bearer ${env.BOOKING_API_KEY}` }
    })
    const { slots } = await response.json()
    return { slots: slots as string[] }
  }
})
```

* `on` is the trigger: `http` or `form`. Each function has one.
* `env` maps each secret's name to its value, or `undefined` when it isn't set.
* Functions run on the WinterTC Minimum Common API (ECMA-429), the web platform Cloudflare Workers, Deno, Bun, and Node share: `fetch` to public addresses, `Request`, `Response`, `Headers`, `FormData`, `Blob`, `File`, streams, compression streams, `URL`, `URLPattern`, Web Crypto (PBKDF2 runs at most 100,000 iterations and derives at most 512 bits, and generated RSA keys are at most 2,048 bits), `TextEncoder`, `TextDecoder`, `Intl`, `AbortController`, `MessageChannel`, timers, and `console`. The Web Platform Tests for these APIs run against the sandbox on every change.
* There is no DOM, file system, Node.js built-ins, `WebAssembly`, or `WebSocket`, and the build rejects code that uses an API the sandbox doesn't provide.
* A function can import `@server`, shared [modules](https://cactal.ai/docs/concepts/source-code.md) that use neither React nor `@website`, and pinned npm packages, which load their worker builds. SDKs built on `fetch` work, such as OpenAI, Anthropic, Stripe, Resend, Notion, HubSpot, Slack, and SendGrid. Packages that need Node.js networking, `WebSocket`, or `WebAssembly` don't: use `jose` for JWTs, and call the service's REST API with `fetch` instead. Pages can't import function source.
* Return values must be JSON-safe.

## The http trigger

Page code imports an `http` function from `@server/{name}` and calls it. The types come from the handler, and the call never throws for expected failures:

```tsx theme={null}
import getSlots from '@server/getSlots'

const result = await getSlots({ date })
if (result.ok) {
  // result.data is { slots: string[] }
} else {
  // result.error.code and result.error.message
}
```

The call is a same-origin `POST /_framework/server/{name}` with a JSON body. Draft hosts run the draft's functions and the live site runs the published ones, with the same secrets. Error codes:

| Code | Status | Meaning |
| - | - | - |
| `bad_request` | 400 | The body isn't JSON or is over 64 KB. |
| `not_found` | 404 | No `http` function has that name. `form` functions have no URL. |
| `function_error` | 500 | The handler threw; its message is returned with secrets redacted. |
| `limit_exceeded` | 500 | The call ran out of CPU or memory, or made too many requests. |
| `timeout` | 504 | The call ran longer than its time limit. |
| `rate_limited` | 429 | Too many calls from one visitor or to one site; see `retryAfterSeconds`. |
| `quota_exceeded` | 429 | The organization used its plan's monthly calls. |
| `unavailable` | 503 | Functions couldn't run right now. |

Put expected outcomes, such as "this time slot is taken", in `data`, not in errors. The payload's type isn't checked at runtime and anyone can call the function, so validate the payload in the handler, for example with [zod](https://zod.dev)'s `safeParse`. Cactal never retries a call.

## The form trigger

A `form` function runs in the background after every [form](https://cactal.ai/docs/concepts/forms.md) submission accepted as not spam, including draft submissions and submissions restored with **Not spam**. The visitor's response never waits for it, and the submission is still stored and emailed. Use it, not an `http` function called from the form's page, to send submissions to a CRM or another service: an `http` call skips spam screening. It receives the data the notification email carries:

```ts theme={null}
export default defineServerFunction({
  on: 'form',
  handler: async ({ id, form, fields, page, submittedAt }, { env }) => {
    if (form !== 'home-value') return
    // send fields.email to the CRM, with id as the idempotency key
  }
})
```

A function handles each submission at most once. It gets 30 seconds and is never retried, so retry inside the function when an upstream is flaky.

## Limits

Each call gets about 200 ms of CPU (time spent waiting on `fetch` is free), 10 seconds for `http` and 30 seconds for `form`, 64 MB of memory, 50 outgoing requests (4 at a time, each request body and response up to 5 MB), 64 KB of input, and 1 MB of output. Code outside the handler gets 500 ms when the function loads. Free organizations get 1,000,000 `http` calls a month, with warnings to owners at 80 percent and at the limit; paid plans have no monthly cap. See [Plans and limits](https://cactal.ai/docs/platform/plans-and-limits.md).

## Secrets and redaction

Each secret's value, plus its base64 (including inside a longer value such as a Basic auth header), base64url, hex, URL-encoded, form-encoded, and JSON-escaped forms, is replaced with `[redacted:NAME]` in function responses, console output, error messages, and run records. Values under 8 characters are left as written, since hiding them would also hide unrelated text. Outgoing requests are sent exactly as written. Code that deliberately disguises a secret, or sends it to a server it controls, can still leak it, as on any platform with environment variables.

## Runs

Every call is recorded for 30 days with its input, output, console output, outgoing requests, and timings.

* `GET /v1/websites/{websiteId}/functions/runs` lists runs newest first; filter with `name`.
* `GET /v1/websites/{websiteId}/functions/runs/{runId}` returns one run.
* `POST /v1/websites/{websiteId}/functions/{name}/invoke` runs an `http` function on the draft and returns its result. It makes the function's real requests.

Runs and invoking need the `websiteFunctions.view` and `websiteFunctions.invoke` capabilities (full editors and above).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.