Building Multi-Tenant SaaS: Workspaces, Roles, Billing, and Isolation
How to design workspaces, role-based access, billing tiers, and tenant isolation for scalable SaaS. Practical architecture from DigiForge.

Multi-tenant SaaS is the default architecture for any B2B platform that expects to grow beyond a handful of customers. Yet the devil is in the details: how you model workspaces, assign roles, structure billing, and isolate tenants determines whether your platform scales smoothly or collapses under complexity. At DigiForge, we've built and rebuilt these systems across dozens of SaaS products. Here's what we've learned.
Workspaces: The Core Unit of Organization
A workspace is a logical container that groups users, data, and configuration for a single customer (or a team within a customer). We've seen teams conflate workspaces with billing accounts or even with projects—don't. Keep the workspace as the fundamental tenant scope, and layer other concepts on top.
Key design decisions:
- Hierarchical or flat? Some platforms need workspaces within workspaces (e.g., an enterprise with multiple departments). We recommend a two-level hierarchy: organization (billing entity) and workspace (team unit). Avoid deeper nesting unless absolutely necessary—it complicates role inheritance and data access.
- Unique identifiers: Use a human-readable slug (like
acmeoracme-marketing) for the workspace in URLs, but always rely on a UUID internally. Slugs can change; UUIDs should not. - Soft-delete with grace period: Deleting a workspace is a drastic action. Implement a 30-day soft-delete so that users can recover data. Stop billing immediately but keep the data until the grace period expires.
When a new user signs up, consider the onboarding flow. Should they create a workspace first, or can they explore a sandbox? We prefer a guided flow where the user creates an organization, then their first workspace, and immediately gets prompted to invite teammates. This reduces friction and sets the expectation that the platform is collaborative.
A common mistake is attaching billing plans directly to workspaces. Instead, attach billing to an organization that contains one or more workspaces. This allows enterprises to have a single invoice while giving each team its own workspace.
Roles and Permissions: Fine-Grained, but Not Too Fine
Role-based access control (RBAC) is the industry standard, but the granularity matters. At DigiForge, we usually start with three built-in roles—Admin, Member, Viewer—and allow custom roles for advanced plans. The Admin role has full workspace control, Members can create and edit most resources, and Viewers can only read.
Where things get tricky is permission scoping. Permissions should be scoped to the workspace by default, but you may need organization-level permissions (e.g., manage billing) or even cross-workspace read access for consolidated reporting. Model permissions as a set of action:resource pairs, and assign them to roles. Store assignments in a join table: (workspace_id, user_id, role_id).
Pro tip: Avoid checking permissions at the application layer alone. Push as much entitlement logic as possible to your database using row-level security (RLS) or a policy engine like OPA. This reduces the chance of a bug in your web layer exposing someone else's data.
One pattern that has served us well is to cache the user's role within a session token (JWT) rather than querying the database on every request. But beware: if you cache roles in JWTs, you must have a mechanism to invalidate tokens when a role changes (e.g., short token expiry or a blocklist).
Also think about role inheritance: should an organization Admin automatically have Admin in all workspaces? Our rule of thumb: organization roles provide a ceiling, but workspace roles can be more restrictive. For example, an Org Admin can access any workspace, but a workspace Admin cannot access billing.
Billing and Pricing Models: Meta-Data, Not Business Logic
Billing is where multi-tenancy gets real. Your pricing model—per‑seat, per‑workspace, usage-based, or tiered—needs to be reflected in your data model, but your billing system should be decoupled from your core application. Use a third-party billing provider (Stripe, Recurly, Chargebee) and keep only the subscription ID and plan ID in your database.
We recommend the following database approach:
- A
planstable that defines the plan slug, price, and feature flags (e.g.,max_users,storage_gb,api_rate_limit). - An
organizationstable that has acurrent_plan_idandbilling_provider_subscription_id. Link organizations to workspaces via a join table. - A
featurestable (or simple JSON column) that stores overrides. For example, if a customer negotiates a custom rate, override the plan's price at the organization level.
The hardest part is limiting access based on the plan. You have two choices: enforce limits in the application (check max_users before inviting) or enforce via database row counts and triggers. We prefer application-level enforcement because it produces better user-facing error messages, but we always add a nightly reconciliation job that flags organizations exceeding their limits.
Plan upgrades and downgrades require careful handling. When a customer upgrades, immediately grant access to new features but prorate the billing via your provider. On downgrade, you need to decide: block access to features that exceed the new plan, or allow a grace period? We recommend a grace period of the current billing cycle, after which you enforce restrictions.
One lesson from the trenches: never let billing failures cascade into data loss. If a payment fails, degrade gracefully (e.g., restrict write operations) but do not delete data. Your customer will pay—eventually.
Tenant Isolation: Shared vs. Silo
Isolation is the most consequential architectural decision. The standard trade-off is between a shared database (one database for all tenants, with a tenant_id column on every table) and a database-per-tenant (each workspace gets its own database). We've run both, and we've settled on a hybrid approach for most projects.
- Shared with strict RLS: Good for small to medium tenants (under 10k users each). Row-level security is built into Postgres, and we use a session variable (
app.tenant_id) to filter every query. This is the simplest to operate and upgrade. - Database-per-tenant: Necessary when tenants require strict compliance (HIPAA, SOC 2, GDPR data residency) or when the application is I/O-heavy per tenant. The operational overhead is real—schema migrations must be applied to hundreds of databases—but tools like Flyway and automated CI make it manageable.
- Schema-per-tenant: A middle ground using separate schemas within one database. It offers more isolation than a shared table but less operational overhead than full databases. We use this for our lower-tier plans and upgrade customers to database-per-tenant if they need it.
Regardless of isolation strategy, never allow direct database access from the client. Always route through an API layer that enforces tenant identity. And for the love of your on-call team: never, ever use tenant_id in URLs without validating that the authenticated user belongs to that tenant.
Data migration between isolation levels is a reality. For example, when a tenant outgrows the shared database, you may need to migrate them to a dedicated database. Plan for that early: write a migration script that exports and imports data, and test it with production-like data. It should be possible to run without downtime by using a blue-green approach.
Automated Provisioning: Let the Machine Do It
Manually spinning up new tenants might work for the first ten customers, but it does not scale. As ContractorHUB's recent patent filing highlights, zero-touch implementation is a key differentiator for multi-tenant SaaS [1]. We've built provisioning workflows that automate everything from database creation (or schema cloning) to seeding default data and sending welcome emails.
A typical automated provisioning pipeline:
- User signs up, creates an organization, and selects a plan.
- A webhook from your billing provider triggers a provisioning job (e.g., a serverless function or a Kubernetes Job).
- The job creates the tenant's isolation layer (schema or database), runs initial migrations, and populates default roles and settings.
- A second job sends an email with login instructions and next steps.
- The user is redirected to the new workspace—fully functional—within seconds.
Idempotency is non-negotiable here. If the provisioning job fails mid-way, it must be safe to retry. We wrap the entire process in a state machine with a provisioning_status column on the organization row: pending → creating → active → failed. Failed states get sent to a dead-letter queue for human intervention.
Don't forget to provision supporting infrastructure: DNS records for custom domains, CDN cache warmers, API rate limit limits per tenant, and monitoring alerts. Automate everything that can be scripted, because manual steps will be forgotten under pressure.
Conclusion: The Multi-Tenant Mindset
Multi-tenancy is not something you bolt on after launch. It must guide your data model, your access control, your billing integration, and your deployment strategy from day one. The good news: if you get these four pillars—workspaces, roles, billing, isolation—right, the rest of your SaaS becomes remarkably easier to build and maintain.
Every SaaS is different, but the patterns above have served us well across industries. Whether you are just starting your MVP or scaling to thousands of tenants, we encourage you to think deeply about these decisions. And if you want a second pair of eyes on your architecture, we are always happy to review.


