mll-lab/laravel-conditional-migrations
Laravel Conditional Migrations adds condition-based execution to your database migrations, letting you run or skip specific migration files when predefined criteria are met. Ideal for environment-specific schema changes, feature rollouts, and safer deployments.
Check the deprecation first. This package is archived and explicitly deprecated in favor of mll-lab/laravel-utils v4.0.0. New work should start at the successor package — read its migration/condition docs before installing anything here. Only invest in this package if you are inheriting an existing codebase that already depends on it, or you have a hard constraint keeping you on the Laravel versions it supports.
Install (existing codebases only). composer require mll-lab/laravel-conditional-migrations against a Laravel version outside the known-bad 9.21 patch. Confirm composer.json require/conflict constraints before scoping — the v3.1.1 release note ("Mark Laravel 9.21 as unsupported") signals patch-level compatibility handling, so treat the supported matrix as a first-class artifact and pin the exact Laravel patch in composer.lock.
Where to look first.
migrate / migrate:rollback — that is where the condition gate actually lives.First use case. Environment- or config-gated schema changes: a migration that creates a PostGIS extension, sets up a staging-only view, or seeds reference data that must not exist in production. Instead of branching in the migration body (or wrapping migrate in a script), you declare the condition on the migration and let the package decide whether it executes during a normal php artisan migrate.
Environment gate. Attach a condition that returns true only when app()->environment() is in an allowlist. Typical targets: extension creation, full-text search setup, or partitioning that only certain environments provision. Keep the allowlist in config rather than hard-coding environment names in each migration.
Config / feature-flag gate. Return the value of a dedicated config key (e.g. config('features.partitioning')). This is the cleanest pattern for optional schema changes that ship in one release but are enabled later per deployment. It also makes the gate testable — flip the config in tests instead of faking the environment.
Capability probes as conditions. Conditions can be arbitrary logic, so probe the database rather than the environment when the real question is "does this driver support X?" — check the connection driver, server version, or Schema::hasTable. Be careful to keep these cheap and side-effect free; they execute on every migrate.
Paired migrations. When a schema change is conditional, write its rollback with the same condition so migrate:rollback stays symmetric. The package claims consistent behavior across migrate and migrate:rollback — verify that symmetry for each conditional migration you add, because an asymmetry here is exactly what leaves environments in divergent states.
Testing workflow. Make conditions deterministic in the test suite: set the environment and any config flags explicitly in phpunit.xml / TestCase::setUp rather than relying on defaults. Run migrate:fresh in CI to prove the conditional set produces a valid schema from scratch, not just incrementally.
Rollout sequencing. Land the migration and its condition together, and document in the PR why the condition exists. A condition with no rationale is indistinguishable from dead code six months later, and nobody will dare flip it.
Deprecation is the headline gotcha. No fixes, no security patches, no new Laravel major support, and effectively no community (1 star, 0 dependents). Any bug you hit is yours to patch in a fork. Budget the successor migration as part of adoption, not as a later cleanup.
Patch-level Laravel pinning. The 9.21 exclusion means minor-version compatibility claims are insufficient. Capture the exact Laravel patch per service and add a CI gate that fails on drift into unsupported patches. Determine whether 9.21 is excluded via a conflict entry (composer refuses to install) or documentation only (composer silently installs into an untested combination) — the two failure modes are very different.
Skipped ≠ forgotten — verify migration history semantics. "Keeps migration history consistent" most plausibly means a skipped migration is still recorded as run so it is not retried on every deploy. If that's the case, flipping a condition from false to true later will not re-run the migration — you need a new migration to apply the change. Confirm this against the source before you rely on a condition as a toggle; the opposite assumption is a classic way to end up with a schema that differs from what the migration file says.
Condition evaluation is invisible by default. A migration that silently does nothing looks identical to one that ran and changed nothing. Log condition decisions (or check the migrations table rows and timestamps) when debugging; don't assume --pretend will faithfully evaluate conditions, since the gate may run outside the pretend path.
Keep conditions pure. No writes, no expensive queries, no calls to services that may be down during a deploy window. A condition that throws will fail the whole migrate run, which is a worse outcome than the migration simply not applying.
Rollback asymmetry. If a conditional migration ran on one environment and its condition is now false, rollback will skip it and leave the schema change in place. Treat conditions as write-once per environment: once a conditional migration has run somewhere, changing its condition retroactively is a footgun.
Extension point. If the package allows custom condition classes, that's the cleanest place to centralize repeated logic (driver checks, feature flags) — but any extension you build is now coupled to an archived package, so keep it thin and easy to port to the successor API.
How can I help you explore Laravel packages today?