Installation
dms-saas runs inside a project that loads @antelopejs/dms in multi-tenant mode, a database module, an e-mail module and a file storage.
Requirements
| Package | Version |
|---|---|
@antelopejs/dms | >=0.9.0 <1.0.0 |
@antelopejs/interface-dms | >=0.7.0 <1.0.0 |
@antelopejs/dms-frontend (the renderer) | 0.6 |
Add it to a project
Add the module
ajs project modules add @antelopejs/dms-saas
Configure Stripe
Give the module your Stripe keys, from the environment rather than committed values:
export default defineConfig({
name: "my-saas",
envOverrides: {
STRIPE_SECRET_KEY: "modules.dms-saas.config.stripe.secretKey",
STRIPE_PUBLISHABLE_KEY: "modules.dms-saas.config.stripe.publishableKey",
STRIPE_WEBHOOK_SECRET: "modules.dms-saas.config.stripe.webhookSecret",
},
modules: {
"dms-saas": {
source: { type: "package", package: "@antelopejs/dms-saas", version: "^0.6.0" },
config: {
stripe: {
secretKey: "sk_test_placeholder",
publishableKey: "pk_test_placeholder",
webhookSecret: "whsec_placeholder",
},
allowedRedirectHosts: ["app.example.com"],
},
},
// dms, api, mongodb, auth-jwt, nodemailer, file-storage-local, ...
},
});
With placeholder keys the module still runs: plan syncs are skipped and Stripe-backed blocks say so.
Point Stripe's webhook at the module
Create a webhook endpoint in the Stripe Dashboard targeting https://<api>/api/saas/webhooks/stripe, with the API version 2026-08-26.dahlia, and put its signing secret in STRIPE_WEBHOOK_SECRET. See Stripe.
Let the frontend server complete sign-ups
The auth/no-workspace completion and OAuth sign-ups log the visitor in through the frontend loader's POST /auth/establish route, which only calls endpoints the deployment names. Declare the finalize endpoint in the frontend server's environment:
DMS_AUTH_ESTABLISH_ENDPOINTS=/api/saas/register/finalize
Publish a free plan
Registration opens every workspace on the catalogue's first free plan. Create one in Modules › SaaS › Plans, or seed it from your module. Until a free plan exists, registration answers 409 saas.errors.plan.no_free_plan.
stripe.readTimeoutMs (4 s by default), and the past-due and trial-ending strips of the layout read Stripe in the browser, so a slow Stripe never holds a page. Dialogs and drawers read their data when they open.Try it in the playground
cd packages/dms-saas
pnpm install && pnpm --dir frontend-vue install && pnpm build
pnpm dev # backend on http://localhost:5010
pnpm frontend:dev # dashboard on http://localhost:3001
The playground connects to MongoDB at mongodb://localhost:27017 (set MONGO_URL in the environment or in playground/.env). Stripe keys and the GitHub and Google OAuth credentials are read from playground/.env, never committed; see playground/.env.example. A provider's sign-in button appears once both its client id and secret are set, with http://localhost:3001/auth/oauth/<provider>/callback as its callback URL.
Playground data set
On its first start against an empty database, the playground writes a data set to review every screen. Sign in as [email protected] with the password Playground-Admin-1!: a platform admin who also owns the acme workspace. Every other seeded account (platform admins [email protected] and [email protected], workspace owners and members) uses the same password.
It holds the feature and plan catalogue (Free, Starter, Pro, Team, Business per seat, Enterprise yearly and hidden, Growth 2024 retired), thirteen workspaces in every billing state (active, trialing, free, complimentary with and without an end date, past due, pending payment, suspended, cancelled) with their members and invitations, invoices in every status, credit notes, plan migrations, segments, internal notes, billing rules and legal documents. Dates are relative to the first start, and Stripe ids are fake: nothing calls Stripe for them.
The seed lives in playground/src/seed and runs once; drop the database to start over. Segment counts appear after the first recompute (every 15 minutes), and the running plan migration turns to "reconciliation required" on the next restart, as an interrupted migration does.