PHPackages                             nubitio/tenant-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. nubitio/tenant-bundle

ActiveSymfony-bundle[Framework](/categories/framework)

nubitio/tenant-bundle
=====================

Opt-in column-tenant kit for Nubit Symfony apps: TenantScoped attribute, Doctrine filter, context resolver, and registry.

v0.12.1(3w ago)031↓50%1MITPHPPHP &gt;=8.3

Since Jun 19Pushed 3w agoCompare

[ Source](https://github.com/nubitio/tenant-bundle)[ Packagist](https://packagist.org/packages/nubitio/tenant-bundle)[ Docs](https://github.com/nubitio/nubit-symfony/tree/main/packages/tenant-bundle)[ RSS](/packages/nubitio-tenant-bundle/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (24)Versions (21)Used By (1)

@nubitio/tenant-bundle
======================

[](#nubitiotenant-bundle)

Opt-in multi-tenancy for Nubit Symfony apps: **column mode** (shared DB + Doctrine filter), **database mode** (per-tenant DSN), **schema mode** (PostgreSQL `search_path`), or **hybrid** routing, plus optional plan quota enforcement.

Install
-------

[](#install)

```
composer require nubitio/tenant-bundle
```

Register the bundle and enable it when the app profile is `saas` or `hybrid`:

```
# config/packages/nubit_tenant.yaml
nubit_tenant:
    enabled: true
    isolation: column          # column | database | schema | hybrid
    resolution: [user, jwt_claim]
    tenant_entity: App\Entity\Restaurant   # or omit for Nubit\TenantBundle\Entity\Tenant
    quotas_enabled: false      # set true to enforce plan limits on prePersist
    rls_enabled: false
```

Pair with admin-bundle SaaS profile:

```
# config/packages/nubit_admin.yaml
nubit_admin:
    app_profile: saas
    single_tenant_defaults: false
```

Column isolation (default)
--------------------------

[](#column-isolation-default)

Mark tenant-owned entities with `#[TenantScoped]` and implement `TenantOwnedInterface`. The Doctrine `nubit_tenant` filter scopes queries to the active tenant.

```
use Nubit\TenantBundle\Attribute\TenantScoped;
use Nubit\TenantBundle\Contract\TenantOwnedInterface;
use Nubit\TenantBundle\Entity\TenantOwnedTrait;

#[TenantScoped]
#[ORM\Entity]
class Order implements TenantOwnedInterface
{
    use TenantOwnedTrait;
}
```

Custom column + association stamping (RestoPOS pattern):

```
#[TenantScoped(field: 'restaurant_id', relation: 'restaurant')]
class Order implements RestaurantOwnedInterface
{
    use RestaurantOwnedTrait;
}
```

Users should implement `TenantAwareUserInterface` so the `user` resolver can populate `TenantContext`.

Database and hybrid isolation
-----------------------------

[](#database-and-hybrid-isolation)

Switch `isolation: database` to route every request to a tenant-specific database URL. For `isolation: hybrid`, the control-plane tenant entity chooses `column` or `database` per tenant via `getIsolationMode()`. Database targets must also expose `getDatabaseUrl()`:

```
nubit_tenant:
    enabled: true
    isolation: hybrid
    tenant_connection: tenant
    control_plane_connection: default
```

Configure the tenant connection wrapper:

```
# config/packages/doctrine.yaml
doctrine:
    dbal:
        connections:
            tenant:
                wrapper_class: Nubit\TenantBundle\Doctrine\Connection\DynamicUrlConnection
```

`control_plane_connection` is used only for registry lookups; keep it on a control-plane entity manager, separate from the dynamically switched `tenant_connection`.

The default `Nubit\TenantBundle\Entity\Tenant` retains `databaseUrl` for compatibility with existing database-isolation deployments. New production applications should use a custom tenant entity whose `getDatabaseUrl()` resolves credentials from a dedicated control-plane secret store.

The bundle only routes an already-provisioned target. Database creation, credentials, grants, migrations, queues, and provider SDKs remain application-owned.

PostgreSQL schema isolation
---------------------------

[](#postgresql-schema-isolation)

`isolation: schema` derives the tenant schema exclusively from the positive resolved tenant ID: with the default `schema_prefix: tenant_`, tenant ID `42` becomes `tenant_42`. Slugs, domains, request headers, and target-provider strings are never used as SQL identifiers.

```
nubit_tenant:
    enabled: true
    isolation: schema
    tenant_connection: default
    schema_prefix: tenant_
    base_schemas: [public, extensions]
```

The schema switcher validates lowercase PostgreSQL identifiers (allowed characters and the 63-byte limit), executes `SET search_path TO "tenant_42", "public", "extensions"`, and explicitly restores `SET search_path TO "public", "extensions"` after the HTTP request. It rejects switching or resetting while a transaction is active; it never uses `SET LOCAL` or bare `RESET`.

For `isolation: hybrid`, an application-owned `TenantIsolationTargetProviderInterface` may return `new TenantIsolationTarget(TenantIsolationTarget::SCHEMA)`. The bundled registry provider intentionally rejects schema targets, so applications must own the placement decision. Column and database hybrid targets remain compatible.

`TenantSchemaProvisionerInterface` and `TenantSchemaMigrationRunnerInterface` are documentation markers only. Applications own schema creation, roles/grants, migrations, backups, and secret retrieval; this bundle supplies neither DDL nor provider credentials.

Plan quotas
-----------

[](#plan-quotas)

Enable `quotas_enabled: true` to block `prePersist` when a plan limit is reached. Limits come from `FeatureCheckerInterface::getFeatureConfig($resource)['max']`.

1. Tag entities with the quota resource name:

```
use Nubit\TenantBundle\Attribute\QuotaResource;

#[QuotaResource('team_users')]
#[ORM\Entity]
class User { /* … */ }
```

2. Register usage counters via `QuotaUsageProviderInterface` (autoconfigured with tag `nubit.quota_usage_provider`):

```
final readonly class TeamUsersQuotaUsageProvider implements QuotaUsageProviderInterface
{
    public function supports(string $resource): bool
    {
        return 'team_users' === $resource;
    }

    public function count(string $resource): int
    {
        // return current tenant usage
    }
}
```

When `quotas_enabled` is on, the bundle aliases `QuotaEnforcerInterface` to `FeatureQuotaEnforcer` (replacing admin-bundle's unlimited noop for SaaS apps).

Commands
--------

[](#commands)

```
bin/console nubit:tenant:list
```

Per-tenant console jobs extend `Nubit\Platform\Symfony\Console\PerTenantCommand` — the bundle wires the connection switcher and a Doctrine-backed registry.

Resolution strategies
---------------------

[](#resolution-strategies)

StrategySource`user``TenantAwareUserInterface` on the authenticated user`jwt_claim``tenantId` / `tenantName` claims in the access JWT`header``X-Tenant-Id` (configurable)`subdomain`Tenant registry lookup by slug / domain

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance95

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

20

Last Release

23d ago

PHP version history (3 changes)v0.10.2PHP &gt;=8.5

v0.10.6PHP &gt;=8.2

v0.10.13PHP &gt;=8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/990211?v=4)[Johan Guerreros](/maintainers/johangm90)[@johangm90](https://github.com/johangm90)

---

Top Contributors

[![johangm90](https://avatars.githubusercontent.com/u/990211?v=4)](https://github.com/johangm90 "johangm90 (6 commits)")

---

Tags

symfonybundlesaasmulti-tenantnubit

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/nubitio-tenant-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/nubitio-tenant-bundle/health.svg)](https://phpackages.com/packages/nubitio-tenant-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k18.3M414](/packages/easycorp-easyadmin-bundle)[pimcore/pimcore

Content &amp; Product Management Framework (CMS/PIM/E-Commerce)

3.8k3.9M529](/packages/pimcore-pimcore)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M763](/packages/sylius-sylius)[contao/core-bundle

Contao Open Source CMS

1301.7M2.9k](/packages/contao-core-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M222](/packages/sulu-sulu)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9626.1k65](/packages/open-dxp-opendxp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
