| name | writing-data-migrations |
| description | Use when writing or altering a Laravel migration that changes data (not only schema) in a Canyon GBS app — data back-fills, transformations, clean-ups, seeding, or activating a Feature Flag. Trigger whenever you create a migration with `make:migration` or `make:tmp-migration`, decide between a permanent and a temporary (`tmp_`) migration, need a migration to be idempotent and safe to re-run, add a required `down()`, or target the landlord versus tenant databases. Covers permanent-migration rules (DB facade only, no removable classes), temporary-migration rules and the `tmp_` prefix with its cleanup task, and running migrations. Do not use for: permission seeding (use `creating-permissions`), creating the Feature Flag class (use `managing-feature-flags`), or cleanup task file mechanics (use `managing-cleanup-tasks`). |
| user-invocable | false |
| license | Elastic-2.0 |
| metadata | {"author":"canyongbs"} |
Writing Data Migrations
Migrations may contain schema changes, data changes, or both — there is no requirement to separate them. Data migrations typically move data between formats, clean up or back-fill data, seed data for a new feature, or activate a Feature Flag after a schema change.
- Migrations in
database/migrations run on every tenant.
- Migrations in
database/landlord run only on the landlord database.
Any data migration whose result the application code depends on must be paired with a Feature Flag — see the zero-downtime guideline and the managing-feature-flags skill.
Run every command through the pls guideline (these apps run inside the app container).
Permanent vs temporary migrations
Permanent migrations
Permanent migrations stay in the codebase indefinitely. When they include data changes:
- Do not reference classes that may later be removed — no Eloquent models, no app facades. The only classes permitted are
Illuminate\Support\Facades\DB and Feature Flag classes under App\Features (extending App\Support\AbstractFeatureFlag).
- Handle every possible SQL error (e.g.
UniqueConstraintViolationException) and wrap changes in a transaction so a failure cannot corrupt the query connection.
- Be idempotent — check table and column existence, catch and handle SQL errors, and verify data state before changing it, so the migration can safely run more than once.
- Always include
down(). Production migrations run once and are not rolled back; if a shipped migration is wrong, write a new migration to fix it. down() is still required to support testing.
Temporary migrations
Temporary migrations are one-time work deleted after they have run across all environments — seeding for existing tenants, one-off clean-ups, or back-fills. If the whole file should be deleted afterwards, prefix its name with tmp_.
Prefer the dedicated command, which adds the prefix and prompts for a cleanup task:
php artisan make:tmp-migration backfill_user_preferences
This creates YYYY_MM_DD_HHMMSS_tmp_backfill_user_preferences.php and attaches it to a cleanup task (see managing-cleanup-tasks). Use --no-cleanup to skip the prompt, and --module=<module> for modular projects.
Temporary migrations may use Eloquent and other removable classes because the file will be deleted, but still wrap changes in a transaction, include down(), and stay idempotent where possible. Prefer the DB facade even here; if you do use a model, account for its side effects (observers, global scopes).
Creating a migration
php artisan make:migration add_status_column_to_orders_table
php artisan make:tmp-migration seed_default_settings_for_existing_tenants
Running migrations
php artisan migrate --database=landlord --path=database/landlord
php artisan tenants:artisan "migrate"
Related: managing-feature-flags, managing-cleanup-tasks, creating-permissions.