Skip to main content

Auth and Scopes

Server functions can inspect the identity of the caller and restrict access based on surface, role, or authentication status.

ctx.auth​

Available in all handler-based functions (query(), mutation(), action()):

export const myMutation = mutation({
args: { text: v.string() },
handler: async (ctx, args) => {
const { userId, accountId, role, surface } = ctx.auth;

if (!userId) {
throw new Error("Authentication required");
}

if (role !== "owner" && role !== "editor") {
throw new Error("Only editors can perform this action");
}

// ... proceed
},
});

ctx.auth properties​

PropertyTypeDescription
userIdstring | nullLumio user ID of the dashboard user, or null on the layer
accountIdstringThe Lumio account that owns this installation
role"owner" | "editor" | "viewer"Caller's role in the account
surface"editor" | "layer"Which surface made this request
platformUserIdstring | nullPlatform user ID if viewer is connected
platformUserNamestring | nullPlatform username if viewer is connected

editorOnly for declarative functions​

For declarative functions, use the editorOnly option instead of ctx.auth:

// Only the editor surface can call this
export const deleteRule = deleteRow("rules", arg("id"), { editorOnly: true });

// Any surface can call this
export const getRules = queryRows("rules");

editorOnly: true is equivalent to checking ctx.auth.surface === "editor" in a handler function. Use it when:

  • The operation is destructive (delete, reset)
  • The operation changes configuration (only the streamer/editor should control this)
  • The overlay or widget running in OBS must not be able to trigger it

How Lumio decides the surface​

Your extension cannot claim a surface. The editorOnly flag comes from the definition in your published functions.json, and the surface comes from the credential of the host that runs your extension:

HostCredentialSurfaceeditorOnly functions
Lumio dashboard (editor panel, previews)the streamer's sessioneditorallowed
OBS overlay (/overlay/…)the overlay's own tokenlayerrefused
OBS widget (/widget/…)the widget's own tokenlayerrefused

A refused call rejects with Editor-only function called outside editor (JSON-RPC code DENIED, see useMutation).

Surface-based restrictions​

In handler functions, use ctx.auth.surface to apply different logic per surface:

export const addItem = mutation({
args: { text: v.string() },
handler: async (ctx, args) => {
if (ctx.auth.surface === "layer") {
throw new Error("Cannot write from the layer surface");
}

await ctx.db.insert("items", {
text: args.text,
userId: ctx.auth.userId,
submittedFrom: ctx.auth.surface,
});
},
});

Viewers​

Viewers never call server functions: Lumio has no viewer page and no viewer identity. Viewer input arrives as chat commands and channel point rewards, which a bot-module handler receives with the chat sender in ctx.user. See Viewer Input.

Role-based access​

Use ctx.auth.role to restrict operations to specific account roles:

export const resetAll = mutation({
args: {},
handler: async (ctx) => {
if (ctx.auth.role !== "owner") {
throw new Error("Only the account owner can reset all data");
}

// Perform destructive reset...
},
});

Summary​

Restriction typeMechanism
Editor surface only{ editorOnly: true } option on declarative functions
Surface check in handlerctx.auth.surface === "editor"
Authentication requiredif (!ctx.auth.userId) throw ...
Role checkif (ctx.auth.role !== "owner") throw ...
Platform identityctx.auth.platformUserId / ctx.auth.platformUserName