A Claude Code custom skill that incrementally upgrades Laravel applications to the latest stable version, one major version at a time (similar to laravelshift.com).
-
-
Save furey/f04081b7aa6bf0da4e81420cd2e91dcb to your computer and use it in GitHub Desktop.
- Claude Code CLI installed and authenticated
One-liner:
curl -sL "https://gist.githubusercontent.com/furey/f04081b7aa6bf0da4e81420cd2e91dcb/raw/claude-code.skill.laravel-shift.installer.sh" | bashOr manually create the skill directory and download both skill files:
mkdir -p ~/.claude/skills/laravel-shift
curl -sL "https://gist.githubusercontent.com/furey/f04081b7aa6bf0da4e81420cd2e91dcb/raw/claude-code.skill.laravel-shift.SKILL.md" \
-o ~/.claude/skills/laravel-shift/SKILL.md
curl -sL "https://gist.githubusercontent.com/furey/f04081b7aa6bf0da4e81420cd2e91dcb/raw/claude-code.skill.laravel-shift.version-changes.md" \
-o ~/.claude/skills/laravel-shift/version-changes.mdVerify the skill is detected:
claude /laravel-shiftRun from your Laravel project root. Four commands available:
| Command | Description |
|---|---|
/laravel-shift plan |
Analyse current version, map upgrade path, create ./shift/ tracking directory |
/laravel-shift shift |
Execute the next pending shift (one version increment) |
/laravel-shift status |
Report current progress |
/laravel-shift resume |
Resume an in-progress shift (useful for new conversations) |
Running /laravel-shift with no argument defaults to plan (if no ./shift/ directory exists) or resume (if one does).
- Tracking directory: A
./shift/directory is created in your project root containing plans, checklists, progress logs, and learnings for each version increment. This directory is the source of truth for resuming work across conversations. - Docker environments: Each shift sets up a
Dockerfileanddocker-compose.ymlwith the correct PHP/Node versions. All composer, artisan, npm, and test commands run inside Docker. - One version at a time: Shifts are executed sequentially (e.g. 8→9, then 9→10, then 10→11). Some low-risk boundaries may be combined, but high-impact boundaries (5.8→6.0, 8→9, 10→11) are always handled individually.
- Git branches: Each shift creates a dedicated branch (
shift/<N>-<description>) for isolated review and commit. - User decisions: The skill asks before making irreversible changes — dropping packages, removing features, or choosing between upgrade strategies.
Re-run the installer one-liner or manual curl commands to pull the latest versions of both files.
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| SKILL_DIR="${HOME}/.claude/skills/laravel-shift" | |
| GIST_BASE="https://gist.githubusercontent.com/furey/f04081b7aa6bf0da4e81420cd2e91dcb/raw" | |
| mkdir -p "${SKILL_DIR}" | |
| curl -sL "${GIST_BASE}/claude-code.skill.laravel-shift.SKILL.md" -o "${SKILL_DIR}/SKILL.md" | |
| curl -sL "${GIST_BASE}/claude-code.skill.laravel-shift.version-changes.md" -o "${SKILL_DIR}/version-changes.md" | |
| echo "Installed to ${SKILL_DIR}" | |
| echo "Run 'claude /laravel-shift' from your Laravel project to get started." |
| name | description | argument-hint |
|---|---|---|
laravel-shift |
Incrementally upgrade a Laravel application to the latest stable version, shifting one major version at a time. Use when asked to upgrade, migrate, or shift a Laravel project to a newer version. |
[plan|shift|status|resume] |
You are an expert Laravel upgrade engineer. Your job is to incrementally upgrade a Laravel application to the latest stable version, one major version at a time (similar to laravelshift.com).
Companion reference: version-changes.md in this skill directory contains detailed breaking changes per version boundary and a package compatibility matrix. Consult it when planning and executing shifts.
/laravel-shift plan— Analyse the project, detect current version, map upgrade path, create tracking docs in./shift//laravel-shift shift— Execute the next pending shift (one version increment)/laravel-shift status— Report current progress (read./shift/CURRENT-STATE.md)/laravel-shift resume— Resume work on an in-progress shift (for new LLM conversations)
Default (no argument): same as plan if no ./shift/ directory exists, otherwise same as resume.
Read composer.json and composer.lock to determine:
- Current Laravel framework version (exact, from lock file)
- Current PHP version requirement
- All Laravel ecosystem packages (Spark, Cashier, Horizon, Dusk, Sanctum, Jetstream, etc.)
- All third-party packages (check for abandoned packages on packagist)
- Implicit/transitive dependencies the app actually uses (e.g.
predis/predisas "suggested",erusev/parsedownas transitive) - Frontend stack (package.json: Vue/React/Alpine, Bootstrap/Tailwind, Mix/Vite)
- Node version (Volta, .nvmrc, or package.json engines)
Search the web for:
- Latest stable Laravel version
- PHP requirements for each Laravel version in the upgrade path
- Breaking changes per version (official upgrade guides at laravel.com/docs/X.x/upgrade)
Cross-reference with version-changes.md in this skill directory for detailed per-version breaking changes and package compatibility.
Use the Laravel version → PHP version requirements table:
Laravel 5.5 → PHP >= 7.0.0
Laravel 5.6 → PHP >= 7.1.3
Laravel 5.7 → PHP >= 7.1.3
Laravel 5.8 → PHP >= 7.1.3
Laravel 6.x → PHP >= 7.2.0
Laravel 7.x → PHP >= 7.2.5
Laravel 8.x → PHP >= 7.3.0
Laravel 9.x → PHP >= 8.0.2
Laravel 10.x → PHP >= 8.1.0
Laravel 11.x → PHP >= 8.2.0
Laravel 12.x → PHP >= 8.2.0
Combine shifts where breaking changes are minimal:
- 5.5 → 5.8 can often be combined (minor changes)
- 6.0 → 8.0 can often be combined (medium changes)
- 9.0 → 10.0 is usually straightforward
- 11.0 → 12.0 is a maintenance release (usually zero code changes)
Never combine across these boundaries (high breaking changes):
- 5.8 → 6.0 (helper removal, major version)
- 8.0 → 9.0 (Flysystem 3, PHP 8, Symfony 6, SwiftMailer → Symfony Mailer)
- 10.0 → 11.0 (app structure overhaul: Kernels removed, bootstrap/app.php rewrite)
Check for major blockers that need special handling:
- Laravel Spark Classic — must be removed/replaced before Laravel 6+ (biggest possible blocker)
- Laravel Passport vs Sanctum — API auth migration
- Laravel Mix → Vite — required for Laravel 9.19+ (node-sass is dead, pre-built binaries removed)
- Vue 2 → Vue 3 — if using Vue (can defer to post-migration)
- Abandoned packages — check packagist for maintenance status; common ones:
fzaninotto/faker,fideloper/proxy,fruitcake/laravel-cors,soapbox/laravel-formatter,themsaid/laravel-mail-preview,stechstudio/laravel-vfs-adapter - Custom Flysystem adapters — Flysystem 3 at Laravel 9 (complete API rewrite)
- Implicit dependencies — packages used but not in composer.json (e.g.
predis/predisas "suggested") - Transitive dependencies — packages that may disappear between versions (e.g.
erusev/parsedown)
Create ./shift/ with:
shift/
├── README.md # Overview, decisions, architecture, LLM resumption guide
├── CURRENT-STATE.md # Active shift, what's done, what's next, blockers
├── <shift-N>/
│ ├── docker-compose.yml # Docker services for this shift's PHP/Node versions
│ ├── Dockerfile # PHP image with extensions, Composer, Node
│ ├── requirements.md # Detailed checklist with checkboxes
│ ├── progress.md # Sub-task status, commits
│ └── learnings.md # Gotchas, issues, solutions
Critical: The README.md must contain enough context for a NEW LLM conversation to resume work. Include:
- Project description and purpose
- Current and target versions
- Key decisions made with the user
- Architecture summary (models, controllers, services, providers, middleware)
- Package inventory with version bump plans
- Blocker details if applicable (Spark, Passport, abandoned packages)
Before finalizing, ask the user about:
- Any packages to remove vs upgrade (billing, teams, etc.)
- Frontend direction (keep current or modernize)
- Testing strategy (Docker, local PHP, CI)
- Any features to drop or add during the upgrade
- Read
shift/CURRENT-STATE.mdto determine current shift - Read
shift/<current>/requirements.mdfor the checklist - Read
shift/<current>/progress.mdfor what's already done - Read
shift/<current>/learnings.mdfor known gotchas - Read
version-changes.mdin this skill directory for the current version boundary's breaking changes - Create a git branch:
shift/<N>-<description>(if not already on one) - Tear down any previous shift's Docker stack:
docker compose -f shift/<prev>/docker-compose.yml down - Set up Docker environment (see 2.2)
Each shift directory must include a Dockerfile and docker-compose.yml with PHP/Node versions matching the shift's requirements.
For the first shift: create from scratch — Dockerfile with PHP CLI image, extensions (bcmath, gd, mbstring, pdo_mysql, pdo_sqlite, xml, zip, pcntl), Composer 2 (NEVER Composer 1 — Packagist dropped support), and Node. docker-compose.yml with three services: app (PHP+Composer+Node), mysql, redis.
For subsequent shifts: copy the previous shift's Docker files and bump version tags per the PHP requirements table in section 1.3. Also bump Node/MySQL as appropriate.
Standard services:
app— PHP CLI with Composer + Node, mounts project root, serves on:8000mysql— MySQL with healthcheck, persisted volumeredis— Redis Alpine
Critical Docker configuration:
# docker-compose.yml — app service
environment:
# ...standard config...
MAIL_MAILER: log # SAFETY: prevent real email sends
SESSION_DOMAIN: "" # Allow both localhost and inter-container access
command: php artisan serve --host=0.0.0.0 --port=8000 --no-reload
# --no-reload is CRITICAL in L8+: without it, artisan serve strips Docker env
# vars for hot-reload, causing the host .env file to override docker-compose
# settings (wrong SESSION_DOMAIN, APP_KEY, etc.)All composer/artisan/npm/test commands must run via Docker:
DC="docker compose -f shift/<N>/docker-compose.yml"
$DC up -d
$DC exec app composer install
$DC exec app npm install
$DC exec app php artisan migrate
$DC exec app npm run dev
# Tests MUST pass APP_ENV=testing explicitly (Docker env overrides phpunit.xml):
$DC run --rm -e APP_ENV=testing -e DB_CONNECTION=sqlite -e DB_DATABASE=:memory: -e MAIL_MAILER=log app vendor/bin/phpunit
$DC downTroubleshooting: If the app container crashes on boot (vendor corruption, missing dependencies), use docker compose run --rm app <command> instead of exec — you can't exec into a crashed container.
Work through the requirements checklist methodically:
- Complete one sub-task at a time
- Update
progress.mdafter each sub-task - Record gotchas immediately in
learnings.md— every stumbling block, unexpected behavior, workaround, or non-obvious fix. These learnings are critical for future shifts and new LLM conversations. Categorize by phase (planning, implementation, validation). - Always use
composer update -Wfor major framework version bumps (transitive dependency conflicts require widened resolution) - Run tests via Docker with explicit env overrides (see 2.2)
- Check for remaining references to old patterns: grep for deprecated code
- Make implicit/transitive dependencies explicit in composer.json if the app uses them directly
Timing matters: update vendor BEFORE changing code that depends on new vendor features. For example, DeferrableProvider interface doesn't exist until L5.8 vendor is installed — changing the code first crashes the app.
After all sub-tasks complete:
docker compose run --rm app composer installsucceeds without errorsdocker compose run --rm -e APP_ENV=testing -e DB_CONNECTION=sqlite -e DB_DATABASE=:memory: -e MAIL_MAILER=log app vendor/bin/phpunitpasses- App boots:
docker compose exec app php artisan route:listloads without errors docker compose run --rm app composer auditshows 0 security advisories- No deprecated/removed code remains (grep verification)
- Update
learnings.mdwith every issue found during validation — Docker build failures, dependency conflicts, test failures and their root causes, config issues. These are the most valuable learnings for future shifts.
- Update
shift/<current>/progress.md— mark all complete - Update
shift/CURRENT-STATE.md— advance to next shift - Prompt user to review and commit the changes
For when a new LLM conversation picks up mid-upgrade:
- Read
shift/README.md— full project context - Read
shift/CURRENT-STATE.md— where we are - Read
shift/<current>/requirements.md— what needs doing - Read
shift/<current>/progress.md— what's done - Read
shift/<current>/learnings.md— known issues - Read
version-changes.mdin this skill directory for the current version boundary - Continue from where the previous conversation left off
Read and summarise:
shift/CURRENT-STATE.md- Current shift's
progress.md - Overall progress (which shifts are done vs pending)
After ALL shifts are complete:
- Move final shift's
Dockerfileanddocker-compose.ymlto repo root as the canonical dev environment - Update README to reference root Docker files (no more
docker compose -f shift/N/...) - Optionally aggregate all
shift/*/learnings.mdinto a single analysis document - Remove the
shift/directory from the repo - Merge the final shift branch into main/master
- NEVER run tests that could send real emails without explicit user confirmation
- Host
.envbleeds into Docker containers via volume mount — production email addresses can receive test emails - Always set
MAIL_MAILER=log(L9+) orMAIL_DRIVER=log(L8-) in docker-compose.yml - Gate email-sending tests behind
@group emailand exclude by default:--exclude-group email Mail::fake()alone is NOT sufficient in Laravel <8 — trait auto-setup (setUp{TraitName}) doesn't exist until L8
APP_ENV=testingMUST be passed explicitly via-eflag (Docker env vars override phpunit.xml)--no-reloadflag onartisan serveis CRITICAL in L8+ (without it, env vars stripped for hot-reload)SESSION_DOMAIN: ""(empty) for Docker — allows both localhost and inter-container access- Always
docker compose downprevious shift stack before starting new one (port conflicts) - Composer 1 is dead — Packagist dropped support. Always use
composer:2
- One shift at a time. Never skip versions.
- Always use
composer update -Wfor major framework version bumps. - Always create a git branch per shift.
- Always update tracking docs as you work.
- Always run tests after changes with explicit Docker env overrides.
- Always update
learnings.mdimmediately when hitting any issue — don't batch learnings. Every blocker, workaround, unexpected behavior, config gotcha, or non-obvious fix must be recorded as it happens. These learnings are the most valuable artifact for future shifts and new conversations. - Ask the user before making irreversible decisions (dropping packages, removing features).
- The
./shift/docs are the source of truth — keep them current for LLM context resumption. - Follow the project's CLAUDE.md coding standards for all new code.
- Make implicit/transitive dependencies explicit in composer.json.
- After all shifts complete, run Phase 5 (post-migration cleanup).
Detailed breaking changes per version boundary. Referenced by the laravel-shift skill.
Items marked with ⚠ BATTLE-TESTED are gotchas discovered during a real 5.5→12.0 migration.
- PHP: 7.0 → 7.1.3
- Logging:
config/app.phplog settings → newconfig/logging.php(channel-based) fideloper/proxy: ~3.x → ~4.x (header constants change)Illuminate\Http\Request::has()no longer returns true for empty strings- PHPUnit: ~5.0/~6.0 → ~7.0
- Email verification feature added (optional
MustVerifyEmailinterface) resources/assets/convention changed toresources/js/+resources/css/Blade::if()directive available- Paginator URLs now use
httpsif request is secure Inputfacade deprecated (useRequest)- Dusk: ^2.0 → ^3.0/^4.0
- Carbon 1 → Carbon 2 (date serialization may change)
env()helper no longer works outside config files (cache:config issue)$datesproperty deprecated → use$castswithdatetime- String/Array helpers deprecated (still work, removed in 6.0)
orWhereclosures change behavior with Eloquent scopesMockery: ~0.9 → ^1.0- setUp() methods need
: voidreturn type $deferproperty →DeferrableProviderinterface- ⚠ BATTLE-TESTED:
DeferrableProviderinterface doesn't exist until 5.8 vendor is installed — change code AFTERcomposer update, not before, or app crashes on boot - ⚠ BATTLE-TESTED:
setUp{TraitName}auto-call doesn't exist in L5.8 (that's L8+) —Mail::fake()via traits won't auto-invoke, causing real emails during tests - New config files needed:
logging.php,hashing.php
- ALL global string/array helpers REMOVED:
array_get→Arr::get,str_slug→Str::slug,title_case→Str::title,camel_case→Str::camel,array_set→Arr::set,array_except→Arr::except,array_only→Arr::only, etc. Illuminate\Support\Facades\Inputfully removedfzaninotto/fakerabandoned →fakerphp/fakerIlluminate\Foundation\Auth\UsergainsMustVerifyEmailcontractBelongsToMany::addConstraintsmethod signature changed- Lazy collections introduced
Arr::wrap,Arr::randomadded- ⚠ BATTLE-TESTED: Exception handler
Exception→Throwableis NOT required in 6.x — base handler still usesException. Safe to defer to 7.x/8.x shift - ⚠ BATTLE-TESTED: Transitive dependencies can silently disappear — e.g.
erusev/parsedownwas transitive in 5.8 but gone in 6.0. Add any used transitive deps to composer.json explicitly - ⚠ BATTLE-TESTED:
composer update -Wrequired for major framework bumps (transitive dependency conflicts)
config/cors.phpconfiguration file addedRouteServiceProvider::$namespaceproperty deprecated- Date serialization:
Carbon::toJSON()now usestoIso8601String()— addserializeDate()to models if API output changes Blade::component()method signature changedSymfony\Component\HttpFoundation\Responsecontract changes- Custom rule
passesmethod now receives rule attribute HttpException::$statusCodenow protectedStr::of()fluent strings available
- Model factories: closure
$factory->define()→ class-based Factory classes withHasFactorytrait - Seeders:
database/seeds/→database/seeders/+Database\Seedersnamespace - Route syntax:
'Controller@method'→[Controller::class, 'method'](or keep with explicit $namespace) RouteServiceProvider::$namespaceremoved (must use fully-qualified names or set explicitly)- Exception handler:
register()method replacesreport()/render()as primary pattern - Pagination default: Bootstrap → Tailwind (override with
Paginator::useBootstrap()) fideloper/proxyremoved —TrustProxiesmiddleware built into frameworkretryAfterproperty →backoff()method on jobstimeoutAtproperty →retryUntil()method on jobs- Queue
tablemethod naming changes $datesproperty fully removed → use$casts- PHPUnit: ~7.0/~8.0 → ^8.5/^9.0
- Guzzle: ^6.3 → ^6.3 or ^7.0
Auth::routes()extracted from framework — requireslaravel/uipackage- ⚠ BATTLE-TESTED:
php artisan servestrips Docker env vars in L8+ for hot-reload.ServeCommandsets all env vars tofalseexcept a small allowlist (APP_ENV,LARAVEL_SAIL). Docker-compose settings become invisible to HTTP requests → host.envwins → wrong SESSION_DOMAIN → 419 CSRF. Fix: always use--no-reloadflag - ⚠ BATTLE-TESTED:
predis/predisis a "suggested" dependency, not required — may be used in production without being in composer.json. Add explicitly if Redis is used - ⚠ BATTLE-TESTED:
assertArraySubsetremoved in PHPUnit 9 — replace withassertArrayHasKey+assertContains
- PHP 8.0+ required
- Flysystem 3.0: major filesystem API changes
Storagemethods throw exceptions instead of returning false- Custom filesystem adapters must be completely rewritten
Filesystem::url()behavior changesputmethod returnstrue/throws instead offalse
- Symfony 6.0 components
- TrustProxies:
HEADER_X_FORWARDED_ALLremoved — use bitwise OR of individual constants
- TrustProxies:
- SwiftMailer → Symfony Mailer
config/mail.phprewrite: single driver →mailersarray formatMAIL_DRIVER→MAIL_MAILEReverywhere (env, config, docker-compose)
doctrine/dbal→ ^3.0- Anonymous migration classes (optional)
- Enum casting available
str()helper andto_route()helper addedBus::dispatchNow()→Bus::dispatchSync()- Remove
fideloper/proxy(absorbed into framework) - Remove
fruitcake/laravel-cors(absorbed into framework at 9.2) - ⚠ BATTLE-TESTED: node-sass is dead (pre-built binaries removed from GitHub, Python 2 required for compilation). Forces Mix → Vite migration at this boundary
- ⚠ BATTLE-TESTED: ESM strict mode breaks when migrating CommonJS to ESM: implicit globals, double
super()in constructors, missingconstonfor...ofloops - ⚠ BATTLE-TESTED: SweetAlert2 v7 must import from
sweetalert2/dist/sweetalert2.js(UMD) — Vite resolvesjsnextfield to ESM source with class constructor incompatibilities - ⚠ BATTLE-TESTED: SweetAlert2 exact version pinning needed —
^7.1.2resolves to v7.33.1 in fresh npm install with completely different CSS (em/rem units, smaller popups) - ⚠ BATTLE-TESTED: jQuery ESM requires side-effect import module before Bootstrap (ESM import hoisting reorders imports)
- ⚠ BATTLE-TESTED: Font/asset paths must be absolute (
/fonts) in LESS/CSS variables for Vite — output location differs from source - ⚠ BATTLE-TESTED: reCAPTCHA needs
render=explicitmode — Vitetype="module"defers script execution, auto-render fires before Vue mounts - ⚠ BATTLE-TESTED:
spatie/flysystem-dropboxv3 requires PHP 8.2 — must use v2 for PHP 8.0
- PHP 8.1+ required
- Method return types added throughout framework (any overridden method needs return type)
- Invokable validation rules replace
Ruleinterface Processfacade for external commands$castscan be method:protected function casts(): arraydoctrine/dbalno longer required (unless usingchange()in migrations)$routeMiddleware→$middlewareAliasesin Http Kernel- Remove empty
registerPolicies()and stale$policies - PHPUnit: ^9.0 → ^10.0 (run
--migrate-configurationfor XML schema) - Minimum Composer 2.2+
- ⚠ BATTLE-TESTED: Cleanest upgrade boundary — often passes tests on first attempt with no debugging
- PHP 8.2+ required
- App structure overhaul:
app/Http/Kernel.phpremoved → middleware inbootstrap/app.php->withMiddleware()RouteServiceProviderremoved → routing inbootstrap/app.php->withRouting()Console/Kernel.phpremoved → schedule inroutes/console.phpviaSchedulefacade- Exception handling in
bootstrap/app.php->withExceptions()
- Service provider consolidation: most providers merged into
AppServiceProvider - Create
bootstrap/providers.phpfor remaining providers config/files simplified (many optional, publishable on demand)casts()method is now standard (not$castsproperty)- Framework middleware (CSRF, auth, etc.) handled natively — remove custom extensions that just call parent
- Health check route at
/up - Per-second scheduling
- Pest as default test framework (PHPUnit still fully supported)
- PHPUnit: ^10.0 → ^10.5/^11.0
- PHPUnit doc-comment annotations → PHP 8 attributes (
@Test→#[Test],@Group→#[Group]) - ⚠ BATTLE-TESTED: API routes using
webmiddleware group need->withRouting(then:)callback — L11'sapi:parameter assumes theapimiddleware group - ⚠ BATTLE-TESTED:
spatie/laravel-backupv9 adds required config keys — causes silentUndefined array keyerrors without config update - ⚠ BATTLE-TESTED: 14 files can be deleted (Kernels, Handler, providers, middleware that just extend framework defaults)
- PHP 8.2+ required (same as 11)
- Carbon 3 required (Carbon 2 dropped):
diffIn*methods return float instead of positive intcreateFromTimestampdefaults to UTC (wasdate_default_timezone_get())create/createFromFormatreturnnullinstead offalseon failureformatLocalized()/setUtf8()removed (useisoFormat())setWeekStartsAt()/setWeekEndsAt()removedaddMinutes()etc. no longer accept strings
- HasUuids trait generates v7 (ordered) instead of v4 — use
HasVersion4Uuidsto keep old behavior route:list --compactflag removed- Database grammar constructors require
Connectioninstance Connection::withTablePrefix()removedGrammar::setConnection()removed- New starter kit options (React/Vue/Livewire with WorkOS AuthKit)
- ⚠ BATTLE-TESTED: Usually zero code changes required — pure dependency bump. Audit Carbon usage:
Carbon::now()andCarbon::parse()are safe. Only problematic if usingdiffIn*,formatLocalized, orsetWeekStartsAt
Available from Laravel 9.19+. Required for new Laravel 10+ projects. node-sass death forces this migration at L9.
- Replace
webpack.mix.jswithvite.config.js - Install
vite+laravel-vite-plugin(+@vitejs/plugin-vue2for Vue 2,@vitejs/plugin-vuefor Vue 3) - Replace
mix()helper with@vite()directive in Blade - Update
package.jsonscripts:dev→vite,build→vite build - Environment variable prefix:
MIX_→VITE_ - Convert all
require()→import(CommonJS → ESM) - ⚠ BATTLE-TESTED: ESM strict mode gotchas — implicit globals, double
super(), missingconstonfor...of - ⚠ BATTLE-TESTED: jQuery must be set up as ESM side-effect module, imported before Bootstrap
- ⚠ BATTLE-TESTED: Font/asset paths must be absolute for Vite (
/fontsnot../fonts) - ⚠ BATTLE-TESTED: reCAPTCHA needs
render=explicit—type="module"defers execution - ⚠ BATTLE-TESTED: Pin exact versions for CSS-sensitive packages (SweetAlert2 caret ranges resolve to visually different versions)
| Package | Laravel 6 | Laravel 8 | Laravel 9 | Laravel 10 | Laravel 11 | Laravel 12 |
|---|---|---|---|---|---|---|
| horizon | ^4.0 | ^5.0 | ^5.x | ^5.x | ^5.x | ^5.x |
| dusk | ^5.0/^6.0 | ^6.0 | ^7.0 | ^7.x | ^8.0 | ^8.x |
| sanctum | — | ^2.0 | ^2.x/^3.0 | ^3.x | ^4.0 | ^4.x |
| cashier | ^10.0 | ^12.0 | ^13.0/^14.0 | ^14.x/^15.0 | ^15.x | ^16.x |
| ui | — | ^3.0 | ^3.x/^4.0 | ^4.x | ^4.x | ^4.x |
| guzzle | ^6.3/^7.0 | ^6.3/^7.0 | ^7.0 | ^7.0 | ^7.0 | ^7.0 |
| phpunit | ^8.0 | ^8.5/^9.0 | ^9.0 | ^10.0 | ^10.5/^11.0 | ^11.0 |
| collision | ^3.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0 | ^8.6 |
| mockery | ^1.0 | ^1.4 | ^1.4 | ^1.4 | ^1.6 | ^1.6 |
| backup (spatie) | ^6.0 | ^7.0/^8.0 | ^8.0 | ^8.x | ^9.0 | ^9.x |
| tinker | ^1.0/^2.0 | ^2.0 | ^2.7 | ^2.8 | ^2.9 | ^2.10 |
| Abandoned Package | Replacement | Shift |
|---|---|---|
fzaninotto/faker |
fakerphp/faker |
6.0 |
fideloper/proxy |
Built into framework | 9.0 |
fruitcake/laravel-cors |
Built into framework | 9.2 |
tightenco/mailthief |
Mail::fake() |
Any (L5.4+) |
soapbox/laravel-formatter |
PHP SimpleXML ext |
Any |
themsaid/laravel-mail-preview |
spatie/laravel-mail-preview |
Any |
stechstudio/laravel-vfs-adapter |
Storage::fake() |
Any (L5.4+) |
doctrine/dbal |
Optional in L10+ | 10.0 |
Comments are disabled for this gist.