Skip to content

Need it customized?

Custom features, theme changes, server setup, migrations and upgrades - built by the Botble team. Quick fixes from $99, fixed quote within 1 business day.

Get a free quote →

Artisan commands ​

All commands live under platform/packages/tenancy/src/Console/ and share the tenancy: prefix.

Summary ​

CommandPurposeScheduled
tenancy:preflightCheck the server can run the SaaSNo — run after any config change
tenancy:activate-pluginsActivate every plugin a new store can be seeded with, platform-wideEvery deploy
tenancy:migrate-tenantsApply a release's pending migrations to every existing storeEvery upgrade
tenancy:create-tenantCreate and provision a storeNo — manual, or via signup / the API
tenancy:billing-maintenanceSuspend past-due stores, purge cancelled ones past retentionDaily
tenancy:send-lifecycle-emailsTrial-ending reminders and pre-purge warningsDaily, 0 8 * * *
tenancy:notify-pending-ordersDigest of bank-transfer requests awaiting reviewDaily, 0 9 * * *
tenancy:collect-usageRefresh dashboard usage countersDaily, 0 3 * * *
tenancy:schedulePer-store scheduled maintenanceCron per --frequency (hourly/daily/everyFiveMinutes)
tenancy:verify-domainsRe-check pending custom domains via DNSEvery 10 min, */10 * * * *
tenancy:repair-storageRecreate missing per-tenant storage directoriesNo — run when storage goes missing
tenancy:backfill-pluginsAlign stores' enabled apps with their planNo — run after curating the catalog
tenancy:webhooks-deliverDeliver due webhook retries, prune historyEvery minute — required
tenancy:themesList installed themes: presets, assets, plugins, catalog, storesNo — run to verify
tenancy:publish-theme-assetsPublish theme assets into public/themesEvery deploy
tenancy:register-themeValidate an installed theme and add it to the catalogNo — one-off per theme

tenancy:preflight ​

bash
php artisan tenancy:preflight

Checks this server can run Ecommerce SaaS. No arguments or options. Run it after any config change — before creating stores, and again after a Botble upgrade to confirm the upstream patches are still intact. It also fails if signup email verification is on but mail goes nowhere (log/array mailer), fails when any plugin a new store can be seeded with is not active platform-wide (and names it), and turns any theme the catalog actively offers into a hard failure if its assets or required plugins are missing.

tenancy:activate-plugins ​

bash
php artisan tenancy:activate-plugins

Activates, platform-wide, every plugin a new store can be seeded with: each theme's required_plugins plus every plugin the preset dumps switch on, in plugin.json require order. No arguments or options. It is the same step the browser installer's automated setup runs, so a CLI deploy and a browser install end up with the same set.

Plugin code is loaded only from the central activated list, and provisioning migrates every plugin on disk — so a platform that activated only ecommerce fails its first store with Class "Botble\Location\Models\City" not found. Plugins that are already active are skipped (re-activating one would clear the whole cache), so it is safe on every deploy. Exits non-zero, naming the plugin, if any activation fails.

tenancy:migrate-tenants ​

bash
php artisan tenancy:migrate-tenants --pretend      # report only, writes nothing
php artisan tenancy:migrate-tenants --pretend -v   # + per-directory counts
php artisan tenancy:migrate-tenants                # apply to every provisioned store
php artisan tenancy:migrate-tenants --tenants=acme # one store (repeatable)
OptionMeaning
--tenants=Limit to specific store ids; defaults to every provisioned store
--pretendReport what would run without applying anything

The way to apply a release's pending migrations to stores that already exist. It runs the same migration set provisioning runs — core, packages, the theme's own migrations and every plugin — inside each store's own database, so a store created today and a store upgraded tomorrow end up with the same schema. One broken store is reported and skipped; it does not abort the sweep for the others.

tenancy:create-tenant ​

bash
php artisan tenancy:create-tenant {subdomain}
    [--name=]
    [--email=]
    [--password=]
    [--theme=]
    [--preset=]
    [--sync]
Argument/OptionMeaning
subdomainRequired. Subdomain label, e.g. acme for acme.example.com
--name=Store display name
--email=Store owner admin email
--password=Store owner admin password (generated when omitted)
--theme=Storefront theme slug; defaults to the platform default theme
--preset=Theme preset to seed, e.g. home-fashion; omit for a bare store
--syncProvision inline instead of queueing

Creates and provisions a store. Without --sync, provisioning is queued — TENANCY_PROVISION_SYNC=true (or a sync queue driver) makes any invocation inline regardless of the flag. A store created this way has no subscription row, so it's entitled to the whole apps and themes catalog until you assign it a plan.

tenancy:billing-maintenance ​

bash
php artisan tenancy:billing-maintenance [--dry-run]

Suspends stores past their grace period and purges cancelled stores past retention. --dry-run reports what a run would suspend or drop without changing anything. Cron it daily.

tenancy:send-lifecycle-emails ​

bash
php artisan tenancy:send-lifecycle-emails [--dry-run]

Sends trial-ending reminders and pre-purge retention warnings. --dry-run lists who would be emailed without sending. Every send is recorded in central tenant_notifications (unique per tenant+type+key), so re-runs never double-send.

cron
0 8 * * * php artisan tenancy:send-lifecycle-emails

tenancy:notify-pending-orders ​

bash
php artisan tenancy:notify-pending-orders [--dry-run]

Emails every operator a digest of bank-transfer requests awaiting review. Deliberately not deduped — it's a work queue, not a one-time notification, so it fires daily while requests are waiting. --dry-run reports without sending.

cron
0 9 * * * php artisan tenancy:notify-pending-orders

tenancy:collect-usage ​

bash
php artisan tenancy:collect-usage [--tenant=*]

Rolls per-tenant usage counters (products, orders, storage, staff users) into the central tenant_usage table for the dashboard. --tenant= limits the run to specific tenant ids (repeatable). Cron it daily:

cron
0 3 * * * php artisan tenancy:collect-usage

Live counts for a single store are always available from the store's own database via GET /stores/{id}/usage?refresh=1.

tenancy:schedule ​

bash
php artisan tenancy:schedule [--frequency=hourly] [--tenant=*]

Runs per-store scheduled maintenance across every tenant. --frequency= accepts hourly, daily or everyFiveMinutes (default hourly) and selects which per-tenant tasks that invocation runs; --tenant= limits it to specific tenant ids. Past a few hundred stores this is O(stores) — shard it with --tenant= across parallel cron entries.

tenancy:verify-domains ​

bash
php artisan tenancy:verify-domains [--domain=] [--all]
OptionMeaning
--domain=Verify a single hostname only (repeatable to verify multiple)
--allAlso re-check domains already verified

Re-checks pending custom domains via DNS. Cron it every 10 minutes so a customer's DNS change is picked up without them clicking Verify by hand:

cron
*/10 * * * * php artisan tenancy:verify-domains

tenancy:repair-storage ​

bash
php artisan tenancy:repair-storage [--tenant=*]

Recreates missing per-tenant storage directories under storage/tenants/tenant<id>/. --tenant= limits the run to specific tenant ids. Not scheduled — run it when a store's uploads or generated assets start 404ing after a lost storage directory.

tenancy:backfill-plugins ​

bash
php artisan tenancy:backfill-plugins [--tenant=*] [--enable-all] [--write]

Aligns each store's enabled apps with its plan entitlement. --tenant= limits it to specific stores; --enable-all also switches on every entitled app a store is currently missing; without --write the command only reports what it would change. Run it after curating the apps catalog or reassigning apps to a plan.

tenancy:webhooks-deliver ​

bash
php artisan tenancy:webhooks-deliver [--limit=100] [--endpoint=]

Required every minute

tenancy:webhooks-deliver is the only retry driver. The queued job that fires a webhook makes just the first attempt; every retry after that is driven by this command reading next_attempt_at off the delivery row — not a queue delay, so it retries the same way whether the queue driver is sync or a real worker. Without this cron entry, a failed delivery is never retried, and a delivery whose worker died mid-attempt is never picked up again.

cron
* * * * * cd /path/to/app && php artisan tenancy:webhooks-deliver

--limit= caps rows attempted per run (default 100); --endpoint= sweeps deliveries for one endpoint id only. Two runs can't trip over each other — the sweep takes a cache lock and a second invocation that finds it held exits immediately. Each run also prunes finished history: delivered/exhausted rows older than TENANCY_WEBHOOKS_RETENTION_DAYS (default 30) are deleted. See Webhooks for delivery states and retry schedule.

tenancy:themes ​

bash
php artisan tenancy:themes

Lists every installed theme with its preset count, whether assets are published, whether its required plugins are present, its catalog status, and how many stores run it. No arguments or options — run it any time to verify a theme before or after registering it.

tenancy:publish-theme-assets ​

bash
php artisan tenancy:publish-theme-assets [theme]

Belongs on every deploy

Publishes storefront theme assets into public/themes. theme is optional — omit it to publish every installed theme, or pass a slug to publish just one. public/themes is shared by every store and no customer request may write there, so an unpublished theme after a deploy renders an unstyled shop and nothing self-heals until this runs.

tenancy:register-theme ​

bash
php artisan tenancy:register-theme {theme} [--publish] [--inactive]
Argument/OptionMeaning
themeRequired. Theme slug — the directory name under platform/themes
--publishPublish the theme's assets first if they're missing
--inactiveCreate the catalog row switched off

Validates an installed theme and adds it to the tenant-facing catalog. Refuses when the theme isn't installed, a required plugin is missing, or assets couldn't be published. Deliberately does not assign plans — that's a separate pricing step. See Adding a storefront theme for the full runbook.

The stock tenants:* commands you must not use ​

php artisan list shows tenants:* commands from the underlying stancl/tenancy package that look like they belong to this product. They do not: stancl configures them with --path => database_path('migrations/tenant'), a directory this build does not have, so they cannot reach the migrations this product uses. A store's schema is built by Botble's own migrator instead.

This build therefore replaces the dangerous ones with a refusal. Running any of them prints an error, points at the right command and exits non-zero — nothing is touched:

CommandWhat happens here
tenants:migrateRefused — use tenancy:migrate-tenants
tenants:migrate-freshRefused. Stock, it wipes every store's database and restores nothing
tenants:rollbackRefused — it would roll back from the same missing path, undoing nothing
tenants:seedRefused — not configured for this product's seeders
tenants:runRefused
tenants:listHarmless — lists tenant ids

Use the tenancy:* commands documented above instead. For upgrades, see Upgrade guide — tenancy:migrate-tenants is the step that migrates existing stores.

A command not to run ​

php artisan list also shows tenancy:install. That one belongs to the underlying stancl/tenancy vendor package, not to Ecommerce SaaS. The platform ships already configured, so running it only republishes vendor scaffolding over working config. Leave it alone.