Skip to content

Edge Functions

Edge Functions are serverless functions powered by Deno that execute JavaScript/TypeScript code in response to HTTP requests.

  • Deno runtime for TypeScript/JavaScript
  • HTTP triggered via REST API or SDK
  • Secure sandbox with configurable permissions
  • Direct database access
  • Execution logging and versioning
  • Configurable timeouts
  • Process webhooks from third-party services
  • Transform and validate data
  • Integrate with external APIs
  • Run scheduled tasks
  • Implement custom business logic
  • Extend authentication flows
Terminal window
npm install @nimbleflux/fluxbase-sdk
import { FluxbaseClient } from "@nimbleflux/fluxbase-sdk";
const client = new FluxbaseClient({
url: "http://localhost:8080",
apiKey: process.env.FLUXBASE_CLIENT_KEY,
});
// Create function
await client.functions.create({
name: "hello-world",
description: "My first edge function",
code: `
async function handler(req) {
const data = JSON.parse(req.body || '{}')
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: \`Hello \${data.name || 'World'}!\`
})
}
}
`,
enabled: true,
});
// Invoke function
const result = await client.functions.invoke("hello-world", {
name: "Alice",
});
console.log(result); // { message: "Hello Alice!" }
// List functions
const functions = await client.functions.list();
// Get function details
const details = await client.functions.get("hello-world");
// View execution history
const executions = await client.functions.getExecutions("hello-world", {
limit: 10,
});

Every function must export an async handler function:

async function handler(req) {
// req contains: method, url, headers, body, params
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ result: "success" }),
};
}
interface Request {
method: string; // GET, POST, etc.
url: string;
headers: Record<string, string>;
body: string; // Raw body (use JSON.parse for JSON)
params: Record<string, string>; // Query parameters
// Authentication context (if user is authenticated)
user_id?: string; // Authenticated user's ID
user_email?: string; // User's email from JWT claims
user_role?: string; // User's role from JWT claims
session_id?: string; // Current session ID
}

Edge functions can import and use the Fluxbase SDK for database operations, authentication, and more:

import { createClient } from "@nimbleflux/fluxbase-sdk";
async function handler(req) {
// Create a service client with elevated permissions
const client = createClient(
Deno.env.get("FLUXBASE_BASE_URL")!,
Deno.env.get("FLUXBASE_SERVICE_ROLE_KEY")!,
);
// Query the database
const { data, error } = await client
.from("users")
.select("*")
.eq("id", req.user_id)
.execute();
if (error) {
return {
status: 500,
body: JSON.stringify({ error: error.message }),
};
}
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
};
}

Note: The SDK is available via import maps configured in /functions/deno.json. For local development with live SDK changes, use /functions/deno.dev.json which points to your local SDK mount.

interface Response {
status: number;
headers?: Record<string, string>;
body?: string;
}
async function handler(req) {
const { email } = JSON.parse(req.body || "{}");
// Validate email
if (!email || !email.includes("@")) {
return {
status: 400,
body: JSON.stringify({ error: "Invalid email" }),
};
}
return {
status: 200,
body: JSON.stringify({ valid: true }),
};
}
async function handler(req) {
// Database client is available via env
const dbUrl = Deno.env.get("DATABASE_URL");
// Use pg client or any PostgreSQL library
const result = await fetch(`${dbUrl}/users`);
return {
status: 200,
body: JSON.stringify(result),
};
}
async function handler(req) {
const { query } = JSON.parse(req.body || "{}");
// Call external API
const response = await fetch(`https://api.example.com/search?q=${query}`);
const data = await response.json();
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
};
}
async function handler(req) {
try {
const data = JSON.parse(req.body || "{}");
// Process data
const result = await processData(data);
return {
status: 200,
body: JSON.stringify({ result }),
};
} catch (error) {
return {
status: 500,
body: JSON.stringify({
error: "Processing failed",
message: error.message,
}),
};
}
}

Functions automatically receive the authenticated user context:

async function handler(req) {
// User info from JWT token (if authenticated)
const userId = req.user_id; // UUID string
const userEmail = req.user_email; // Email string
const userRole = req.user_role; // Role string (e.g., "authenticated", "admin")
if (!userId) {
return {
status: 401,
body: JSON.stringify({ error: "Unauthorized" }),
};
}
// Use user context for authorization
return {
status: 200,
body: JSON.stringify({ userId, userEmail, userRole }),
};
}

How it works:

  1. Fluxbase validates the JWT token in the Authorization header
  2. User context (id, email, role) is extracted from the token
  3. User info is added to the request object (user_id, user_email, user_role)
  4. Function handler receives the request with user context
  5. Handler can use user info for authorization and data filtering
graph TB
A[HTTP Request] --> B[Initialize Deno Runtime]
B --> C[Load Function Code]
C --> D[Apply Permissions<br/>net, env, read, write]
D --> E[Execute handler req]
E --> F{Success?}
F -->|Yes| G[Return Response Object]
F -->|Error| H[Return Error Response]
G --> I[HTTP Response to Client]
H --> I
style B fill:#000,color:#fff
style D fill:#ff6b6b,color:#fff

Execution steps:

  1. Initialize Deno runtime with configured permissions
  2. Load the function code into the sandbox
  3. Apply security permissions (net, env, read, write)
  4. Execute the handler with the request object
  5. Return the response to the client

Fluxbase supports importing npm packages in your edge functions using Deno’s npm: specifier or URL imports. Functions are automatically bundled when they contain imports.

// Import from npm
import { z } from "npm:zod@3.22.4";
import dayjs from "npm:dayjs@1.11.10";
// Import from URL
import { marked } from "https://esm.sh/marked@9.1.0";
async function handler(req) {
const data = JSON.parse(req.body || "{}");
// Use zod for validation
const schema = z.object({
name: z.string(),
email: z.email(),
});
const validated = schema.parse(data);
return {
status: 200,
body: JSON.stringify({ success: true, data: validated }),
};
}
  1. Detection - Fluxbase detects import statements in your code
  2. Validation - Imports are checked against security blocklist
  3. Bundling - Deno bundles your code with dependencies into a single file
  4. Storage - Bundled code is stored in the database for fast execution
  5. Execution - Runtime uses the pre-bundled code (no bundling overhead)

Performance: Bundling happens once at creation/update time, not on every invocation.

For security, the following packages are blocked:

  • child_process / node:child_process - Process execution
  • vm / node:vm - Code evaluation
  • fs / node:fs - Filesystem access
  • process / node:process - Process manipulation

Use Deno’s built-in APIs or web-standard alternatives instead.

If bundling fails, Fluxbase stores the error but keeps the unbundled code:

// Check bundling status
const func = await client.functions.get("my-function");
console.log("Is bundled:", func.is_bundled);
console.log("Bundle error:", func.bundle_error);

Common bundling errors:

  • Package not found - Check package name and version
  • Network timeout - npm registry may be slow/unavailable
  • Bundle too large - Limit is 5MB after bundling
  • Blocked package - Using a restricted security package

Air-Gapped / Private Registry Environments

Section titled “Air-Gapped / Private Registry Environments”

By default, Deno downloads packages from public registries:

  • npm packages (npm:zod) from registry.npmjs.org
  • JSR packages (jsr:@std/path) from jsr.io

For air-gapped environments or private registries, you have several options:

Option 1: Configure server-wide registries (Recommended)

Set custom npm and/or JSR registries for all edge functions and jobs at the server level:

fluxbase.yaml
deno:
npm_registry: "https://npm.your-company.com/"
jsr_registry: "https://jsr.your-company.com/"

Or via environment variables:

Terminal window
FLUXBASE_DENO_NPM_REGISTRY=https://npm.your-company.com/
FLUXBASE_DENO_JSR_REGISTRY=https://jsr.your-company.com/

Or in Docker Compose:

environment:
FLUXBASE_DENO_NPM_REGISTRY: "https://npm.your-company.com/"
FLUXBASE_DENO_JSR_REGISTRY: "https://jsr.your-company.com/"

Or in Helm values:

config:
deno:
npm_registry: "https://npm.your-company.com/"
jsr_registry: "https://jsr.your-company.com/"

This applies to all edge functions and background jobs using npm: or jsr: specifiers.

Option 2: Use a deno.json per function (overrides server config)

Create a deno.json file in your function directory to configure a private npm registry:

{
"npmRegistry": "https://npm.your-company.com/"
}

Or with authentication:

{
"npmRegistry": {
"url": "https://npm.your-company.com/",
"auth": "bearer <token>"
}
}

Then your functions can use npm: specifiers as normal - Deno will fetch from your private registry.

Option 3: Use URL imports instead of npm specifiers

Import directly from ESM CDNs you control or mirror:

// Instead of npm:zod
import { z } from "https://your-cdn.example.com/zod@3.22.4/index.mjs";
// Or use esm.sh, unpkg, skypack (if accessible)
import { z } from "https://esm.sh/zod@3.22.4";

You can self-host esm.sh or mirror specific packages.

Option 4: Pre-bundle on a machine with internet access (Recommended for air-gapped)

Bundle your functions on a development machine with internet access, then deploy the pre-bundled code to your air-gapped server. The server will skip bundling when it receives pre-bundled code.

Method A: Use the CLI with Deno installed locally

The Fluxbase CLI automatically bundles locally when Deno is available:

Terminal window
# On machine with internet + Deno installed
cd your-project/fluxbase/functions
# Sync will bundle locally and send pre-bundled code
fluxbase functions sync --namespace production
# Output shows local bundling:
# Bundling my-function... 2.5KB → 156KB
# Synced functions: 3 created, 0 updated, 0 deleted.

The CLI sends is_bundled: true and original_code so the server stores both versions (bundled for execution, original for editing in the admin UI).

Method B: Bundle manually with Deno

Terminal window
# On machine with internet access
deno bundle my-function.ts bundled.ts
# Deploy via API with is_bundled flag
curl -X POST http://your-server:8080/api/v1/functions \
-H "X-Service-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "my-function",
"code": "'"$(cat bundled.ts)"'",
"original_code": "'"$(cat my-function.ts)"'",
"is_bundled": true,
"enabled": true
}'

Method C: Build a deployment pipeline

For CI/CD pipelines, bundle in a container with internet access:

# GitHub Actions example
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: denoland/setup-deno@v1
- name: Bundle and deploy
run: |
fluxbase functions sync --namespace production
env:
FLUXBASE_URL: ${{ secrets.FLUXBASE_URL }}
FLUXBASE_SERVICE_KEY: ${{ secrets.FLUXBASE_SERVICE_KEY }}

Option 5: Avoid npm packages entirely

Use Deno’s standard library and web APIs, which don’t require network access:

// Use Deno std library (bundled)
import { crypto } from "https://deno.land/std@0.200.0/crypto/mod.ts";
// Or rely on built-in web APIs
const hash = await crypto.subtle.digest("SHA-256", data);

Shared modules allow you to reuse code across multiple edge functions, similar to Supabase’s _shared directory pattern. Modules are stored with a _shared/ prefix and can be imported by any function.

Via SDK:

await client.functions.createSharedModule({
module_path: "_shared/cors.ts",
description: "CORS headers helper",
content: `
export function corsHeaders(origin?: string) {
return {
'Access-Control-Allow-Origin': origin || '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE'
}
}
`,
});

Via File System:

Create a _shared/ directory in your functions folder:

functions/
├── _shared/
│ ├── cors.ts ← Shared CORS utilities
│ ├── database.ts ← Database helpers
│ └── validation.ts ← Input validation
└── my-function.ts
functions/_shared/cors.ts
export function corsHeaders(origin?: string) {
return {
"Access-Control-Allow-Origin": origin || "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
};
}
export function handleCors(req: Request) {
if (req.method === "OPTIONS") {
return {
status: 200,
headers: corsHeaders(),
};
}
return null;
}

Import shared modules using the _shared/ prefix:

functions/my-api.ts
import { corsHeaders, handleCors } from "_shared/cors.ts";
import { validateEmail } from "_shared/validation.ts";
async function handler(req) {
// Handle preflight requests
const corsResponse = handleCors(req);
if (corsResponse) return corsResponse;
const data = JSON.parse(req.body || "{}");
// Use shared validation
if (!validateEmail(data.email)) {
return {
status: 400,
headers: corsHeaders(),
body: JSON.stringify({ error: "Invalid email" }),
};
}
return {
status: 200,
headers: corsHeaders(),
body: JSON.stringify({ success: true }),
};
}

Organize shared code in subdirectories:

functions/
├── _shared/
│ ├── utils/
│ │ ├── date.ts
│ │ └── string.ts
│ ├── database/
│ │ ├── connection.ts
│ │ └── queries.ts
│ └── cors.ts
└── my-function/
└── index.ts
functions/my-function/index.ts
import { corsHeaders } from "_shared/cors.ts";
import { formatDate } from "_shared/utils/date.ts";
import { query } from "_shared/database/queries.ts";
async function handler(req) {
const users = await query("SELECT * FROM users");
const timestamp = formatDate(new Date());
return {
status: 200,
headers: corsHeaders(),
body: JSON.stringify({ users, timestamp }),
};
}

CORS Helper:

_shared/cors.ts
export function corsHeaders(origin?: string) {
return {
"Access-Control-Allow-Origin": origin || "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
};
}
export function handleCors(req: Request) {
if (req.method === "OPTIONS") {
return {
status: 200,
headers: corsHeaders(),
};
}
return null;
}

Input Validation:

_shared/validation.ts
export function validateEmail(email: string): boolean {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
export function validateRequired(data: any, fields: string[]): string[] {
const missing = [];
for (const field of fields) {
if (!data[field]) {
missing.push(field);
}
}
return missing;
}
export function sanitizeString(str: string): string {
return str.trim().replace(/[<>]/g, "");
}

Error Responses:

_shared/errors.ts
export function errorResponse(message: string, status = 400) {
return {
status,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ error: message }),
};
}
export function successResponse(data: any, status = 200) {
return {
status,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
};
}

List all shared modules:

const modules = await client.functions.listSharedModules();
console.log(modules);
// [
// { module_path: '_shared/cors.ts', version: 1, ... },
// { module_path: '_shared/validation.ts', version: 2, ... }
// ]

Get a specific module:

const module = await client.functions.getSharedModule("_shared/cors.ts");
console.log(module.content);

Update a shared module:

await client.functions.updateSharedModule("_shared/cors.ts", {
content: `
export function corsHeaders() {
// Updated implementation
}
`,
description: "Updated CORS helper",
});

Delete a shared module:

await client.functions.deleteSharedModule("_shared/cors.ts");
  1. Detection - Fluxbase detects imports from _shared/ in function code
  2. Loading - All shared modules are loaded from database or filesystem
  3. Bundling - Deno bundles the function with all dependencies and shared modules
  4. Storage - Bundled code is stored for fast execution
  5. Execution - Runtime uses the pre-bundled code

Important: When you update a shared module, you must re-save or reload any functions that use it to trigger re-bundling.

File-Based (functions/_shared/*.ts):

  • ✅ Version controlled with your code
  • ✅ Easy local development
  • ✅ Syncs on reload/boot
  • ❌ Requires filesystem access

Database (created via SDK/API):

  • ✅ Dynamically updatable
  • ✅ No filesystem required
  • ✅ Versioning built-in
  • ❌ Not version controlled with code

Best Practice: Use file-based shared modules for production deployments and database modules for dynamic/temporary utilities.

await client.functions.create({
name: "my-function",
code: `async function handler(req) { ... }`,
enabled: true,
});
// Update existing function
await client.functions.update("my-function", {
code: `async function handler(req) { ... }`,
});

Mount a directory with function files:

docker-compose.yml
services:
fluxbase:
image: ghcr.io/nimbleflux/fluxbase:latest
volumes:
- ./functions:/app/functions
environment:
FLUXBASE_FUNCTIONS_ENABLED: "true"
FLUXBASE_FUNCTIONS_FUNCTIONS_DIR: /app/functions
FLUXBASE_FUNCTIONS_AUTO_LOAD_ON_BOOT: "true" # Load on startup (default: true)

Function File Patterns:

Fluxbase supports two ways to organize your functions:

1. Flat File Pattern (simple functions):

functions/hello.ts
async function handler(req) {
return {
status: 200,
body: JSON.stringify({ message: "Hello!" }),
};
}

2. Directory Pattern (complex functions with multiple files):

functions/
└── complex-webhook/
├── index.ts ← Entry point (handler function)
├── types.ts ← Type definitions
├── helpers.ts ← Shared utilities
└── config.ts ← Configuration
functions/complex-webhook/index.ts
import { processData } from "./helpers.ts";
import { WebhookConfig } from "./types.ts";
async function handler(req) {
const data = processData(req.body);
return {
status: 200,
body: JSON.stringify(data),
};
}

Priority Rules:

  • If both hello.ts and hello/index.ts exist, the flat file takes precedence
  • Directory pattern requires index.ts as the entry point
  • Only .ts files are supported

Auto-Load on Boot: By default (AUTO_LOAD_ON_BOOT=true), functions are automatically loaded from the filesystem when Fluxbase starts. Auto-load:

  • ✅ Creates new functions from filesystem
  • ✅ Updates existing functions from filesystem
  • Never deletes functions (preserves UI-created functions)

Manual Reload: Trigger full sync after adding/updating/deleting function files:

Terminal window
curl -X POST http://localhost:8080/api/v1/admin/functions/reload \
-H "Authorization: Bearer ADMIN_TOKEN"

Manual reload performs full synchronization:

  • ✅ Creates new functions from filesystem
  • ✅ Updates existing functions from filesystem
  • Deletes functions missing from filesystem

File-Based Workflow:

Mixed UI + File-based Functions:

  • Functions created via UI/SDK remain in database even after container restarts
  • File-based functions sync on boot (create/update only)
  • Use manual reload to remove functions deleted from filesystem

Pure File-based Functions:

  1. Mount functions directory to container
  2. Add/update/delete .ts or .js files
  3. Call reload endpoint to sync deletions
  4. Functions are bundled and stored in database

Important: If you use both UI-created and file-based functions, do not call the reload endpoint unless you understand it will delete UI-created functions that don’t exist on the filesystem.

  • Navigate to Functions section
  • Click “New Function”
  • Write code in browser editor
  • Save (stores in database and syncs to filesystem)

Namespaces provide multi-tenant function isolation. Use namespaces to organize functions by team, environment, or service:

// Create function in specific namespace
await client.admin.functions.create({
name: "process-payment",
namespace: "payment-service",
code: `async function handler(req) { ... }`,
});
// Invoke with namespace
const result = await client.functions.invoke(
"payment-service/process-payment",
{
body: JSON.stringify({ amount: 100 }),
},
);
// List functions by namespace
const functions = await client.admin.functions.list("payment-service");

Default namespace: Functions without an explicit namespace use "default".

Namespace use cases:

  • Multi-tenancy: Isolate functions per customer
  • Environments: Separate dev, staging, production functions
  • Services: Organize by microservice boundaries

For bulk function deployment, use the sync API:

// Sync functions from filesystem or API
const result = await client.admin.functions.sync({
namespace: "default",
functions: [
{ name: "fn1", code: "..." },
{ name: "fn2", code: "..." },
],
delete_missing: false, // Don't delete existing functions
dry_run: false, // Apply changes
});
console.log(result.summary);
// { created: 2, updated: 0, deleted: 0, unchanged: 5 }

Options:

  • delete_missing: true - Remove functions not in sync list
  • dry_run: true - Preview changes without applying

Performance:

  • Keep functions lightweight
  • Avoid long-running operations (use timeouts)
  • Cache external API responses when possible
  • Minimize database queries

Security:

  • Validate all inputs
  • Never expose secrets in function code
  • Use environment variables for sensitive data
  • Implement proper authentication checks
  • Sanitize user-provided data

Error Handling:

  • Always wrap code in try-catch blocks
  • Return appropriate HTTP status codes
  • Log errors for debugging
  • Provide meaningful error messages

Code Organization:

  • Keep functions focused on single tasks
  • Extract shared logic into utility modules
  • Use consistent naming conventions
  • Document function purpose and parameters

Set function-level configuration:

await client.functions.create({
name: "my-function",
code: "...",
timeout: 30, // seconds
memory: 256, // MB
env: {
API_KEY: "secret-key",
API_URL: "https://api.example.com",
},
});

Access environment variables in function:

async function handler(req) {
const apiKey = Deno.env.get("API_KEY");
// Use apiKey...
}

Fluxbase supports special @fluxbase: directives in function code comments to configure function behavior. These annotations provide a convenient way to set function-level configuration without API calls.

@fluxbase:namespace - Specify which namespace the function belongs to (overrides CLI --namespace flag):

/**
* Production-only function
*
* @fluxbase:namespace production
*/
async function handler(req) {
// This function will be deployed to the 'production' namespace
return { status: 200, body: "OK" };
}

This is useful when you want to keep functions for different environments in the same directory but deploy them to different namespaces based on the annotation.

@fluxbase:allow-unauthenticated - Allow function invocation without authentication:

/**
* Public webhook handler
*
* @fluxbase:allow-unauthenticated
*/
async function handler(req) {
// This function can be called without auth
return {
status: 200,
body: JSON.stringify({ message: "OK" }),
};
}

@fluxbase:public - Control whether function is publicly listed (default: true):

/**
* Internal helper function
*
* @fluxbase:public false
*/
async function handler(req) {
// This function won't appear in public function listings
return { status: 200, body: "OK" };
}

Configure Cross-Origin Resource Sharing (CORS) per function. If not specified, functions use the global FLUXBASE_CORS_* environment variables.

@fluxbase:cors-origins - Allowed origins (comma-separated):

/**
* API function with restricted origins
*
* @fluxbase:cors-origins https://app.example.com,https://admin.example.com
*/
async function handler(req) {
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data: "result" }),
};
}

@fluxbase:cors-methods - Allowed HTTP methods (comma-separated):

/**
* Read-only API endpoint
*
* @fluxbase:cors-methods GET,OPTIONS
*/
async function handler(req) {
// Only GET and OPTIONS methods allowed
return { status: 200, body: JSON.stringify({ data }) };
}

@fluxbase:cors-headers - Allowed request headers (comma-separated):

/**
* Custom header requirements
*
* @fluxbase:cors-headers Content-Type,Authorization,X-API-Key
*/
async function handler(req) {
const apiKey = req.headers["x-api-key"];
// Process request...
}

@fluxbase:cors-credentials - Allow credentials (cookies, auth headers):

/**
* Function that requires cookies
*
* @fluxbase:cors-origins https://app.example.com
* @fluxbase:cors-credentials true
*/
async function handler(req) {
// Can receive cookies from app.example.com
return { status: 200, body: "OK" };
}

@fluxbase:cors-max-age - Preflight cache duration in seconds:

/**
* Long-lived preflight cache
*
* @fluxbase:cors-max-age 3600
*/
async function handler(req) {
// Preflight results cached for 1 hour
return { status: 200, body: "OK" };
}

Control how many requests each user or IP can make to your function within a time window.

@fluxbase:rate-limit - Limit requests per user/IP with format N/unit where unit is min, hour, or day:

/**
* API endpoint with rate limiting
*
* @fluxbase:rate-limit 100/min
*/
async function handler(req) {
// Each user/IP can make max 100 requests per minute
return {
status: 200,
body: JSON.stringify({ data: "result" }),
};
}

Hourly and daily limits:

/**
* Expensive AI endpoint with strict limits
*
* @fluxbase:rate-limit 1000/hour
*/
async function handler(req) {
// Max 1000 requests per hour per user/IP
return { status: 200, body: "OK" };
}
/**
* Free tier endpoint with daily quota
*
* @fluxbase:rate-limit 10000/day
*/
async function handler(req) {
// Max 10000 requests per day per user/IP
return { status: 200, body: "OK" };
}

Rate limit behavior:

  • Authenticated users: Limits are tracked per user ID
  • Anonymous requests: Limits are tracked per IP address
  • Exceeded limits: Returns 429 Too Many Requests with headers:
    • Retry-After: Seconds until the limit resets
    • X-RateLimit-Limit: The configured limit
    • X-RateLimit-Remaining: Requests remaining (0 when exceeded)
    • X-RateLimit-Reset: Unix timestamp when the limit resets

Example response when rate limited:

{
"error": "Rate limit exceeded: 100 requests per minute",
"retry_after": 45
}
/**
* Webhook handler with custom CORS
*
* @fluxbase:allow-unauthenticated
* @fluxbase:cors-origins https://api.stripe.com,https://api.github.com
* @fluxbase:cors-methods POST,OPTIONS
* @fluxbase:cors-headers Content-Type,X-Stripe-Signature,X-Hub-Signature
* @fluxbase:cors-max-age 300
*/
async function handler(req) {
if (req.method === "POST") {
// Process webhook
const payload = JSON.parse(req.body);
// ... handle webhook ...
return {
status: 200,
body: JSON.stringify({ received: true }),
};
}
return { status: 405, body: "Method not allowed" };
}

Configuration sources are applied in this order (highest to lowest priority):

  1. API Request - Explicit values in functions.create() or functions.update()
  2. Code Annotations - @fluxbase: directives in function comments
  3. Global Defaults - FLUXBASE_CORS_* environment variables

Example:

// Function with partial CORS config
/**
* @fluxbase:cors-origins https://app.example.com
*/
async function handler(req) { ... }
// Result:
// - origins: https://app.example.com (from annotation)
// - methods: FLUXBASE_CORS_ALLOWED_METHODS (from env)
// - headers: FLUXBASE_CORS_ALLOWED_HEADERS (from env)
// - credentials: FLUXBASE_CORS_ALLOW_CREDENTIALS (from env)
// - max_age: FLUXBASE_CORS_MAX_AGE (from env)

When CORS is configured (via annotations, API, or global settings), Fluxbase automatically handles OPTIONS preflight requests. Your function code doesn’t need to handle OPTIONS methods - the server manages this automatically.

const logs = await client.functions.getLogs("my-function", {
limit: 50,
});
logs.forEach((log) => {
console.log(`${log.timestamp}: ${log.message}`);
});

Use Deno CLI to test functions:

Terminal window
deno run --allow-net --allow-env my-function.ts
const executions = await client.functions.getExecutions("my-function");
executions.forEach((exec) => {
console.log("Status:", exec.status);
console.log("Duration:", exec.duration_ms, "ms");
console.log("Error:", exec.error);
});
  • Maximum execution time: 60 seconds (configurable)
  • Maximum response size: 6MB
  • No filesystem persistence (use database or external storage)
  • Limited Deno permissions by default (configurable)

Permissions: Functions run with restricted permissions. Enable only what’s needed:

await client.functions.create({
name: "my-function",
code: "...",
permissions: {
net: true, // Allow network access
env: true, // Allow environment variables
read: false, // Deny filesystem read
write: false, // Deny filesystem write
},
});

Input Validation: Always validate and sanitize inputs:

async function handler(req) {
const data = JSON.parse(req.body || "{}");
// Validate required fields
if (!data.email || typeof data.email !== "string") {
return { status: 400, body: JSON.stringify({ error: "Invalid email" }) };
}
// Sanitize inputs
const email = data.email.trim().toLowerCase();
// Continue processing...
}

Environment Variable Security: Only FLUXBASE_* prefixed variables are accessible in functions. Sensitive secrets are automatically blocked for security:

// ✅ Allowed
const apiUrl = Deno.env.get("FLUXBASE_API_URL");
const customConfig = Deno.env.get("FLUXBASE_MY_CONFIG");
// ❌ Blocked (returns undefined)
const jwtSecret = Deno.env.get("FLUXBASE_AUTH_JWT_SECRET");
const dbPassword = Deno.env.get("FLUXBASE_DATABASE_PASSWORD");
const s3Secret = Deno.env.get("FLUXBASE_STORAGE_S3_SECRET_KEY");

Blocked variables:

  • FLUXBASE_AUTH_JWT_SECRET
  • FLUXBASE_DATABASE_PASSWORD / FLUXBASE_DATABASE_ADMIN_PASSWORD
  • FLUXBASE_STORAGE_S3_SECRET_KEY / FLUXBASE_STORAGE_S3_ACCESS_KEY
  • FLUXBASE_EMAIL_SMTP_PASSWORD
  • FLUXBASE_SECURITY_SETUP_TOKEN

Secrets provide secure storage for sensitive values like client keys, database credentials, and tokens. Secrets are encrypted at rest and made available to functions via the built-in secrets object.

Use the CLI or SDK to manage secrets. There are two types of secrets:

System secrets (admin-only, available to all functions):

Terminal window
fluxbase settings secrets set stripe_api_key "sk_live_xxx"
fluxbase settings secrets set openai_api_key "sk-proj-xxx"
fluxbase settings secrets list

User secrets (per-user, set by users via SDK):

// Users can set their own secrets via the SDK
await client.settings.setSecret("openai_api_key", "sk-proj-user-key");

The secrets object is automatically available in all functions - no import needed:

// The 'secrets' object is automatically available - no import needed
export default async function handler(req: Request): Promise<Response> {
// Get with automatic fallback: user secret -> system secret
const openaiKey = secrets.getRequired("openai_api_key");
// Get without throwing (returns undefined if not found)
const stripeKey = secrets.get("stripe_api_key");
// Explicit access to user vs system secrets
const userKey = secrets.getUser("openai_api_key");
const systemKey = secrets.getSystem("stripe_api_key");
// Use the OpenAI API
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${openaiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "Hello!" }],
}),
});
const data = await response.json();
return new Response(
JSON.stringify({
message: data.choices[0].message.content,
}),
{
headers: { "Content-Type": "application/json" },
}
);
}
MethodDescription
secrets.get(key)Get secret with fallback: user → system. Returns undefined.
secrets.getRequired(key)Same as get() but throws if not found.
secrets.getUser(key)Get user-specific secret only (no fallback).
secrets.getSystem(key)Get system-level secret only (no fallback).
TypeSet byScopeUse case
SystemAdmin (CLI)All functions, sharedShared client keys (Stripe, SendGrid)
UserUser (SDK)Per-user, privateUser’s own client keys (OpenAI, custom tokens)

When a function calls secrets.get("openai_api_key"):

  1. First checks for user-specific secret (if user is authenticated)
  2. Falls back to system secret if user secret not found
  3. Returns undefined if neither exists
  • Secret values are never logged or returned by the API
  • Secrets are encrypted at rest using the server’s encryption key
  • User secrets use per-user encryption (HKDF) - even admins can’t decrypt other users’ secrets
  • System secrets are admin-only and shared across all function executions

For direct HTTP access without the SDK, see the SDK Documentation.

Function not executing:

  • Verify function is enabled: enabled: true
  • Check function syntax (use Deno to validate TypeScript)
  • Review execution logs for errors

Timeout errors:

  • Increase timeout configuration
  • Optimize slow operations
  • Consider breaking into smaller functions

Permission errors:

  • Enable required permissions in function config
  • Check environment variable availability

Memory errors:

  • Increase memory allocation
  • Optimize data processing (stream large datasets)
  • Reduce in-memory caching

Edge functions can leverage AI capabilities via the utils.ai object for chat completions and embeddings.

Functions support a unified handler signature with SDK clients and utilities:

async function handler(
request: Request, // Web Request object
fluxbase: FluxbaseClient, // User-scoped client (respects RLS)
fluxbaseService: FluxbaseClient, // Service client (bypasses RLS)
utils: FunctionUtils // Utilities including AI
) {
// Use AI for intelligent processing
const response = await utils.ai.chat({
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Summarize this data..." }
]
});
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ summary: response.content }),
};
}
async function handler(req, fluxbase, fluxbaseService, utils) {
const { text } = JSON.parse(req.body || "{}");
// Use AI to analyze text
const response = await utils.ai.chat({
messages: [
{ role: "system", content: "Analyze the sentiment of the following text." },
{ role: "user", content: text }
],
maxTokens: 200,
temperature: 0.3
});
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
analysis: response.content,
model: response.model
}),
};
}
async function handler(req, fluxbase, fluxbaseService, utils) {
const { query } = JSON.parse(req.body || "{}");
// Generate embedding for semantic search
const { embedding } = await utils.ai.embed({ text: query });
// Use pgvector to find similar documents
const { data: results } = await fluxbaseService.rpc("match_documents", {
query_embedding: embedding,
match_threshold: 0.7,
match_count: 5
});
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ results }),
};
}
interface FunctionUtils {
// Report progress (0-100)
reportProgress(percent: number, message?: string, data?: any): void;
// Check if function was cancelled
isCancelled(): Promise<boolean>;
// Get execution context
getExecutionContext(): {
execution_id: string;
function_name: string;
namespace: string;
user: { id: string; email: string; role: string } | null;
};
// AI capabilities (requires network access)
ai: {
// Chat completion
chat(options: {
messages: Array<{ role: string; content: string }>;
provider?: string; // AI provider name
model?: string; // Model override
maxTokens?: number; // Default: 1024
temperature?: number; // 0-1, default: 0.7
}): Promise<{
content: string;
model: string;
finish_reason?: string;
usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
}>;
// Generate embeddings
embed(options: {
text: string;
provider?: string;
}): Promise<{
embedding: number[];
model: string;
}>;
// List available providers
listProviders(): Promise<{
providers: Array<{ name: string; type: string; model: string; enabled: boolean }>;
default: string;
}>;
};
}

Note: AI capabilities require network access. Ensure your function has allow_net: true configured.

Supabase Edge Functions use serve(), Fluxbase uses handler():

// Supabase
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
serve(async (req) => {
const data = await req.json();
return new Response(JSON.stringify(result), {
headers: { "Content-Type": "application/json" },
});
});
// Fluxbase equivalent
async function handler(req) {
const data = JSON.parse(req.body || "{}");
return {
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(result),
};
}