Serverless Functions
Deploy custom backend logic as serverless functions. Functions are written in TypeScript or JavaScript and run in an isolated Deno environment with database access, environment variables, and network capabilities.
Deploying a function
Section titled “Deploying a function”POST /v1/{app_id}/functionsAuthorization: Bearer {token}
{ "name": "hello-world", "code": "export default async function handler(req) {\n return new Response(JSON.stringify({ message: 'Hello!' }), {\n headers: { 'Content-Type': 'application/json' }\n });\n}", "description": "A simple greeting function", "trigger": { "type": "http", "config": {} }}Required fields:
name— Unique name (1-100 characters)code— Function source code with a default export handler
Optional fields:
description— What the function doesenvVars— Key-value pairs for environment variables (encrypted at rest)timeoutMs— Max execution time (default: 30000, max: 300000)memoryLimitMb— Memory limit (default: 128, range: 64-1024)trigger— How the function is invoked
Trigger types
Section titled “Trigger types”| Type | Description | Config |
|---|---|---|
http | Called via HTTP requests (default) | {} |
cron | Runs on a schedule | {"schedule": "*/5 * * * *"} |
websocket | Fires on custom WebSocket events | {"event": "event_name"} |
Writing functions
Section titled “Writing functions”Functions receive a Request and must return a Response:
export default async function handler(req: Request, ctx: any): Promise<Response> { const body = await req.json();
return new Response(JSON.stringify({ result: 'ok' }), { status: 200, headers: { 'Content-Type': 'application/json' } });}Available inside a function:
- Standard Web APIs (fetch, Request, Response, Headers, URL, etc.)
- Environment variables via
ctx.env.VAR_NAME - Database access via
ctx.db.query(sql) - User info via
ctx.user(when invoked with end-user JWT) - Network access
RLS in functions
Section titled “RLS in functions”Functions respect RLS policies based on how they’re invoked:
| Invocation | Role | RLS |
|---|---|---|
| End-user JWT | butterbase_user | Enforced — sees only user’s data |
| Platform API key | butterbase_service | Bypassed — sees all data |
| Cron trigger | butterbase_service | Bypassed — sees all data |
User-scoped function
Section titled “User-scoped function”export default async function handler(req: Request, ctx: any): Promise<Response> { if (!ctx.user) { return new Response('Unauthorized', { status: 401 }); }
// Automatically filtered to current user's orders (RLS enforced) const orders = await ctx.db.query('SELECT * FROM orders');
return new Response(JSON.stringify(orders.rows), { headers: { 'Content-Type': 'application/json' } });}Testing RLS from service functions
Section titled “Testing RLS from service functions”Use ctx.db.asUser() and ctx.db.asAnon() to run queries under a specific role:
export default async function handler(req: Request, ctx: any): Promise<Response> { const userId = 'some-user-uuid';
// Runs as butterbase_user with RLS enforced const userPosts = await ctx.db.asUser(userId, async (db) => { const result = await db.query('SELECT * FROM posts'); return result.rows; });
// Runs as butterbase_anon with RLS enforced const publicProducts = await ctx.db.asAnon(async (db) => { const result = await db.query('SELECT * FROM products'); return result.rows; });
return new Response(JSON.stringify({ userPosts, publicProducts }), { headers: { 'Content-Type': 'application/json' } });}Using environment variables
Section titled “Using environment variables”// Deploy with: envVars: { "API_KEY": "secret123", "BASE_URL": "https://api.example.com" }
export default async function handler(req: Request, ctx: any): Promise<Response> { const apiKey = ctx.env.API_KEY; const baseUrl = ctx.env.BASE_URL;
const response = await fetch(`${baseUrl}/data`, { headers: { 'Authorization': `Bearer ${apiKey}` } });
return new Response(await response.text());}Calling functions from your frontend
Section titled “Calling functions from your frontend”HTTP-triggered functions are available at:
ANY /v1/{app_id}/fn/{function_name}Any HTTP method is supported. End-user tokens are forwarded to the function.
const response = await fetch(`${API_BASE}/v1/${appId}/fn/hello-world`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${userAccessToken}` }, body: JSON.stringify({ input: 'data' })});Cron functions
Section titled “Cron functions”Use standard cron expressions:
| Expression | Schedule |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour |
0 9 * * * | Daily at 9 AM |
0 0 * * 1 | Every Monday at midnight |
Function metrics
Section titled “Function metrics”Each function tracks: total invocation count, error count and rate, average execution duration, and last invocation time.
Invocation logs
Section titled “Invocation logs”Logs include: HTTP method and path, status code, execution duration, memory usage, and error messages with stack traces.