| title | Satellites |
|---|---|
| description | Ten opt-in features attached to tenants; audit logs, feature flags, webhooks, branding, SSO, real-time WebSockets, metrics, quotas, impersonation, Stripe billing. |
# Selective at install (core leaf satellites)
node ace configure @adonisjs-lasagna/saas-tenancy --with=audit,webhooks
# Add a new one later — re-run with the satellite list you want
node ace configure @adonisjs-lasagna/saas-tenancy --with=audit,webhooks,metrics
# List what's installable (core bundles + any packaged satellites you have installed)
node ace configure @adonisjs-lasagna/saas-tenancy --list-satellitesPackaged satellites (billing, SSO, and any community package) carry their own migrations. Install them through core by package name, or run their own configure hook:
node ace configure @adonisjs-lasagna/saas-tenancy --with=@adonisjs-lasagna/billing
# equivalently
node ace configure @adonisjs-lasagna/billingThe configure command is idempotent; re-running it does not duplicate migrations or tenant model scaffolding. For the full step-by-step of adding a satellite to a running app (packages, config blocks, recovery), see Adding features later. To build your own, see Creating a satellite.
| Satellite | What it gives you | Storage |
|---|---|---|
| Audit | Structured audit trail with actor + payload, queryable by date range. | tenant_audit_logs |
| Feature flags | Per-tenant boolean flags (kill switches, beta cohorts), cached. | tenant_feature_flags |
| Webhooks | HMAC-signed outbound events with delivery state machine and retries. | tenant_webhooks, tenant_webhook_deliveries |
| Branding | Per-tenant logo, colors, custom domain, encrypted SMTP. | tenant_brandings |
| SSO | Per-tenant OIDC config with JWKS-backed verification. | tenant_sso_configs |
| WebSockets | Bidirectional socket.io, tenant-isolated per connection; per-tenant rooms + per-event tenant context. | None (stateless) |
| Metrics | Time-series counters per tenant with cursor-based aggregation. | tenant_metrics |
| Quotas | Plan-bound limits; rolling and snapshot, served as middleware. | Redis counters + tenant_plans |
| Billing | Multi-provider (Stripe/Paddle/Lemon Squeezy) — idempotent webhook, dunning, metered, checkout/portal, lifecycle hook. Provider-driven, reporting-only (no tax engine / invoice numbering). | billing_customers, billing_subscriptions, billing_processed_events, billing_usage_events |
| Impersonation | Admin enters a tenant as a target user, time-boxed and audited. | Redis (no DB row) |
- Every satellite that writes to a database table goes through the
backofficeschema; never the per-tenant schema. This makes cross-tenant reporting and aggregate queries straightforward, and it means removing a satellite is a singlemigration:rollbackof its backoffice tables, with nothing left behind in any per-tenant schema. - The audit satellite is the single point of truth for "who did
what": impersonation writes its rows automatically, and every other
satellite's mutations can be recorded through the same
AuditLogService.log()API from your hooks and listeners (see Audit logs). - Satellites never call each other directly; they go through their respective service contracts. Replace one and the rest keep working.
For how the satellite lifecycle behaves under failure (a provider that throws at boot, a fail-closed tenant destroy, a migration that fails mid-batch, the read-only uninstall), see Satellite lifecycle: failure modes and recovery.
Satellites are a public extension point. The billing, SSO, and WebSockets
satellites ship as their own packages (@adonisjs-lasagna/billing,
@adonisjs-lasagna/sso, @adonisjs-lasagna/websockets): each carries its own
configure hook (plus migrations when it has tables, though WebSockets is stateless
and ships none), and registers itself through core's public registries without core
ever importing it. You can publish your
own the same way and have it discovered by configure --list-satellites and
installed with --with=<package>. See
Creating a satellite.
Pick a satellite from the table above, or look at the admin REST API for the HTTP surface they share.