VOOZH about

URL: https://dev.to/deploynix/running-artisan-commands-across-tenants-a-guide-to-tenantsrun-stancltenancy-5d8h

⇱ Running Artisan Commands Across Tenants: A Guide to tenants:run (stancl/tenancy) - DEV Community


Running Artisan Commands Across Tenants: A Guide to tenants:run (stancl/tenancy)

If you run a multi-tenant Laravel app with stancl/tenancy, sooner or later you need to run an Artisan command for every tenant at once — migrate their databases, clear their caches, or regenerate a nightly report. The problem is that php artisan migrate only touches your central database. Each tenant gets left behind.

Plenty of developers go hunting for a tenants:artisan command to solve this. It doesn't exist. The command you actually want is tenants:run, and it wraps any registered Artisan command in each tenant's context. This guide walks through tenants:run, the dedicated tenant-aware commands, the programmatic API, and the gotchas that bite once you're past a handful of tenants.

Key takeaways- The generic command is tenants:run {commandname} — not tenants:artisan. It runs any registered Artisan command inside each tenant's context. - Scope it with --tenants=ID (repeat the flag for several). Omit it and the command hits every tenant. - Forward arguments and options with --argument="key=value" and --option="key=value". - For migrations, seeds, and rollbacks, prefer the dedicated commands: tenants:migrate, tenants:seed, tenants:rollback. - It runs tenants sequentially. Once you pass a few hundred, queue a job per tenant instead of looping in one process.

What does tenants:run actually do?

The tenants:run command iterates over your tenants and, for each one, initializes tenancy, runs the command you named, then ends tenancy before moving on. Initializing tenancy is what swaps the database connection, cache prefix, and filesystem paths over to that tenant via the package's bootstrappers — so the wrapped command behaves exactly as it would inside a tenant request.

Here's the signature straight from the package source:

php artisan tenants:run {commandname}
 {--tenants=*}
 {--argument=*}
 {--option=*}

The commandname is any Artisan command that's already registered in your app. The simplest case takes no extra arguments at all:

# Clear the cache for every tenant
php artisan tenants:run cache:clear

Behind the scenes this is the same loop you'd write by hand: initialize tenant, call cache:clear, end tenant, repeat. The package just hides the boilerplate and the context switching.

How do I target specific tenants?

Use the --tenants option, repeating it once per tenant ID. Leave it off and the command runs for all tenants — which is convenient for cache clears but genuinely dangerous for anything destructive.

# One tenant
php artisan tenants:run cache:clear --tenants=acme

# Several tenants (repeat the flag)
php artisan tenants:run cache:clear --tenants=acme --tenants=globex

# All tenants (the default when --tenants is omitted)
php artisan tenants:run cache:clear

Get into the habit of scoping with --tenants while testing. A mistyped command name fails loudly for one tenant; a destructive command run against the default "all" can ruin your evening.

How do I pass arguments and options to the command?

This is where tenants:run trips people up. You can't just append flags to the wrapped command, because the parser would assign them to tenants:run itself. Instead, you pass them through with --argument and --option, each as a key=value pair.

php artisan tenants:run email:send \
 --tenants=acme \
 --argument="subject=Welcome" \
 --option="queue=1"

So a command you'd normally invoke as php artisan email:send "Welcome" --queue becomes the form above. Repeat --argument and --option for each value you need to forward. It's verbose, but it keeps the wrapper's flags and the inner command's flags from colliding.

When should I use the dedicated commands instead?

stancl/tenancy ships purpose-built commands for the operations you run most. They handle the right defaults for you — tenant migrations, for example, look in database/migrations/tenant automatically rather than your central migrations folder. Reach for these before tenants:run:

Command

What it does

Key options

tenants:migrate

Runs tenant migrations against each tenant database

--tenants=*, --path, --force

tenants:migrate-fresh

Wipes and re-migrates a tenant database (destructive)

--tenants=*

tenants:rollback

Reverses the last migration batch per tenant

--tenants=*

tenants:seed

Seeds each tenant database

--tenants=*, --force

tenants:list

Lists all tenants with their IDs and domains

tenants:run

Runs any other registered Artisan command per tenant

--tenants=*, --argument=*, --option=*

# Migrate every tenant database (use --force in production)
php artisan tenants:migrate --force

# Roll back the last batch for two tenants
php artisan tenants:rollback --tenants=acme --tenants=globex

# Wipe and re-migrate a single tenant (destructive!)
php artisan tenants:migrate-fresh --tenants=acme

# Seed every tenant non-interactively
php artisan tenants:seed --force

One detail worth burning into memory: these commands run non-interactively when invoked from a deploy hook or scheduler, so any command that normally asks "are you sure?" in production needs --force. Without it, tenants:migrate will hang or abort.

Can I run tenant commands from my own code?

Yes, and it's often cleaner than shelling out to Artisan. Every tenant exposes a run() method that initializes its context, executes a closure, then restores the previous context. For batches, tenancy()->runForMultiple() does the same across a collection.


php