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)
| Parameter | Type | Description |
|---|---|---|
name | string | The secret name as set in the dashboard or CLI |
| Returns | string | null | The 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:
- Click Add secret
- Enter the secret name (e.g.
ESPN_API_KEY) - Enter the secret value
- Click Save
The secret is available to your extension immediately — no redeploy required.
Security model
| Property | Value |
|---|---|
| Encryption | AES-256-GCM with per-secret keys |
| Storage | PostgreSQL ext_secrets table, values never in plaintext |
| Access | Only ctx.secrets in action handlers — never in client code |
| Scoping | Secrets are per-extension per-environment (staging vs production) |
| Audit | Every 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
},
});