Skip to main content

Secrets

Secrets let you store sensitive values — API keys, tokens, passwords — that your extension needs to call external services. They are stored encrypted in the Lumio database and injected into ctx.secrets at runtime.

Adding a secret

Secrets are managed in the extension dashboard under Settings → Secrets, or via the CLI:

lumio env set MY_API_KEY "sk-abc123..."

Secrets are stored with AES-256-GCM encryption. Only your extension's server functions can read them — Lumio staff cannot view the plaintext values.

Using secrets in action functions

// server/functions.ts
import { action, v } from "@zaflun/lumio-sdk/server";

export const callExternalApi = action({
args: { query: v.string() },
handler: async (ctx, args) => {
// Read the secret by name
const apiKey = ctx.secrets.get("MY_API_KEY");

if (!apiKey) {
throw new Error("MY_API_KEY is not configured. Add it in the extension dashboard.");
}

const res = await ctx.fetch("https://api.example.com/search", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: args.query }),
});

return res.json();
},
});

ctx.secrets.get(name)

ParameterTypeDescription
namestringThe secret name as set in the dashboard or CLI
Returnsstring | nullThe secret value, or null if not set

Always handle the null case — if a secret is not configured, throw a helpful error rather than failing silently.

Managing secrets via CLI

# Set a secret
lumio env set API_KEY "your-api-key-here"

# List secret names (values are never shown)
lumio env list

# Delete a secret
lumio env delete API_KEY

Managing secrets via dashboard

Navigate to Dashboard → Extensions → {your extension} → Settings → Secrets:

  1. Click Add secret
  2. Enter the secret name (e.g. ESPN_API_KEY)
  3. Enter the secret value
  4. Click Save

The secret is available to your extension immediately — no redeploy required.

Security model

PropertyValue
EncryptionAES-256-GCM with per-secret keys
StoragePostgreSQL ext_secrets table, values never in plaintext
AccessOnly ctx.secrets in action handlers — never in client code
ScopingSecrets are per-extension per-environment (staging vs production)
AuditEvery ctx.secrets.get() call is logged in the extension audit log

Never expose secrets client-side

Secrets are only available in server functions. Never pass a secret back to the client in a function's return value:

// WRONG: never return secret values
export const badAction = action({
args: {},
handler: async (ctx) => {
const key = ctx.secrets.get("API_KEY");
return { apiKey: key }; // exposes the key to the browser!
},
});

// Correct: use the secret server-side only
export const goodAction = action({
args: { query: v.string() },
handler: async (ctx, args) => {
const key = ctx.secrets.get("API_KEY");
const res = await ctx.fetch(`https://api.example.com/q=${args.query}`, {
headers: { "Authorization": `Bearer ${key}` },
});
return res.json(); // return API result, not the key
},
});