Skip to content

Edge Functions Overview

Edge Functions let you run server-side TypeScript/JavaScript on AnvilBase — for webhooks you receive, custom API endpoints, scheduled work, third-party integrations, and anything that needs a secret you can’t ship to the client. They run in the Deno runtime, with each project’s functions served by their own isolated OS process that is filesystem-scoped to that project alone — see Runtime & Isolation.

Invoking a function

POST /v1/functions/<project_id>/<function_name>
Terminal window
curl -X POST "http://localhost:39001/v1/functions/<id>/hello" \
-H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
-d '{"name":"Ada"}'
const { data } = await db.functions.invoke('hello', { body: { name: 'Ada' } })
res = db.functions.invoke("hello", {"body": {"name": "Ada"}})

What’s injected into a function

When the control plane proxies an invocation to the Deno runtime, it injects your project’s context as request headers (and strips any that the client tried to spoof). Inside the function you can read them to talk back to AnvilBase:

HeaderValue
X-AnvilBase-Project-IDthe project UUID
X-AnvilBase-URLthe control-plane base URL
X-AnvilBase-Anon-Keythe project anon key
X-AnvilBase-Service-Role-Keythe project service_role key
X-AnvilBase-DB-URLa direct Postgres connection string for the project DB

This means a function can query the database, call the REST API, read storage, or use the secrets vault as the project, without you hard-coding credentials.

On a public (verify_jwt: false) invocation made with no credential, the X-AnvilBase-Service-Role-Key header is withheld (anon-level context — see Authentication below). Project secrets are available regardless via Deno.env.get.

// supabase-functions style handler
Deno.serve(async (req) => {
const projectUrl = req.headers.get('X-AnvilBase-URL')!
const serviceKey = req.headers.get('X-AnvilBase-Service-Role-Key')!
const projectId = req.headers.get('X-AnvilBase-Project-ID')!
// Use the REST API as the project (service_role — server-side, bypasses RLS)
const r = await fetch(`${projectUrl}/v1/rest/${projectId}/todos?limit=5`, {
headers: { apikey: serviceKey },
})
const todos = await r.json()
return Response.json({ todos })
})

Authentication: verify_jwt

Each function has a verify_jwt flag, default true (secure by default):

  • verify_jwt: true — the caller must present a valid project credential (the project JWT or an API key in Authorization: Bearer or the apikey header). An invocation with no credential — or an invalid one — is rejected with 401. As always, the credential must belong to this project (cross-tenant credentials get 403).
  • verify_jwt: false — the function is public: it can be invoked with no credential at all (e.g. an inbound Stripe / GitHub / third-party webhook that can’t send your API key). Do your own verification inside (signature checks, shared-secret headers, etc.).

This gate is per-function: marking one function verify_jwt: false opens only that function. Every other function — and every other route on the platform — still requires authentication. A credential, when supplied, is still validated and tenant-checked even for a public function.

A public invocation gets anon-level context, never service_role

When a public function is invoked without a credential, the proxy injects only anon-level project context — the X-AnvilBase-Service-Role-Key header is not sent (a public webhook target must not get elevated DB access by default). If a public function genuinely needs service_role for a privileged DB write, store the service-role key (or any other credential) as a project secret and read it from Deno.env inside the function — explicitly. An authenticated invocation receives the full context unchanged.

Isolation & limits

  • One isolated process per project — each project’s functions run in their own OS process, filesystem-scoped to /functions/<project_id> only. A function cannot read another tenant’s source, and a crash in one project can’t take down another. Full details and pool/lifecycle behavior in Runtime & Isolation.
  • No ambient filesystem/network trust — Deno’s permission model sandboxes each worker; functions reach the outside world over fetch.
  • Function code size is capped (on the order of 10 MB) and function count is bounded by the project’s max_edge_function_count quota (breaches return 429).

See Multi-Tenancy for the platform-wide isolation model and Runtime & Isolation for the functions runtime specifically.

Where code is stored

Function metadata lives in the project database; the code body lives in object storage. The management API is the store-of-record and also syncs the code into the Deno runtime so console-deployed functions are immediately invocable. The runtime rehydrates code on a cold cache-miss via an internal endpoint you never call directly.

Next