Skip to main content

Server Functions

Server functions are optional server-side logic that runs on the Lumio API or in an isolated worker. They let extensions persist structured data, query databases, and use secrets. Handler-based server functions cannot call external APIs; use a bot module for outbound requests.

Enabling server functions​

Add "server": true to lumio.config.json:

{
"server": true
}

This enables the server/ directory and activates the server runtime for your extension.

Two styles​

StyleUse caseOverhead
DeclarativeSimple CRUD on tables (queryRows, insertRow, patchRow, deleteRow)Minimal - transpiles to parameterized SQL, no JS runtime
Handler-basedComplex logic, conditional writes (query(), mutation(), action())Low - runs in a V8 isolate on the Bot Module Worker with memory and time limits

Execution flow​

Extension Client (browser)
-> useMutation("addRule") / useQuery("getRules") sends only { fn, params }
-> postMessage to Lumio host (dashboard, OBS overlay or widget)
-> host forwards with its own credential (session, or the overlay/widget token)
-> Lumio API: POST /v1/extension-installs/{install_id}/{query|mutate}/{functionName}
-> caller + surface from the credential, rate limit check
-> definition looked up by name in the install's published functions.json
-> editorOnly check (definition), arg() values bound as typed parameters
-> Declarative: typed, parameterized SQL -> PostgreSQL (ext_{extension_id}.*)
-> Handler: the API re-runs the call as /execute/{functionName}; the
Bot Module Worker runs the published server.js in a V8 isolate
-> ctx.db / ctx.cache / ctx.secrets
-> Response serialized to JSON
-> postMessage back to extension

Your extension never sends a table name, a filter or an editorOnly flag: the API takes all of that from the functions.json that lumio build wrote and lumio deploy uploaded with the version. Calling a name the installed version does not export rejects with Unknown server function: <name>.

Rate limits​

Function typeLimitApplies to
queryRows100 calls/minutePer installation, shared with the bot module's ctx.db.get/list
insertRow / patchRow / deleteRow200 calls/minutePer installation, shared with the bot module's ctx.db.insert/patch/delete
query()100 calls/minutePer installation, shared with queryRows
mutation()50 calls/minutePer installation
action()20 calls/minutePer installation

Database isolation​

Each extension gets its own isolated PostgreSQL schema: ext_\{extension_id\}. Tables defined in server/schema.ts live in this schema:

ext_88b2478a_39c7_468e_bebd_14e52fd97ac0.rules
ext_88b2478a_39c7_468e_bebd_14e52fd97ac0.votes

No extension can read or write another extension's tables, and every row is scoped to the account of the install (see Storage scoping). The Lumio API enforces this twice: every statement it generates carries the install's account, and the database itself applies row-level security on every extension table, so a row of another account is neither visible nor writable. Server functions run as a database role that can only read and write rows; it cannot create, drop or truncate tables.

Declarative queries return at most 1000 rows (100 when you set no limit), in created_at order unless the definition sets orderBy, and are cancelled after 2 seconds. When the rate-limit store is unreachable, reads still run and writes are refused (503).

File structure​

server/
├── schema.ts <- Table definitions (defineSchema + defineTable)
└── functions.ts <- Function exports (queryRows, action, etc.)

Both files are optional. An extension can have server: true but only use schema for useExtensionStorage() type safety, without defining custom functions.

When to use server functions​

ScenarioRecommendation
Simple toggle / text settinguseExtensionStorage() - no server needed
List of items (rules, songs, votes)Declarative server functions + defineTable
Aggregations (count, sum, averages)Handler-based query()
External API calls (sports scores, weather)Bot-module handler with declared egress hosts
User-specific data (per-viewer state)Handler-based with ctx.auth.userId
Secrets (API keys)Handler-based action() with ctx.secrets