Fixing Laravel IDE Helper for Multi-Tenant Setups with Stancl Tenancy
While Laravel IDE Helper works great for standard applications, things break down in multi-tenant apps — especially when tenant databases differ from the landlord. Here's how I got it working with Stancl Tenancy.
I have used Barry's Laravel IDE Helper in a lot of my projects. PHPStorm's Laravel Idea plugin is also excellent, but for those who don't want a paid option, IDE Helper is the go-to. What I particularly like is how it adds database column names as PHPDoc properties on Eloquent model classes — which eliminates a significant class of errors that PHPStan/Larastan would otherwise flag.
It worked great until I needed it in a multi-database multi-tenant application with a landlord database and multiple tenant databases (using stancl/tenancy). The way this package works:
- The landlord database is set in
.envas the main connection - When a request comes in, the package reads the domain from the landlord database, identifies the tenant, and switches context to the tenant's database
The problem
When I ran php artisan ide-helper:models -W, the column annotations in my Eloquent models were wrong. After some investigation, I realised IDE Helper was reading from the landlord database — which doesn't contain the tenant tables. The actual model fields live in the tenant databases, but since tenancy was never initialised during the command, IDE Helper never saw them.
I looked through the IDE Helper configuration but found nothing to point it at a different connection per model. So I wrote a workaround.
The fix: a custom Artisan command
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Tenant;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class GenerateTenantIDEHelper extends Command
{
protected $signature = 'tenancy:ide-helper';
protected $description = 'Generate IDE Helper annotations using a tenant database connection';
public function handle(): void
{
$tenant = Tenant::first();
if (! $tenant) {
$this->info('No tenants found. Exiting.');
return;
}
$this->info('Initialising tenant context for IDE Helper generation...');
tenancy()->initialize($tenant);
Artisan::call('ide-helper:generate');
Artisan::call('ide-helper:meta');
Artisan::call('ide-helper:models', ['-W' => true]);
tenancy()->end();
$this->info('IDE Helper generation completed.');
}
}
Run it with:
php artisan tenancy:ide-helper
The logic is straightforward:
- Find any available tenant — it doesn't matter which one, they all share the same schema
- Initialise tenancy with that tenant, switching the active connection to the tenant database
- Run the three IDE Helper commands — they now read from the tenant database and see all the actual columns
- End tenancy, restoring the landlord connection
What each command does
ide-helper:generate
Generates _ide_helper.php with autocomplete stubs for Laravel facades, helpers, and macros. Helps your IDE understand dynamic magic methods.
ide-helper:meta
Generates .phpstorm.meta.php, which helps PHPStorm understand service container bindings and app()->make() calls — useful when working with dependency injection.
ide-helper:models -W
Scans your Eloquent models and writes their database columns directly into the class files as @property PHPDoc annotations. The -W flag writes to the files rather than generating a separate helper file. This is the one that fixes PHPStan complaints about unknown model properties.
The full command is available as a Gist on GitHub.
TL;DR: If Laravel IDE Helper is pulling fields from the wrong database in your multi-tenant project, initialise a known tenant before running the ide-helper commands. One custom command, and your model annotations will reflect your actual tenant schema.