Building a Multi-Tenant SaaS Dashboard from Scratch
· 4 min · snapitsaas.com
The hardest part of building SnapIt SaaS was not any single feature. It was the constraint that runs through every feature: multiple organizations share the same infrastructure, and none of them should ever see each other's data.
The Multi-Tenancy Problem
Multi-tenancy means multiple customers (tenants) use the same application and the same database. The alternative -- spinning up separate infrastructure for each customer -- is simpler to reason about but wildly expensive and operationally painful at scale. Multi-tenancy is cheaper and easier to maintain, but it introduces the risk that a bug in a query could leak one tenant's data to another.
SnapIt SaaS prevents this at the data layer. Every record in the database includes a tenant identifier as part of its primary key. Every query is scoped to a single tenant by default -- not by convention, not by a filter that developers remember to add, but by the structure of the key itself. It is physically impossible to query across tenants without explicitly constructing a cross-tenant query, which no standard API endpoint does.
Authentication reinforces this boundary. When a user logs in, their session token carries their tenant identifier. The API layer extracts the tenant from the token and uses it to scope every database operation. Even if an attacker modified a request to reference a different tenant's resources, the system would use the tenant from the authenticated token, not from the request body.
Stripe and the Subscription Lifecycle
Subscription management sounds straightforward until you list the edge cases. A customer upgrades mid-cycle and expects prorated billing. A payment fails and the system needs to retry before downgrading access. An admin cancels but wants to keep access until the end of the billing period. A team adds three seats, removes one, and the invoice needs to reflect the change accurately.
SnapIt SaaS offloads all of this to Stripe. Stripe handles proration calculations, failed payment retries, invoice generation, and subscription state management. When something changes on Stripe's side -- a payment succeeds, a subscription renews, a card is updated -- Stripe sends a webhook event. A serverless function receives the event and updates the tenant's record in the database within milliseconds. The dashboard reflects the change immediately.
This approach means the billing logic lives in Stripe, not in our application. Stripe has spent years handling the edge cases of subscription billing across every payment method and currency. Reimplementing that would be foolish.
The Admin Experience
A SaaS dashboard serves two audiences with different needs. Tenant administrators need to manage their team, view their usage, and handle their billing. Platform administrators need to see across all tenants, monitor system health, and handle support escalations.
SnapIt SaaS handles this with role-based access at two levels. Within a tenant, users can be owners, admins, editors, or viewers -- each with different permissions. At the platform level, internal administrators have a separate interface that shows aggregate metrics and cross-tenant data for support purposes. The two views share the same underlying API but with different authorization scopes, so the platform admin tools cannot accidentally be exposed to tenant users.