Overview
There are three types of server functions:
All functions receive a typed context object and validated arguments.
Declarative queries (built with
queryRows) support real-time subscriptions — when data changes, connected clients update automatically.
Handler-based queries (built with query()) work for on-demand fetching but do not support real-time subscriptions yet.
Getting Started
Scaffold with the CLI
When you runvision init, select “Include server functions” when prompted. This creates a server/ directory with starter files:
server/ directory manually and setting "server": true in your vision.config.json.
Imports
Server functions and schema utilities are imported from@1upvision/sdk/server:
Schema
Define your database tables inserver/schema.ts. Each table has typed fields and optional indexes.
Supported Field Types
Any validator can be made optional by chaining
.optional():
Indexes
Add indexes to tables for efficient querying. An index references one or more columns:Storage Scopes
By default, table data is scoped to the account (channel). You can change how data is partitioned by setting the storage scope:
Set the storage scope via the options object or the chainable
.storage() method:
When you change a table’s storage scope, existing data will not be
automatically migrated. The runtime uses a new partition key, so data written
under the previous scope will no longer be visible.
Declarative Functions
The simplest way to define server functions. Instead of writing handler logic, you describe what you want and the runtime executes it. Declarative queries also support real-time subscriptions — connected clients update automatically when data changes.queryRows — Read from a table
useQuery:
FilterDefinition[]
Array of field/operator/value conditions. See Filter syntax
below.
number
Maximum rows to return.
number
Skip rows (for pagination).
boolean
Restrict to channel editors/owner.
insertRow — Create a row
useMutation:
patchRow — Update a row
useMutation:
deleteRow — Delete a row
useMutation:
fetchAction — Outbound HTTP request
Make external API calls from the server. Requires configuring an egress allowlist.useMutation:
string | arg() | secret()
required
The URL to fetch. Can be a literal string,
arg("paramName"), or built from a
secret.string
HTTP method. Defaults to
"GET".Record<string, string | arg() | secret()>
Request headers.
string | arg()
Request body.
'json' | 'text' | 'none'
How to parse the response. Defaults to
"json".Value Expressions
Dynamic values that are resolved at runtime:
Common identity keys:
"subject", "channelId", "extensionId", "installationId", "overlayId", "layerId".
Filter Syntax
Usefilter() to build query conditions for queryRows:
"eq", "neq", "lt", "lte", "gt", "gte", "in"
Declarative vs Handler-based
You can mix both styles in the same
server/functions.ts file.
Egress Allowlist
fetchAction can only reach hosts on your extension’s allowlist. Configure it in vision.config.json:
vision dev or vision deploy.
Complete Declarative Example
Handler-based Functions
For complex logic that can’t be expressed declaratively — conditional writes, multi-step operations, or custom validation — usequery(), mutation(), and action() with handler functions.
Queries
Queries read data from the database. They receive aQueryContext with a read-only db and auth API.
For simple reads, prefer declarative
queryRows — it’s less code and supports
real-time subscriptions. Use handler-based queries when you need custom logic.Querying with Filters
Querying with Indexes
Database Reader API
Query Request Options
Filter Operators
eq, neq, lt, lte, gt, gte, in
Mutations
Mutations read and write data. They receive aMutationContext with the full db API. For simple inserts, updates, and deletes, prefer the declarative builders instead.
Database Writer API
Extends the reader API with write operations:Actions
Actions have full database access plus the ability to make HTTP requests and read secrets. For simple HTTP requests without custom logic, you can use the declarativefetchAction instead. Use handler-based actions for complex flows — conditional logic, multiple API calls, or processing responses before storing data.
Action Context
Actions receive everything mutations get, plus:Auth
Every server function context includes anauth API for identity and authorization:
Auth API
UserIdentity
Scoping Functions
Restrict a function to callers with specific scopes:Editor-Only Functions
Restrict a function so only verified editors of the channel (users with an assigned role) and the channel owner can invoke it. All other callers receive a 403 Forbidden response.editorOnly is enforced at both the API layer and the edge runtime. Even if
the caller has valid authentication, they must be a verified editor of the
channel to invoke editor-only functions.editorOnly with scope for additional restrictions:
Argument Validation
All function arguments are validated at runtime using the samev validators used in schemas. Invalid arguments are rejected before the handler runs.
CLI Commands
vision dev
Watches your project, builds client and server bundles, pushes the schema, and uploads to the dev environment on every change.
vision run
Execute a server function from the terminal during development:
vision deploy
Build and deploy a production version:
Environment Variables (Secrets)
Secrets are managed in the extension dashboard under Settings > Environment. They are encrypted at rest and only available toaction functions via ctx.secrets.get().
Secrets are server-only. They can only be accessed in
action functions
via ctx.secrets.get() and are never exposed to client-side code.Managing Secrets
Use the Environment page in the extension settings sidebar to:- Add new secrets with uppercase keys (e.g.,
DISCORD_WEBHOOK_URL,API_TOKEN) - Update existing secret values
- Delete secrets that are no longer needed
^[A-Z0-9_]+$ — uppercase letters, numbers, and underscores only.
How It Works
Each extension installation gets its own isolated database at the edge. Data partitioning is handled by the storage scope configured on each table. When a server function is invoked:- The client request is authenticated and scoped to the extension and channel
- The function executes in an isolated runtime with access only to its own database
- Results are returned to the client