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

Awesome Landlord Laravel Package

torzer/awesome-landlord

torzer/awesome-landlord is a Laravel package for multi-tenant applications with a landlord/tenant architecture. It helps manage tenants, isolate databases, route by domain or subdomain, and switch tenant contexts, making SaaS and multi-tenant Laravel apps easier to build.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and register it the Laravel-5.2 way (service provider + facade alias in config/app.php); on 5.5+ auto-discovery should pick it up, but the package predates it, so verify.

composer require torzer/awesome-landlord
php artisan vendor:publish --provider="Torzer\Awesome\Landlord\LandlordServiceProvider"
php artisan migrate

Publish the config first (config/landlord.php) and set the two model classes and foreign keys:

// config/landlord.php
'landlord' => ['model' => App\User::class,   'foreign_key' => 'landlord_id'],
'tenant'   => ['model' => Tenant::class,      'foreign_key' => 'tenant_id'],

Wire up the two traits — this is the "where to look first" step:

// app/User.php  — owns tenants
use Torzer\Awesome\Landlord\Traits\Landlord;

// app/Tenant.php — represents a tenant (workspace/account)
class Tenant extends Model { /* landlord() relation to owner */ }

// app/Post.php   — tenant-scoped data
use Torzer\Awesome\Landlord\Traits\Tenant;

Add a tenant_id (nullable, indexed, FK) column to every tenant-scoped table, then set the active tenant once per request and query normally:

use Torzer\Awesome\Landlord\Facades\Landlord;

Landlord::setTenant($tenant);   // or setTenantById($id)
Post::all();                    // automatically WHERE tenant_id = ?

That scoping is the first use case and the core value: you write ordinary Eloquent queries and tenancy is enforced by a global scope plus a creating hook that stamps tenant_id on insert.

Implementation Patterns

Resolve the tenant in middleware. The most common workflow is a SetTenant middleware that runs after auth (or resolves from a subdomain/session), calls Landlord::setTenant(...), and lets the rest of the request stay ignorant of tenancy:

public function handle($request, Closure $next)
{
    if ($tenant = $request->user()->currentTenant()) {
        Landlord::setTenant($tenant);
    }
    return $next($request);
}

Landlord-side tenant management. The Landlord trait gives the user a tenants() relation, so CRUD over tenants is just Eloquent on the owner model ($user->tenants()->create([...])). Use this for onboarding: create tenant → assign user → set tenant → seed defaults.

Automatic stamping on create. The Tenant trait hooks model creation to fill the configured foreign key, so you should not pass tenant_id manually in ordinary create()/save() calls. Do pass it explicitly (or disable the hook) for imports/seeders.

Bypassing the scope deliberately. Admin panels, cross-tenant reports, and "which tenants have this record?" queries need an escape hatch. The package ships one (a withoutTenant-style helper or a removable global scope); wrap only the minimum surface:

// verify the exact helper name in your pinned version's README
$allPosts = Post::withoutGlobalScope(...)->get();

Queues and console. Tenant state is request-scoped and not serialized. In jobs, receive the tenant id and call Landlord::setTenant() again in handle() before touching scoped models. In console commands, loop tenants explicitly:

foreach (Tenant::all() as $tenant) {
    Landlord::setTenant($tenant);
    // per-tenant work
}

Testing. Set the tenant in setUp() (or a test trait) so factories and assertions hit the same scope production uses; add at least one test asserting tenant A cannot read tenant B's rows.

Eager loading. Because the scope is a where, with('comments') on a scoped parent is filtered correctly — but only if the related model also uses the Tenant trait. Apply it consistently or relations will leak.

Gotchas and Tips

  • The package is archived (last release 2020-09-22) and targets Laravel 5.2+. On modern Laravel, expect to fork or pin; evaluate stancl/tenancy / tenancy/tenancy before committing to this for new work.
  • Unset tenant state is the sharpest edge. Depending on version, an unset tenant may scope to NULL (returns nothing) or skip the scope entirely (returns everything). Test both, and fail closed in middleware if security matters.
  • Bulk inserts bypass the creating hook. Model::insert(), raw DB::table() queries, and query-builder writes are not scoped and not stamped — you must set tenant_id yourself.
  • No DB-level enforcement. Because tenancy is an Eloquent global scope, a missing trait, a DB::raw join, or a model without the Tenant trait silently sees all rows. Add composite indexes ((tenant_id, ...)) and consider FK constraints so cross-tenant rows are at least detectable.
  • Raw SQL and aggregates (selectRaw, whereRaw, DB::select) ignore the scope; re-add tenant_id manually.
  • The Tenant model itself must not be scoped by the Tenant trait, or you can never list all tenants to loop over. Keep tenant scoping on tenant-owned data only.
  • Config caching (php artisan config:cache) is fine, but if you change landlord.php while cached, tenant resolution will silently keep the old model/fk — clear the cache when debugging "wrong tenant" bugs.
  • Relationship FK names. The landlord_id and tenant_id keys are config-driven; if relations return null, check the config matches the actual columns, not the trait defaults.
  • Debugging: ->toSql() on a scoped query will show the injected tenant_id where clause; if it's missing, the trait or global scope isn't registered on that model. Log Landlord::getTenantId() early in the request to confirm resolution order.
  • Extension points are limited: the scope class, the two traits, and the config are effectively the only seams. Forking the trait is usually cleaner than fighting the global scope for edge cases.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource