Build a multi-tenant app where each organization's data lives under a Firestore tenants/{tenantId}/ path prefix. Store the user's tenantId on their user document, scope every Backend Query to tenants/{currentUser.tenantId}/, apply tenant-specific branding from a settings document, and let tenant admins invite new users via Cloud Function email invitations containing the tenantId for automatic assignment on signup.
Organization-isolated data with dynamic branding and user invitations
Multi-tenancy lets one app serve many organizations with completely isolated data. A school, a clinic, and a retailer all use the same app but see only their own data, branding, and users. This tutorial implements multi-tenancy in FlutterFlow using Firestore path prefixes for data isolation, a user document tenantId for query scoping, a tenant settings document for dynamic branding, and a Cloud Function invitation system for onboarding new users to a specific tenant.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected
- Firebase Authentication enabled with user sign-in working
- Understanding of Firestore collection paths and Backend Queries
- Cloud Functions enabled for the invitation system
Step-by-step guide
Design the tenant-scoped Firestore data structure
Design the tenant-scoped Firestore data structure
Structure all tenant data under a tenants/{tenantId}/ path prefix. Create the following nested collections: tenants/{tenantId}/products, tenants/{tenantId}/orders, tenants/{tenantId}/users (tenant-specific user profiles), and tenants/{tenantId}/settings (single document with tenant config). The settings document has fields: tenantName (String), logoUrl (String), primaryColor (String, hex like '#2196F3'), plan (String: 'free'/'pro'/'enterprise'). On the top-level users collection (Firebase Auth users), add a tenantId field (String) that links each user to their organization. Set Firestore Security Rules to enforce tenant isolation: match /tenants/{tenantId}/{document=**} { allow read, write: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.tenantId == tenantId; }. This ensures users can only access data under their own tenant path.
Expected result: Firestore is structured with tenant-prefixed paths and security rules enforce that users can only access their own tenant's data.
Scope all Backend Queries to the current user's tenantId
Scope all Backend Queries to the current user's tenantId
Every Backend Query in your app must use the path tenants/{currentUser.tenantId}/ as the collection prefix. On the current user's document, read the tenantId field and store it in App State tenantId (persisted) on login. For a products list page, set the Backend Query collection path to tenants/{appState.tenantId}/products instead of just products. For orders: tenants/{appState.tenantId}/orders. Do this for EVERY collection in the app. Create Firestore document references that always include the tenant prefix. When creating new documents, always create them under the tenant path: Create Document in tenants/{appState.tenantId}/products. This pattern ensures complete data isolation without any additional filtering logic.
Expected result: All reads and writes are scoped to the current user's tenant. Users from different tenants never see each other's data.
Apply tenant-specific branding on login
Apply tenant-specific branding on login
On the login success Action Flow, after authenticating the user, query the tenant settings document at tenants/{user.tenantId}/settings. Read the logoUrl, primaryColor, and tenantName fields. Store these in App State variables: tenantLogo (String), tenantPrimaryColor (String), tenantName (String), all persisted. In your app's AppBar, replace the static logo with a dynamic Image widget bound to App State tenantLogo. For the primary color, FlutterFlow's theming is static, so apply the tenant color to key elements using Conditional Styling or a Custom Widget that reads the hex color from App State and applies it. Set the AppBar title or subtitle to App State tenantName. This way, each tenant sees their own branding when they log in.
Expected result: After login, the app displays the tenant's logo, name, and primary color throughout the interface.
Build the tenant admin panel for managing users
Build the tenant admin panel for managing users
Create a TenantAdminPage accessible only to users with role 'admin' within the tenant. Add a ListView bound to Backend Query on tenants/{appState.tenantId}/users showing each member's name, email, role, and join date. Add role management: each row has a DropDown with options (admin, editor, viewer). On change, Update Document to set the new role on both the tenant user profile and the top-level user doc. Display a member count and tenant plan information from the settings doc. Add a Remove User button with a confirmation dialog that deletes the tenant user profile (but does not delete the Firebase Auth account, which is managed separately).
Expected result: Tenant admins can view all members, change roles, and remove users from their organization.
Implement user invitations with automatic tenant assignment
Implement user invitations with automatic tenant assignment
On the TenantAdminPage, add an Invite User section with a TextField for the invitee's email and a DropDown for their initial role. The Invite button's On Tap Action Flow calls a Cloud Function named inviteUser that: creates an invite document in tenants/{tenantId}/invites with fields email, role, tenantId, invitedBy, invitedAt, and status 'pending'; then sends an email (via SendGrid or Firebase Extensions) with a signup link containing the tenantId as a URL parameter: https://yourapp.com/signup?tenant={tenantId}&invite={inviteId}. On the signup page, read the tenant URL parameter. After the user creates their account, the signup Action Flow: sets tenantId on their user doc, creates a tenant user profile at tenants/{tenantId}/users/{uid}, updates the invite status to 'accepted', and navigates to the tenant's home page.
Expected result: Admins can invite users by email. New users who sign up through the invite link are automatically assigned to the correct tenant.
Add a super-admin view for managing all tenants
Add a super-admin view for managing all tenants
Create a SuperAdminPage accessible only to users with a global superAdmin flag on their user doc. Add a ListView showing all tenant documents from the tenants collection (top level), displaying tenantName, plan, member count, and creation date. Add the ability to create new tenants: a form with tenantName, initial admin email, and plan selection. On submit, create the tenant document, settings document, and an invite for the initial admin. Add tenant-level controls: upgrade/downgrade plan, view usage statistics, and disable a tenant (set an isActive flag that the app checks on login to block access). Use a DropDown to switch between tenants for debugging: selecting a tenant overrides the App State tenantId temporarily.
Expected result: A super-admin can create, manage, and monitor all tenants from a centralized dashboard.
Complete working example
1Firestore Data Model:2├── users/{uid} (top-level, Firebase Auth linked)3│ ├── email: String4│ ├── tenantId: String ("acme-corp")5│ ├── role: String ("admin" | "editor" | "viewer")6│ └── superAdmin: Boolean (false)7├── tenants/{tenantId}8│ ├── settings/config (single document)9│ │ ├── tenantName: String ("Acme Corporation")10│ │ ├── logoUrl: String11│ │ ├── primaryColor: String ("#2196F3")12│ │ └── plan: String ("pro")13│ ├── users/{uid} (tenant-scoped profiles)14│ │ ├── displayName: String15│ │ ├── role: String16│ │ └── joinedAt: Timestamp17│ ├── products/{productId} (tenant data)18│ ├── orders/{orderId} (tenant data)19│ └── invites/{inviteId}20│ ├── email: String21│ ├── role: String22│ ├── status: String ("pending" | "accepted")23│ └── invitedAt: Timestamp2425Firestore Security Rules:26match /tenants/{tenantId}/{document=**} {27 allow read, write: if request.auth != null28 && get(/databases/$(database)/documents/users/$(request.auth.uid))29 .data.tenantId == tenantId;30}3132Login Flow:331. Firebase Auth → sign in342. Read user doc → get tenantId353. Store tenantId in App State (persisted)364. Read tenants/{tenantId}/settings → get branding375. Store logo, color, name in App State386. Navigate to Home3940All Backend Queries:41├── Products: tenants/{appState.tenantId}/products42├── Orders: tenants/{appState.tenantId}/orders43├── Users: tenants/{appState.tenantId}/users44└── NEVER query without tenant prefixCommon mistakes when creating a Multi-Tenant App with FlutterFlow
Why it's a problem: Forgetting to scope ALL queries by tenantId — even one unscoped query leaks data between tenants
How to avoid: Audit every Backend Query in the project to verify the collection path starts with tenants/{appState.tenantId}/. Enforce this in Firestore Security Rules so even if a query is misconfigured, the rules block cross-tenant reads. Add the rule: request.auth.token.tenantId == tenantId on every tenant-scoped path.
Why it's a problem: Storing tenant branding in the app code instead of Firestore
How to avoid: Store branding (logo URL, primary color, tenant name) in the tenants/{tenantId}/settings document. Read it on login and apply dynamically via App State variables. New tenants get their own branding without any code changes.
Why it's a problem: Not setting tenantId on the user document during signup
How to avoid: During the signup flow, always set tenantId on the user document. For invited users, read the tenantId from the invite link URL parameter. For self-service signups, create a new tenant document and assign the user as its admin.
Best practices
- Use Firestore path prefixes (tenants/{tenantId}/...) for complete data isolation, not a tenantId filter field on a shared collection
- Store tenantId on every user document and in persisted App State for fast access in every query
- Enforce tenant isolation in Firestore Security Rules as the ultimate safeguard, not just in UI logic
- Store tenant branding in a settings document for dynamic theming without code changes
- Use Cloud Function invitations to ensure new users are assigned to the correct tenant
- Audit all Backend Query paths during development to verify tenant scoping
- Add a super-admin role for cross-tenant management and debugging
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Design a Firestore data model for a multi-tenant SaaS application where each organization has isolated data under a tenants/{tenantId}/ path prefix. Include Firestore Security Rules that enforce tenant isolation by checking the user's tenantId against the path.
Create a tenant admin page with a ListView of organization members showing name, email, and role. Add a role DropDown to change each member's role and an Invite User section with an email TextField and Send Invite button.
Frequently asked questions
What is the difference between multi-tenancy with path prefixes versus a tenantId filter field?
Path prefixes (tenants/{tenantId}/products) create physically separate collections per tenant. A filter field (products where tenantId == X) stores all tenants' data in one collection. Path prefixes are more secure because Firestore rules can enforce isolation at the path level, and there is zero risk of accidentally querying without the filter.
How do I handle users who belong to multiple tenants?
Store a list of tenantIds on the user document and add a tenant switcher UI. On switch, update App State tenantId and reload the current page. Firestore rules should check if the user's tenantIds array contains the accessed tenantId.
Can tenants have different feature sets based on their plan?
Yes. Store the plan on the tenant settings document (free/pro/enterprise). Use Conditional Visibility on features checking App State tenantPlan. For example, hide the analytics page for free plan tenants. Enforce plan limits in Cloud Functions for server-side gating.
How do I migrate an existing single-tenant app to multi-tenant?
Create a default tenant document, move all existing data under tenants/{defaultTenantId}/, update all Backend Query paths to include the tenant prefix, add tenantId to all user documents, and update Firestore Security Rules. Test thoroughly that no queries remain unscoped.
How do I handle tenant-specific custom domains?
For web deployments, use subdomain routing (acme.yourapp.com) with a Cloud Function that maps the subdomain to a tenantId. Pass the tenantId to the app on load. For mobile apps, tenant selection happens at login or via invite links.
Can RapidDev help build a multi-tenant SaaS platform?
Yes. A production multi-tenant SaaS needs tenant provisioning automation, usage metering, per-tenant billing via Stripe Connect, data export tools, tenant admin consoles, and compliance features. RapidDev can architect the full platform beyond what the visual builder handles.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation