Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tenants Laravel Package

cortex/tenants

Cortex Tenants brings multi-tenancy to Laravel applications, helping resolve tenants, scope Eloquent queries, and isolate data per tenant. It provides a lightweight foundation for building SaaS or multi-tenant systems within the Cortex ecosystem.

View on GitHub
Deep Wiki
Context7

Getting Started

Cortex Tenants is the tenant model + resolution layer of the Cortex stack, not a standalone tenancy engine. Before you write a line of code, know that it is archived — there will be no fixes for new Laravel/PHP releases, so treat version pinning as part of the install, not an afterthought.

  1. Verify the runtime before anything else. These packages were built for the Laravel 5.5–8 / PHP 7.x era. Run composer require cortex/tenants --dry-run and composer why-not laravel/framework to see what blocks you. If you are on Laravel 10+, budget time for a fork rather than expecting it to work.
  2. Pin the whole chain, explicitly. Tenants is described as a "frontend layer" because the real behaviour lives in its dependencies — cortex/support, rinvex/attributes, and cortex/tenantable (the trait that actually makes a model tenant-aware). Require them by exact version in composer.json so a transitive bump can't silently change semantics.
  3. Publish and migrate.
php artisan vendor:publish --provider="Cortex\Tenants\Providers\TenantsServiceProvider"
php artisan migrate

Check the provider in src/Providers/TenantsServiceProvider.php for the real publish tags (config, migrations, views) — they differ slightly between Cortex packages.

  1. Where to look first, in order: the Tenant model (src/Models/Tenant.php — route key, fillable, relationships), the published migration (tenants table plus the polymorphic pivot), the config file (table names, route prefix, middleware group), then cortex/tenantable's trait, which is where attaching/scoping actually happens.
  2. First use case — prove isolation with two tenants. Don't start with your domain models; start with a throwaway model so you can see the pivot behave:
$acme = Tenant::create(['name' => 'Acme', 'slug' => 'acme']);
$globex = Tenant::create(['name' => 'Globex', 'slug' => 'globex']);

$post = Post::create(['title' => 'Only Acme sees this']);
$post->tenants()->attach($acme);

// Isolation check
Post::whereHas('tenants', fn ($q) => $q->whereKey($acme->id))->count();   // 1
Post::whereHas('tenants', fn ($q) => $q->whereKey($globex->id))->count(); // 0

If that passes, the rest of the package is mostly wiring around it.

Implementation Patterns

Resolution belongs in middleware, once. The package gives you a Tenant model and route binding; it does not decide which tenant a request is for. Pick one strategy and centralise it:

  • Domain/subdomain — look up by the request host in a ResolveTenant middleware, abort 404 on miss, and bind into the container so downstream code reads one source of truth.
  • Session/selector — for internal admin tools where users switch tenants; store the tenant key in session and re-resolve on every request (never trust a tenant id from the request body).
public function handle($request, Closure $next)
{
    $tenant = Tenant::where('slug', $request->route('tenant'))->firstOrFail();
    app()->instance('tenant.current', $tenant);   // or use the package's resolver
    return $next($request);
}

Attach, don't scope globally. The Cortex model is many-to-many via a polymorphic pivot (tenantables-style). There is no automatic where tenant_id = ? global scope, and that is deliberate: it lets you keep reference/lookup tables central while only tenant-owned models are attached. Build one query scope or repository method that expresses "visible to current tenant" and use it everywhere, rather than sprinkling whereHas('tenants') through controllers.

Partition your schema explicitly. Write down which tables are central (users, countries, currencies, plans) and which are tenant-owned. Attaching everything "just in case" makes queries slower and makes the pivot a second source of truth you have to keep in sync.

Authorisation is a separate scope. Cortex Auth (Bouncer-based) scopes roles/abilities by tenant. The common wiring is to set the authorisation scope from the resolved tenant in the same middleware, so Gate/can() checks don't leak across tenants. Do this after tenant resolution and before the controller.

Onboarding as one transaction. Creating a tenant and seeding its defaults is where you will get partial state. Wrap Tenant::create() + default role assignment + seed data in a transaction, and fire any job after commit (DB::afterCommit or ->afterCommit() on the job).

Admin CRUD via Cortex Fort if you're using it: register the tenant resource there rather than hand-rolling controllers, and make sure route model binding uses the slug so URLs are readable and stable.

Testing pattern. Give every feature test at least two tenants. Reset the resolved-tenant singleton in setUp/tearDown so leaked state can't make an isolation test pass vacuously.

Gotchas and Tips

  • Archived means you own the bugs. Vendor-fork (repositories with a path/VCS entry) if you need a patch, and record why in the fork README. Keep composer.lock committed; a composer update on an unmaintained graph is how these installs break.
  • whereHas is your N+1 trap. Listing tenant-owned records without eager loading produces a subquery per row. Either eager-load tenants or maintain an indexed tenant_id column alongside the pivot if read volume justifies denormalising.
  • Pivot rows are not cleaned up automatically. Deleting a Tenant does not cascade to tenant-owned models in most setups. Delete or detach explicitly in an observer/deleting hook, and decide up front whether tenant deletion is soft or hard.
  • Queues and console don't have a tenant. Any job, command, or scheduled task that touches tenant data must be given the tenant id in its constructor and re-establish the tenant context in handle(). This is the most common source of cross-tenant writes.
  • Slug/route-key uniqueness is global. Two tenants can't share a slug; if you want per-tenant duplicate names, key on something else.
  • Morph map assumptions. If your app registers Relation::morphMap(), the pivot's tenantable_type values must match. Register it in a service provider before anything writes pivot rows, or you get orphaned rows that silently disappear from queries.
  • Debugging the wrong layer. If attaching "doesn't work", check cortex/tenantable's trait and your model's $fillable/morph setup — the tenants package is mostly the model, migrations, and route/CRUD surface. Dump ->toSql() and ->getBindings(), and inspect the pivot table directly rather than assuming the relation definition is wrong.
  • Config caching hides publishes. After publishing config, run php artisan config:clear in local/staging; config:cache will happily serve stale table names.
  • Extension points, in order of preference: bind your own subclass of the Tenant model in a service provider; publish and edit migrations if column names collide with your schema; publish views/routes only as a last resort, since you're now maintaining archived code.
  • Migration ordering. Install Cortex packages in dependency order (support → attributes → tenantable → tenants → auth) and run migrations in a single pass on a fresh DB before wiring anything else up.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo