Multi-tenancy
Isolation
Exactly which data is shared across tenants and which isn't.
Shared across tenants
- Customers (Firebase Auth users). One account works on every store.
- The
/users/{uid}doc. Profile, addresses, wishlist — they follow the customer, not the store. - Reviews. A customer's reviews are written under their identity; the product they review belongs to a tenant, but the review itself is global.
Isolated per tenant
- Products and variants (
/products/{productId}+ subcollections). The rule requiresrequest.resource.data.tenantId === auth.token.tenantIdon create and a matching tenant on update. - Orders (
/orders/{orderId}). Each order doc liststenantIds: string[]. Tenant admins can read orders that include their tenant; the customer can read their own. - Categories, coupons, shipping rates, branding, legal pages, settings — all per tenant.
- Push notification tokens —
/admin_push_tokens/*is keyed by tenant.
Server-only collections
These collections deny all client reads and writes — only the Admin SDK (server code) touches them:
/mail— outbound email queue./reservations— checkout stock holds./pending_status_emails— debounce queue for status-change emails./coupons— read by checkout, written by admins through API routes./admin_invites— pending team invites./admin_audit— append-only audit log of team changes./privacy_audit— append-only audit log of GDPR deletions.
What server routes must always do
Every mutating API route:
- Verifies the session or bearer token.
- Re-reads the auth claims server-side.
- Compares the
tenantIdclaim against the request's resolved tenant. - Performs the write through the Admin SDK.
The Firestore rule is the second line of defense, not the first. The route is responsible for refusing requests before the database does.