Why we picked native multi-tenancy
One organization, one scope, zero leak. The most structuring decision of the platform.
Shared multi-tenancy — every organization in the same database, isolated by application filters — is the most dangerous compromise a SaaS platform can make. One mis-scoped query, one forgotten join, one missing voter, and the leak is silent, lasting, and undetectable without a full audit.
That is why Jul6art applies multi-tenancy to every layer of the app:
- Doctrine — every business entity carries an
organization_idFK, indexed alongsidedeleted_at. A barefindAll()is never valid; repositories exposequeryByOrganization(Organization)as the canonical entry point. - HTTP — the
X-ORGANIZATIONheader is resolved at the very start of the chain by a subscriber that rejects any mismatch with the authenticated user's tenant. - API Platform — every
StateProviderapplies the tenant filter automatically. Processors check the scope before persisting on potentially cross-org FKs (mass-assignment guard). - Voters —
denyAccessUnlessGranted(Voter::VIEW, $entity)on everyshow,edit,delete, with an entity-side re-check oforganization_id.
Defense in depth keeps a single mistake from becoming a breach. A voter regression is caught by the Doctrine filter; a Doctrine filter regression is caught by the voter.