Download all docs

Recipe: Multi-Tenant SaaS Skeleton

This recipe lays out the bones of a multi-tenant SaaS: a parent circle that owns the product, a circle-ref per customer pointing at their isolated sub-circle, an auth-policy governing who gets in, and an api-token per tenant for programmatic access. Each customer gets a sovereign namespace; you get one product to run.

The problem it solves

Multi-tenancy is where SaaS projects get dangerous: one customer’s data leaking into another’s is an existential bug, and bolting isolation onto a shared database after the fact is painful and fragile. This recipe starts from isolation — every tenant is a separate circle with its own schema — so data separation is structural, not a WHERE tenant_id = ? clause you have to remember everywhere.

Elements

ElementRole
circleThe parent product circle, and (per tenant) an isolated sub-circle.
circle-refA reference tile pointing at a tenant’s nested sub-circle.
auth-policyDeclares authentication requirements and access rules.
api-tokenA scoped credential per tenant for programmatic access.

Flow

  1. Start with your product circle — the tenant that owns the shared product surface.
  2. Onboard each customer as a sub-circle. The parent circle’s add_subcircle operation creates a new circle nested under it; because every circle carries its own circle_{uuid} Postgres schema, that tenant’s data is physically isolated from every other tenant by construction. A circle-ref tile points at it, and its resolve operation returns the child’s public metadata (name, type, created_at) without entering the child’s schema.
  3. Govern access with an auth-policy. Its evaluate operation tests credentials (role, action, token) against the policy’s rules — attach it to the surfaces that need gating so the same access logic applies everywhere.
  4. Issue per-tenant programmatic access with an api-token: generate a scoped token, define what it can touch with scopes, and rotate or revoke it over the customer’s lifecycle.
  5. Build the product features flat inside each tenant’s sub-circle; the parent circle holds only what is shared.

What this shows

In Triform, a tenant is a circle — its own schema, git repo, and wallet — so multi-tenant isolation is the platform’s default shape, not a discipline you impose. add_subcircle plus a circle-ref gives you a clean parent→tenant tree where the parent can see public metadata (resolve) without reaching into a child’s private data. Access and credentials are themselves elements — an auth-policy you attach and an api-token you scope per tenant — so the security model is declared and inspectable, not scattered through application code.

Next pages