SaaS mode
The optional cms-saas module turns a multi-tenant dashboard into a sellable product: visitors sign up, pick a plan, pay through Stripe, and operate their own isolated workspace, while you run plans, subscriptions, invoices, and customers from the same admin. It builds directly on the multi-tenant primitives — tenant isolation, scoped models, invites, the platform owner — and adds the commercial layer on top.
Install and configure
cms-saas is a distributable DMS module (Distributable module): declare it in antelope.config.ts and its pages, tables, and routes register into the dashboard. The config is Stripe-first, and all three keys are required:
"cms-saas": {
source: { type: "package", package: "@antelopejs-private/cms-saas", version: "0.1.9" },
config: {
stripe: {
secretKey: "sk_…", // server-side Stripe API key
publishableKey: "pk_…", // forwarded to the frontend layer automatically
webhookSecret: "whsec_…", // verifies incoming Stripe webhook events
},
allowedRedirectHosts: ["localhost:3001"],
},
},
allowedRedirectHosts whitelists the hosts Stripe may redirect back to (the billing portal's return_url and other outbound redirects); values match against the URL host. Keep real Stripe keys out of the file by mapping environment variables onto these paths with the config's envOverrides block (Configuration).
The two roles
cms-saas formalizes the two roles a multi-tenant platform implies:
- The platform owner — you, the maker. A SaaS module joins the sidebar's Modules group (owner-only, like every module — Distributable module): the operational back-office for workspaces, users, plans, invoices, and the platform settings.
- Owners — your customers. Each signs up publicly, receives an isolated tenant, and manages it from a Workspace settings category: billing, plan, and data export.
How an owner signs up
The module ships the public registration screen and the legal pages behind it — privacy policy, terms and conditions, terms of use, stored as editable documents. Registration runs in three steps:
- The form collects the payment method through a Stripe SetupIntent.
- Submitting creates the user, the tenant, the Stripe customer, and the subscription in one pass — and rolls every step back if a later one fails.
- An existing user who logs in with no workspace arrives with a
requires_tenant_assignmenttoken (Built-in authentication) and finalizes a workspace through the same flow instead.
A plan can grant a free trial (trialDays). Trial consumption is recorded per payment-method identity, so the same card does not receive a second trial.
Plans gate permissions
A plan is a record you manage from the SaaS module. It carries the commercial terms — price, currency, interval (month/year), billing mode (flat or per-seat, seat billing starting at one seat), an audience (any, individual, business), feature values — and can inherit from another plan. The integration point is its list of permission ids: a registered permissions resolver intersects each user's effective permissions with their tenant's plan.
Because the effective set feeds every permission surface — route guards, the sidebar, page layouts, table-view actions — a permission outside the plan both disappears from the dashboard and answers 403 on its routes, even when the user's role grants it.
Suspension and the access gate
Plan intersection shapes which features a workspace gets; a blocking subscription status cuts the workspace off entirely. The module registers a tenant access gate that denies every workspace whose subscription is pending_payment, suspended, or cancelled — all tenant authorization surfaces then answer 403 with the code saas.errors.workspace.access_blocked.
What a member of a blocked workspace experiences:
- They are redirected to the
/workspace-suspendedscreen, which explains the status. The workspace owner gets the Stripe customer-portal button there; other members are told to contact the owner. - The billing surfaces bypass the gate on purpose — they are the recovery path a blocked workspace uses to regularize.
- Once the subscription is regularized, the suspended screen's re-check sends the user back into the dashboard.
Auth flows, registration, and the legal pages remain reachable throughout — the gate denies the tenant's product surfaces, not the session.
Billing operations and housekeeping
Owners manage their own billing through Stripe's customer portal, opened from the Workspace billing page. The data-export page drives the TENANT_DATA_EXPORT hook — register a handler to include your module's tenant data in the ZIP archive, JSON payloads as well as heavy files and streamed dumps (Tenant data export archive).
The module also runs its own background jobs: past-due subscriptions are suspended (which trips the access gate), free workspaces expire (with an ending-soon notification beforehand), cancelled workspaces are eventually hard-deleted, billing state and segments are recomputed on schedule, and processed Stripe webhook events are cleaned up.