Storage Scoping
Each table in your extension's schema can be scoped to control which installations share the same data. The scope is declared with .storage(scope) on defineTable.
Scopes
| Scope | Rows shared by | Use case |
|---|---|---|
"instance" | One specific overlay install | Per-install rules, settings, state |
"overlay" | All installs on the same overlay | Shared overlay leaderboard |
"account" | All installs across all overlays in one account | Account-wide vote history |
"global" | All installs of this extension across all accounts | Global community leaderboard |
Example
// server/schema.ts
import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";
export default defineSchema({
// One set of rules per overlay install
rules: defineTable({
text: v.string(),
revealed: v.boolean(),
}).storage("instance"),
// Votes shared across all overlays in the account
votes: defineTable({
choice: v.string(),
userId: v.string(),
}).storage("account"),
// Global leaderboard shared across ALL streamers using this extension
globalLeaderboard: defineTable({
accountId: v.string(),
score: v.number(),
userName: v.string(),
}).storage("global"),
});
How scoping works
The scope is implemented via a scope_key column added automatically to each table:
| Scope | scope_key value |
|---|---|
"instance" | install_id (UUID of this specific install) |
"overlay" | overlay_id (UUID of the overlay) |
"account" | account_id (UUID of the account) |
"global" | extension_id (UUID of the extension itself) |
All declarative functions (queryRows, insertRow, etc.) and ctx.db operations automatically filter by the correct scope_key based on the calling installation's context. You never need to filter by scope manually.
Choosing the right scope
"instance" (most common)
Use when each overlay install should have completely independent data:
// Each overlay the streamer creates has its own rules list
rules: defineTable({ text: v.string() }).storage("instance")
"overlay"
Use when multiple extensions installed on the same overlay should share data (rare):
// All extensions on "Gaming Overlay 1" share this chat log
chatLog: defineTable({ message: v.string() }).storage("overlay")
"account"
Use when data should persist across all overlays a streamer creates:
// Song requests appear on all overlays for this streamer
songRequests: defineTable({ title: v.string(), artist: v.string() }).storage("account")
"global"
Use for community features across all streamers:
// Global trivia high scores across all Lumio streamers
highScores: defineTable({
accountId: v.string(),
userName: v.string(),
score: v.number(),
}).storage("global")
"global" scope means any installation can read and write this data. Use it only for truly public, community data. Never store sensitive or per-streamer data in global scope.
Mixing scopes in one schema
You can mix scopes freely within one schema:
export default defineSchema({
rules: defineTable({ text: v.string() }).storage("instance"), // per install
votes: defineTable({ choice: v.string() }).storage("account"), // per account
topPlays: defineTable({ score: v.number() }).storage("global"), // global
});