Files
supabase/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx
9ef9f1b8c1 feat(self-host): use @supabase/server in functions template and docs (#48996)
Updates the self-host Edge Functions template to use `@supabase/server`,
matching the CLI's `supabase functions new` templates (part of SDK-1150,
follows up on #45635 which exposed `SUPABASE_JWKS` to the functions
container). The `hello` example function now wraps its handler in
`withSupabase({ auth: 'none' })` and resolves the package through a
per-function `deno.json` import map, which the runtime auto-discovers,
so no dispatcher changes are needed. The self-hosted functions guide is
updated to match: the create-a-function snippet, a `ctx.supabaseAdmin`
example replacing the manual esm.sh `createClient` wiring, and a note
that `auth: 'user'` requires `SUPABASE_JWKS`. Verified on
`supabase/edge-runtime:v1.74.0` with the compose environment variables:
`curl /functions/v1/hello` returns the same response body as before, so
existing docs and troubleshooting pages stay accurate.

The `docker/.gitignore` change: `volumes/functions/**` ignores
self-hosters' own functions, but it also hid the new `deno.json`, which
must ship with the repo for the `hello` import to resolve. The allowlist
entries follow the existing `main/index.ts` pattern.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Edge Functions now support authenticated invocation with publishable
or secret API keys.
* Function handlers can access authenticated and administrative Supabase
clients through the request context.
* Added automatic environment configuration and JWT verification
support.

* **Documentation**
* Updated the self-hosting guide with the new function setup and
authentication workflow.
* Improved local function examples for supported access patterns and
privileged operations.

* **Tests**
* Updated self-hosted smoke tests to validate publishable-key function
access.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Kalleby Santos <kalleby_santos@hotmail.com>
Co-authored-by: Kalleby Santos <105971119+kallebysantos@users.noreply.github.com>
2026-08-14 12:00:11 +03:00

254 lines
9.0 KiB
Plaintext

---
title: 'Self-Hosted Functions'
description: 'Run and manage Edge Functions in your self-hosted Supabase instance.'
subtitle: 'Run and manage Edge Functions in your self-hosted Supabase instance.'
---
Edge Functions work out of the box in a self-hosted Supabase setup. The `functions` service, API gateway routing, and a `hello` example function are all [pre-configured](https://github.com/supabase/supabase/tree/master/docker).
<Admonition type="note">
On managed Supabase platform, Edge Functions are deployed across multiple regions. Self-hosted standalone instance configuration resembles a standard serverless setup.
</Admonition>
## Invoke the default function
The default `hello` function is located at `volumes/functions/hello/index.ts`. You can invoke it immediately after starting your stack:
```sh
curl http://<your-domain>/functions/v1/hello \
--header 'apiKey: <sb_publishable/sb_secret key>'
```
This returns:
```json
{ "message": "Hello from Edge Functions!" }
```
## Create a new function
### Step 1: Add a new function directory and the function code
```sh
mkdir -p volumes/functions/my-function &&
touch volumes/functions/my-function/index.ts
```
Add the following code to `index.ts`:
```typescript
import { withSupabase } from '@supabase/server'
export default {
fetch: withSupabase({ auth: 'none' }, async (req) => {
const { name } = await req.json()
const message = `Hello, ${name}!`
return Response.json({ message })
}),
}
```
The `auth` option controls who can call the function: `'none'` accepts every request, `'user'` requires a valid user JWT, and `'publishable'` / `'secret'` require an API key. See the [Edge Functions auth guide](/docs/guides/functions/auth) for details.
### Step 2: Restart the functions service to pick up the new function
```sh
sh run.sh restart functions
```
### Step 3: Invoke your function
```sh
curl -X POST http://<your-domain>/functions/v1/my-function \
-H 'Content-Type: application/json' \
-d '{"name": "World"}'
```
You should be able to see the response from `my-function`:
```json
{ "message": "Hello, World!" }
```
## Custom environment variables
### Using an env file (recommended)
For multiple variables or secrets, create a separate env file, e.g., `.env.functions` in your `docker/` directory:
```
MY_CUSTOM_VAR=some-value
```
Add `env_file` to the `functions` service in `docker-compose.yml` (variables in `env_file` load first, then `environment` values take precedence):
```yaml name=docker-compose.yml
functions:
env_file:
- .env.functions
environment:
# ... existing variables ...
```
<Admonition type="caution">
Don't commit `.env.functions` to version control if it contains secrets. Add it to your `.gitignore`.
</Admonition>
Restart the functions service:
```sh
sh run.sh recreate functions
```
### Using inline environment variables
For one or two variables, you can add them directly under `environment` in `docker-compose.yml`:
```yaml name=docker-compose.yml
functions:
environment:
# Custom variables
MY_CUSTOM_VAR: ${MY_CUSTOM_VAR}
# ... existing variables ...
```
Then define `MY_CUSTOM_VAR` in your main `.env` file, or specify the value directly.
### Accessing variables in functions
All container environment variables are forwarded to the function workers by `main/index.ts`. Access them with:
```typescript
const customVar = Deno.env.get('MY_CUSTOM_VAR')
```
## Calling Supabase services from functions
The functions service is pre-configured with the following environment variables:
| Variable | Value | Purpose |
| --------------------------- | --------------------------------- | ------------------------------------------------- |
| `SUPABASE_URL` | `http://api-gw:8000` | Internal API gateway URL |
| `SUPABASE_PUBLIC_URL` | `http(s)://<your-domain>` | Base URL for accessing Supabase from the Internet |
| `JWT_SECRET` | `your-jwt-secret` | Legacy symmetric encryption key for JWTs |
| `SUPABASE_ANON_KEY` | `your-anon-key` | Client-side API key (`anon` role). |
| `SUPABASE_SERVICE_ROLE_KEY` | `your-service-role-key` | Server-side API key (`service_role` role) |
| `SUPABASE_DB_URL` | `postgresql://...` | Postgres connection string |
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_...}` | New publishable API key |
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_...}` | New secret API key |
| `SUPABASE_JWKS` | `{"keys":[{...}]}` | JWKS used to verify JWTs issued by Auth |
Here's an example function that queries a table using the admin client provided by `@supabase/server`:
```typescript
import { withSupabase } from '@supabase/server'
export default {
fetch: withSupabase({ auth: 'secret' }, async (_req, ctx) => {
// ctx.supabaseAdmin bypasses RLS. This function requires a secret
// API key, so only server-to-server callers can reach it.
const { data, error } = await ctx.supabaseAdmin.from('todos').select('*')
return Response.json({ data, error })
}),
}
```
`withSupabase` reads `SUPABASE_URL`, the API keys, and `SUPABASE_JWKS` from the environment variables above. You don't need to wire up `createClient` yourself.
<Admonition type="note">
`auth: 'user'` verifies caller JWTs against `SUPABASE_JWKS`. If you're on a legacy setup without it configured, see [New API Keys and Asymmetric Authentication](/docs/guides/self-hosting/self-hosted-auth-keys).
</Admonition>
### Internal vs external URLs
This is a key distinction that affects how you build URLs in your functions:
- **`SUPABASE_URL`** contains an internal Docker network hostname. Use it for server-side calls from your functions to other Supabase services (Auth, Storage, database via PostgREST). This is what the Supabase JS client should use inside functions.
- **`SUPABASE_PUBLIC_URL`** is the externally-reachable URL of your Supabase instance. Use it if your function needs to build URLs that HTTP clients can reach from the outside.
## Managing functions via dashboard
Self-hosted Studio [mounts](https://github.com/supabase/supabase/blob/df8729a82b1847e2989c14ede27965612761d503/docker/docker-compose.yml#L66) the same `volumes/functions` directory as the functions service. You can check what functions are available using **Edge Functions** > **Functions** UI.
## Deploying functions to a remote server
To deploy a function to a remote server running self-hosted Supabase, copy the function directory with `scp`:
```sh
scp -r ./my-function user@<your-domain>:/path/to/self-hosted/volumes/functions/
```
Then restart the functions service on the remote host:
```sh
ssh user@<your-domain> 'cd /path/to/self-hosted && sh run.sh restart functions'
```
## Copying functions from Supabase platform
If you have existing functions on Supabase platform, you can download them and run them on your self-hosted instance. There are two ways to get the function source code:
- **Dashboard** - open the function details in Dashboard and click **Download**.
- **Local development & CLI** - run `supabase functions download <function-name> --project-ref <ref>` to download the source.
Use `scp` to copy the function into `volumes/functions/<function-name>/` on your self-hosted instance, then restart the functions service.
For more details, see:
- [Quick start - Download edge functions](/docs/guides/functions/quickstart-dashboard#download-edge-functions)
- [CLI commands - Download a function](/docs/reference/cli/supabase-functions-download)
## Troubleshooting
### 400 "missing function name in request"
The request URL must include the function name after `/functions/v1/`. For example, `/functions/v1/hello`.
### 500 error on invocation
Check the functions service logs:
```sh
docker compose logs functions
```
Common causes: syntax errors in your function code, invalid imports, or missing dependencies.
### 401 "invalid JWT"
- Check that `FUNCTIONS_VERIFY_JWT` matches your intent (`true` or `false`) in `.env`
- If verification is enabled, ensure you're passing a valid token: `Authorization: Bearer <anon_key or service_role_key>`
### Changes to function code not reflected after editing
Restart the functions service:
```sh
sh run.sh restart functions
```
### Custom env vars not available in functions
- Verify the variable is defined in `docker-compose.yml` (under `env_file` or `environment`)
- Recreate the functions container after changing configuration
- Check that the variable name matches exactly (case-sensitive)
Use the following command to recreate the container:
```sh
sh run.sh recreate functions
```
### Memory or timeout errors
The default limits are 150 MB memory and 60 seconds timeout per function invocation. These are set in `volumes/functions/main/index.ts`. To adjust them, edit the `memoryLimitMb` and `workerTimeoutMs` values and restart the functions service.