Case Study - Multi-Tenant SaaS Template (Remix, RBAC & Billing)
A reusable SaaS foundation with authentication, role-based access control, billing, and audit logging. Built to handle the boring but critical parts of production SaaS correctly from day one.
- Date
- Roles
- Developer,
- Architect
- Tech
- Remix,
- PostgreSQL,
- Stripe,
- TypeScript,
- Row-Level Security

Problem Context
Not gunna lie, I just wanted to build this out myself and learn the ins-and-outs of building a multi-tenant SaaS product from scratch. I've built a few SaaS products before and have reused a lot of the code from those projects over the years. This is something I hope to opensource and improve on over time. Plus, obviously use it as a very quick starter for my own projects.
Product & Architecture Approach
The template is built around a simple principle: Be flexible where products differ, and strict where correctness matters.
As a result, the system is opinionated about access control, billing state, and security, but intentionally unopinionated about marketing pages, copy, or visual branding.
The marketing surface is minimal by design. It's trivial to scaffold a landing page, but hard to do proper RBAC, auth / onboarding flows, or billing correctness. The template optimizes for the latter.
What I Built
At a system level, the template provides: A production-ready authenticated application shell, multi-user access within a tenant boundary (I am going to add organization switching as an addon if desired), configurable RBAC enforced at both application (custom middleware) and database layers (RLS), subscription billing with Stripe integration, core governance flows (invitations, ownership transfer, MFA), notifaction system, and audit logging for sensitive system events.
Note... I may make this template private soon and work on it a bit more before opening it up again.
Tenancy Model
The current tenancy model is: A single account boundary, multiple users per account, access controlled via role-based access control, and enforcement backed by Postgres Row-Level Security (RLS).
This keeps tenant boundaries explicit and enforceable, while supporting real SaaS requirements such as multiple roles, invitations, and administrative control.
The next step is to introduce an organizations table as a parent entity, allowing a single user identity to belong to multiple organizations, clean organization switching within the same account, and shared infrastructure without duplicating users. This evolution preserves the existing model while enabling more complex SaaS scenarios.
This is a 'shared db and schema with a tenantID as a foreign key' approach... I get that for applications requiring a high degree of isolation perhaps a schema-per-tenant approach is the more appropriate, but for most appliacations I think this is a good compromise between isolation and complexity.
Billing & Subscription Design
The billing model is intentionally simple and extensible: Freemium (restrictive), Basic, and Pro. Pricing mechanics themselves are not hardcoded beyond this structure; the goal is to provide a framework that supports common SaaS pricing models without locking developers into a specific strategy.
Stripe is used as the source of truth for subscription state. The application maintains mirrored status in its own database to support authorization checks and UI state.
A webhook endpoint is exposed to receive Stripe events: Signature verification is performed using Stripe's signing secret, events are acknowledged immediately, processing is handled asynchronously, all events are logged, and idempotency checks prevent duplicate processing. This ensures billing state remains consistent without blocking or reprocessing events under retry conditions.
RBAC Model
Authorization is a first-class concern in the template. Roles are configurable, with sensible defaults: Owner (immutable, full access), Admin, and User. Role hierarchy is enforced: Users can only manage roles below their own level, Admins cannot modify other Admins, and the Owner role cannot be modified or removed.
If additional privilege tiers are needed, the Owner can create roles above Admin explicitly.
Permissions are defined at the resource level (entity + CRUD actions), enforcement is handled via middleware, the model is extensible to feature-level permissions if needed, and database-level enforcement via RLS mirrors application-level checks. The goal is clarity and safety over clever abstraction.
Key User Flows
Several critical SaaS flows are fully supported:
1) Invitations. Users with invite privileges can invite others with a predefined role. On acceptance, the user account is created and scoped correctly.
2) Ownership transfer. Ownership can only be transferred to an existing user within the same account. Only one owner exists at any time, by design.
3) Permission denial. Unauthorized access results in a 403 page with clear messaging and recovery actions.
4) MFA. Owners are required to complete MFA during setup. Other users are encouraged via notifications and can enable MFA from their account settings. TOTP is implemented, with plans for mobile-based MFA and recovery via phone.
These flows are designed to be explicit and predictable, even at the cost of additional ceremony.
Deployment & Operations
Hosted on Fly.io by default, with optional instructions for Vercel deployment. No CI/CD pipeline yet (planned post-checkpoint).
Audit logs stored in an immutable table for key system events: Role changes, permission updates, invitations, and ownership changes. Domain-specific audit events are intentionally left to downstream products to define.
Key Trade-off
A deliberate trade-off was choosing not to invest heavily in the marketing site.
Instead, the template includes: A minimal landing page, policy placeholders, and a mechanism to support multiple marketing 'design styles' over time.
This avoids forcing branding decisions too early and keeps the focus on the product shell and governance dashboard (the parts most likely to be reused across products).
Outcomes
The template is actively used to support JobRef and schma.ai, which acts as a forcing function for correctness and usability. Rather than being a theoretical scaffold, it represents a tested foundation for shipping SaaS products with real users and real constraints.
I am constantly updating the template to accommodate bug fixes and style changes (necessary ones) I have made in both JobRef and Schma.ai. I'm also adding new features as I need them for my own projects.
What's Next
Add organization-level tenancy and org switching, split into two distributable variants (single-user/simpler version and full multi-tenant version), add CI/CD and further operational tooling, and continue refining based on downstream product needs.