Hi everyone,
I wanted to share how we implemented multi-tenancy in Mautic using a single codebase with multiple database instances. The goal was to make it highly scalable, flexible, and production-ready for multiple clients.
This approach worked really well for us in real-world uses. It also helped us turn Mautic into a SaaS-like platform, where a single instance can serve multiple clients without things getting messy.
High-Level Architecture
We designed the system as follows:
- One Core Database
- Multiple Tenant Databases (one per tenant)
Core DB handles:
- Users
- Authentication
- Tenant registry
- Mapping users ↔ tenants
Tenant DB handles:
- Contacts
- Segments
- Campaigns
- Emails
- Plugins
- Permissions
- Everything Mautic-related for that tenant
Architecture Flowchart
The idea is simple: one instance serving many isolated databases.
-
Core Database: Handles the Core data like user authentication, tenants data, and a mapping table (tenant_users) that defines who has access to which tenant.
-
Tenant Databases: Each project/tenant has their own completely isolated database.
-
Dynamic Configuration: Instead of a single
local.php, we store tenant-specific configs inapp/config/tenant/{tenant_id}.php
Authentication Flow
Step-by-step:
- User sends login request
- Core DB validates credentials
- On success:
-
Check if tenant cookie exists
- YES → connect to tenant DB dynamically
- NO → redirect to tenant selection page
-
- Once tenant is resolved:
-
Load tenant DB
-
Validate user permissions from tenant DB
-
- Login successful
Request Lifecycle Flow
The tenant DB connection stays consistent throughout the request lifecycle.
Public & API URLs
Since tracking pixels, landing pages, and API calls don’t use cookies, we updated the URL pattern: mautic-url.com/t/{tenant_id}/{original-path} The middleware extracts the {tenant_id} directly from the URL to route the data to the correct DB instantly.
Switching Between Tenants
We added a simple UX improvement
- Dropdown in top navigation
- Shows all accessible tenants
- On selection:
- Updates tenant cookie
- Reloads context
Command Handling (CLI)
We extended Mautic commands with an optional parameter. --tenant-id
- If tenant ID is provided → runs for that tenant
- If not provided → runs for all tenants
Example
php bin/console mautic:emails:send --tenant-id=1
This makes cron jobs super flexible.
Installation
We modified the installation steps. During setup, you specify Core DB credentials and your first Tenant DB. You can choose to host the DB on the same server or point to a remote DB server.
Tenant Management
There is a dedicated Manage Tenants page. Creating a new client is a 10-second process. System creates the DB, creates the schema, and generates the config file automatically.
Video link: https://drive.google.com/file/d/1w3KPZZmz0J8pS1s77-y77RQn3Ti4ZCwr/view?usp=sharing
New Configuration Parameters
We also introduced a couple of configuration options to make things more controlled and flexible:
'max_tenants_limit' => 10,
'allow_remote_db_for_tenants' => true,
-
max_tenants_limit → This helps restrict how many tenants can be created in a single instance. Useful to avoid overloading the system or to control usage based on your setup.
-
allow_remote_db_for_tenants → When enabled, it allows tenant databases to be hosted on remote servers.
Why This is Highly Scalable?
-
Database Decoupling: You can host the application on Server A and the tenant database on Server B (with SSL support). This is crucial for clients with strict data residency requirements.
-
Custom Domains: Supports domain mapping. Client A can use
marketing.clientA.comwhile Client B usesmautic.clientB.com. The middleware validates the domain against the Core DB to ensure the request hits the right data. -
Easy Horizontal Scaling: Add new tenants without affecting others. No shared DB bottleneck
-
Tenant Isolation: Each tenant has its own email settings, its own plugins, and its own user roles. They never even know other tenants exist on the same server.
-
Compliance Friendly: Clients can host their own data. Supports secure connections (SSL)
-
Single Codebase Maintenance
- One deployment
- One upgrade process
- Multiple tenants benefit instantly
Would love to hear more ideas or suggestions from the community on how we can make this even more robust. Always open to feedback and improvements ![]()





