Guide: TaskFlow — Multi-Tenant SaaS Config
This guide walks through a real-world SaaS application (TaskFlow — a task management platform) that uses Orbseal to manage per-tenant configuration, per-environment infrastructure, and encrypted secrets.
The problem
TaskFlow is a multi-tenant SaaS. Each customer (workspace) has different limits and branding. Each deployment environment (production, staging) has different database URLs and secrets. Individual users can override their own preferences (theme, language).
Without Orbseal, this typically means:
- A
configtable in the database with ad-hoc columns - Secrets hardcoded in environment variables per-deploy
- No audit trail for who changed what
- No type safety or schema validation for config keys
With Orbseal, all of this is structured, versioned, and encrypted.
Schema
# orb.yaml
plugin: taskflow
definitions:
# ── User preferences (each user sets their own) ───────────────────────────
theme:
type: enum
values: [light, dark, system]
scope: user
default: system
overridable: true
label: Theme
component: select
language:
type: enum
values: [en, tr, de, fr, es]
scope: user
default: en
overridable: true
label: Language
component: select
# ── Team / Workspace settings ─────────────────────────────────────────────
max_seats:
type: number
scope: workspace
default: 5
label: Max Team Seats
allowed_domains:
type: json
scope: workspace
default: []
label: Allowed Email Domains
# ── App behavior (project scope) ─────────────────────────────────────────
default_view:
type: enum
values: [list, board, calendar, timeline]
scope: project
default: list
overridable: true
label: Default View
component: select
features:
type: json
scope: project
default: { "ai_assist": false, "recurring_tasks": true }
label: Feature Flags
# ── Infrastructure (environment scope) ───────────────────────────────────
app_url:
type: string
scope: environment
required: true
label: App URL
cdn_url:
type: string
scope: environment
default: "https://cdn.taskflow.app"
label: CDN URL
# ── Secrets (E2E encrypted) ───────────────────────────────────────────────
database_url:
type: secret
scope: environment
required: true
label: Database URL
stripe_secret:
type: secret
scope: environment
required: true
label: Stripe Secret Key
Setup
# Create workspace and project
npx orbseal workspaces create "Platform"
npx orbseal use acme/platform
npx orbseal projects create "TaskFlow"
npx orbseal use acme/platform/taskflow
# Create environments
npx orbseal envs create "Production"
npx orbseal envs create "Staging"
# Sync schema
npx orbseal sync
Set values
# Workspace-level defaults (apply to all projects)
npx orbseal values set taskflow:max_seats 10 \
--scope workspace --ref default
# Production environment
npx orbseal values set taskflow:app_url https://app.taskflow.io \
--scope environment --ref production
npx orbseal values set taskflow:cdn_url https://cdn.taskflow.io \
--scope environment --ref production
# Staging environment
npx orbseal values set taskflow:app_url https://staging.taskflow.io \
--scope environment --ref staging
# Feature flags (project-wide)
npx orbseal values set taskflow:features \
'{"ai_assist":true,"recurring_tasks":true}' \
--scope project --ref taskflow
Seal secrets
# Generate keypair once per app
npx orbseal keygen
# Save the recovery phrase offline
# Register the public key
npx orbseal use --public-key "orbpk-..."
npx orbseal keys create "prod-worker" --env production
# Seal secrets (plaintext never leaves your terminal)
npx orbseal seal taskflow:database_url --scope environment --ref production
npx orbseal seal taskflow:stripe_secret --scope environment --ref production
Cut a release
npx orbseal use acme/platform/taskflow/production
npx orbseal release
✓ Release v1 etag: 9e691b38a0b25e68
config keys 10
secret keys 2
Multi-tenant: per-customer workspaces
When a new customer signs up, create a workspace for them:
npx orbseal workspaces create "Acme Corp"
# → workspace: acme-corp (id: r7kvmx)
They get their own isolated config space. You can set different max_seats or allowed_domains per customer workspace without touching anyone else's config.
npx orbseal values set taskflow:max_seats 50 \
--scope workspace --ref acme-corp
Resolve at runtime
// Your app server
const ORBSEAL_TOKEN = process.env.ORBSEAL_TOKEN; // orb_live_...
const ORBSEAL_PK = process.env.ORBSEAL_PUBLIC_KEY;
const ORBSEAL_SK = process.env.ORBSEAL_PRIVATE_KEY;
async function getConfig(userId) {
const res = await fetch(
`https://api.orbseal.com/v1/config/resolve?user=${userId}`,
{ headers: { Authorization: `Bearer ${ORBSEAL_TOKEN}` } }
);
const { config, secrets, etag } = await res.json();
// Decrypt secrets
const dbUrl = decrypt(secrets['taskflow:database_url'], ORBSEAL_PK, ORBSEAL_SK);
return { config, dbUrl, etag };
}
The etag can be stored in a cache (Redis, memory) and compared on subsequent requests. If the etag hasn't changed, skip the resolve call and return the cached config.
Settings UI
Map each setting panel to an Orbseal scope:
| Settings panel | Scope | Who controls it |
|---|---|---|
| Appearance (theme, language) | user |
User themselves |
| Team (seats, domains) | workspace |
Workspace admin |
| App behavior (views, features) | project |
Developer / product |
| Infrastructure (URLs) | environment |
DevOps |
| Secrets (DB, API keys) | environment |
Developer (sealed) |
Your settings UI can call /v1/config/user-values (via app key) to read and write user overrides live, without needing a release.