Skip to content

Instantly share code, notes, and snippets.

@furey
Last active March 5, 2026 01:28
Show Gist options
  • Select an option

  • Save furey/f04081b7aa6bf0da4e81420cd2e91dcb to your computer and use it in GitHub Desktop.

Select an option

Save furey/f04081b7aa6bf0da4e81420cd2e91dcb to your computer and use it in GitHub Desktop.

laravel-shift Installation & Usage Guide

Prerequisites

Installation

One-liner:

curl -sL "https://gist.githubusercontent.com/furey/f04081b7aa6bf0da4e81420cd2e91dcb/raw/claude-code.skill.laravel-shift.installer.sh" | bash

Or 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.md

Verify the skill is detected:

claude /laravel-shift

Usage

Run 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).

What to Expect

  • 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 Dockerfile and docker-compose.yml with 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.

Updating

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]

Laravel Shift — Incremental Version Migration

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.

Commands

  • /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.


Phase 1: Plan (/laravel-shift plan)

1.1 Detect Current State

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/predis as "suggested", erusev/parsedown as transitive)
  • Frontend stack (package.json: Vue/React/Alpine, Bootstrap/Tailwind, Mix/Vite)
  • Node version (Volta, .nvmrc, or package.json engines)

1.2 Research Target

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.

1.3 Map Upgrade Path

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)

1.4 Detect Blockers

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 MixVite — required for Laravel 9.19+ (node-sass is dead, pre-built binaries removed)
  • Vue 2Vue 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/predis as "suggested")
  • Transitive dependencies — packages that may disappear between versions (e.g. erusev/parsedown)

1.5 Create Tracking Directory

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)

1.6 Ask User Decisions

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

Phase 2: Shift (/laravel-shift shift)

2.1 Pre-flight

  1. Read shift/CURRENT-STATE.md to determine current shift
  2. Read shift/<current>/requirements.md for the checklist
  3. Read shift/<current>/progress.md for what's already done
  4. Read shift/<current>/learnings.md for known gotchas
  5. Read version-changes.md in this skill directory for the current version boundary's breaking changes
  6. Create a git branch: shift/<N>-<description> (if not already on one)
  7. Tear down any previous shift's Docker stack: docker compose -f shift/<prev>/docker-compose.yml down
  8. Set up Docker environment (see 2.2)

2.2 Docker Environment

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 :8000
  • mysql — MySQL with healthcheck, persisted volume
  • redis — 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 down

Troubleshooting: 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.

2.3 Execute

Work through the requirements checklist methodically:

  • Complete one sub-task at a time
  • Update progress.md after 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 -W for 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.

2.4 Validation

After all sub-tasks complete:

  • docker compose run --rm app composer install succeeds without errors
  • docker compose run --rm -e APP_ENV=testing -e DB_CONNECTION=sqlite -e DB_DATABASE=:memory: -e MAIL_MAILER=log app vendor/bin/phpunit passes
  • App boots: docker compose exec app php artisan route:list loads without errors
  • docker compose run --rm app composer audit shows 0 security advisories
  • No deprecated/removed code remains (grep verification)
  • Update learnings.md with 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.

2.5 Post-shift

  1. Update shift/<current>/progress.md — mark all complete
  2. Update shift/CURRENT-STATE.md — advance to next shift
  3. Prompt user to review and commit the changes

Phase 3: Resume (/laravel-shift resume)

For when a new LLM conversation picks up mid-upgrade:

  1. Read shift/README.md — full project context
  2. Read shift/CURRENT-STATE.md — where we are
  3. Read shift/<current>/requirements.md — what needs doing
  4. Read shift/<current>/progress.md — what's done
  5. Read shift/<current>/learnings.md — known issues
  6. Read version-changes.md in this skill directory for the current version boundary
  7. Continue from where the previous conversation left off

Phase 4: Status (/laravel-shift status)

Read and summarise:

  • shift/CURRENT-STATE.md
  • Current shift's progress.md
  • Overall progress (which shifts are done vs pending)

Phase 5: Post-Migration Cleanup

After ALL shifts are complete:

  1. Move final shift's Dockerfile and docker-compose.yml to repo root as the canonical dev environment
  2. Update README to reference root Docker files (no more docker compose -f shift/N/...)
  3. Optionally aggregate all shift/*/learnings.md into a single analysis document
  4. Remove the shift/ directory from the repo
  5. Merge the final shift branch into main/master

Safety Rules

Email Safety (CRITICAL)

  • NEVER run tests that could send real emails without explicit user confirmation
  • Host .env bleeds into Docker containers via volume mount — production email addresses can receive test emails
  • Always set MAIL_MAILER=log (L9+) or MAIL_DRIVER=log (L8-) in docker-compose.yml
  • Gate email-sending tests behind @group email and 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

Docker Environment Safety

  • APP_ENV=testing MUST be passed explicitly via -e flag (Docker env vars override phpunit.xml)
  • --no-reload flag on artisan serve is 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 down previous shift stack before starting new one (port conflicts)
  • Composer 1 is dead — Packagist dropped support. Always use composer:2

Rules

  • One shift at a time. Never skip versions.
  • Always use composer update -W for 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.md immediately 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).

Laravel Version Changes Reference

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.

5.5 → 5.6

  • PHP: 7.0 → 7.1.3
  • Logging: config/app.php log settings → new config/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

5.6 → 5.7

  • Email verification feature added (optional MustVerifyEmail interface)
  • resources/assets/ convention changed to resources/js/ + resources/css/
  • Blade::if() directive available
  • Paginator URLs now use https if request is secure
  • Input facade deprecated (use Request)
  • Dusk: ^2.0 → ^3.0/^4.0

5.7 → 5.8

  • Carbon 1 → Carbon 2 (date serialization may change)
  • env() helper no longer works outside config files (cache:config issue)
  • $dates property deprecated → use $casts with datetime
  • String/Array helpers deprecated (still work, removed in 6.0)
  • orWhere closures change behavior with Eloquent scopes
  • Mockery: ~0.9 → ^1.0
  • setUp() methods need : void return type
  • $defer property → DeferrableProvider interface
  • ⚠ BATTLE-TESTED: DeferrableProvider interface doesn't exist until 5.8 vendor is installed — change code AFTER composer 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

5.8 → 6.0 (MAJOR)

  • ALL global string/array helpers REMOVED: array_getArr::get, str_slugStr::slug, title_caseStr::title, camel_caseStr::camel, array_setArr::set, array_exceptArr::except, array_onlyArr::only, etc.
  • Illuminate\Support\Facades\Input fully removed
  • fzaninotto/faker abandoned → fakerphp/faker
  • Illuminate\Foundation\Auth\User gains MustVerifyEmail contract
  • BelongsToMany::addConstraints method signature changed
  • Lazy collections introduced
  • Arr::wrap, Arr::random added
  • ⚠ BATTLE-TESTED: Exception handler ExceptionThrowable is NOT required in 6.x — base handler still uses Exception. Safe to defer to 7.x/8.x shift
  • ⚠ BATTLE-TESTED: Transitive dependencies can silently disappear — e.g. erusev/parsedown was transitive in 5.8 but gone in 6.0. Add any used transitive deps to composer.json explicitly
  • ⚠ BATTLE-TESTED: composer update -W required for major framework bumps (transitive dependency conflicts)

6.x → 7.0

  • config/cors.php configuration file added
  • RouteServiceProvider::$namespace property deprecated
  • Date serialization: Carbon::toJSON() now uses toIso8601String() — add serializeDate() to models if API output changes
  • Blade::component() method signature changed
  • Symfony\Component\HttpFoundation\Response contract changes
  • Custom rule passes method now receives rule attribute
  • HttpException::$statusCode now protected
  • Str::of() fluent strings available

7.x → 8.0 (MAJOR)

  • Model factories: closure $factory->define() → class-based Factory classes with HasFactory trait
  • Seeders: database/seeds/database/seeders/ + Database\Seeders namespace
  • Route syntax: 'Controller@method'[Controller::class, 'method'] (or keep with explicit $namespace)
  • RouteServiceProvider::$namespace removed (must use fully-qualified names or set explicitly)
  • Exception handler: register() method replaces report()/render() as primary pattern
  • Pagination default: Bootstrap → Tailwind (override with Paginator::useBootstrap())
  • fideloper/proxy removed — TrustProxies middleware built into framework
  • retryAfter property → backoff() method on jobs
  • timeoutAt property → retryUntil() method on jobs
  • Queue table method naming changes
  • $dates property 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 — requires laravel/ui package
  • ⚠ BATTLE-TESTED: php artisan serve strips Docker env vars in L8+ for hot-reload. ServeCommand sets all env vars to false except a small allowlist (APP_ENV, LARAVEL_SAIL). Docker-compose settings become invisible to HTTP requests → host .env wins → wrong SESSION_DOMAIN → 419 CSRF. Fix: always use --no-reload flag
  • ⚠ BATTLE-TESTED: predis/predis is a "suggested" dependency, not required — may be used in production without being in composer.json. Add explicitly if Redis is used
  • ⚠ BATTLE-TESTED: assertArraySubset removed in PHPUnit 9 — replace with assertArrayHasKey + assertContains

8.x → 9.0 (MAJOR)

  • PHP 8.0+ required
  • Flysystem 3.0: major filesystem API changes
    • Storage methods throw exceptions instead of returning false
    • Custom filesystem adapters must be completely rewritten
    • Filesystem::url() behavior changes
    • put method returns true/throws instead of false
  • Symfony 6.0 components
    • TrustProxies: HEADER_X_FORWARDED_ALL removed — use bitwise OR of individual constants
  • SwiftMailer → Symfony Mailer
    • config/mail.php rewrite: single driver → mailers array format
    • MAIL_DRIVERMAIL_MAILER everywhere (env, config, docker-compose)
  • doctrine/dbal → ^3.0
  • Anonymous migration classes (optional)
  • Enum casting available
  • str() helper and to_route() helper added
  • Bus::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, missing const on for...of loops
  • ⚠ BATTLE-TESTED: SweetAlert2 v7 must import from sweetalert2/dist/sweetalert2.js (UMD) — Vite resolves jsnext field to ESM source with class constructor incompatibilities
  • ⚠ BATTLE-TESTED: SweetAlert2 exact version pinning needed — ^7.1.2 resolves 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=explicit mode — Vite type="module" defers script execution, auto-render fires before Vue mounts
  • ⚠ BATTLE-TESTED: spatie/flysystem-dropbox v3 requires PHP 8.2 — must use v2 for PHP 8.0

9.x → 10.0

  • PHP 8.1+ required
  • Method return types added throughout framework (any overridden method needs return type)
  • Invokable validation rules replace Rule interface
  • Process facade for external commands
  • $casts can be method: protected function casts(): array
  • doctrine/dbal no longer required (unless using change() in migrations)
  • $routeMiddleware$middlewareAliases in Http Kernel
  • Remove empty registerPolicies() and stale $policies
  • PHPUnit: ^9.0 → ^10.0 (run --migrate-configuration for XML schema)
  • Minimum Composer 2.2+
  • ⚠ BATTLE-TESTED: Cleanest upgrade boundary — often passes tests on first attempt with no debugging

10.x → 11.0 (MAJOR)

  • PHP 8.2+ required
  • App structure overhaul:
    • app/Http/Kernel.php removed → middleware in bootstrap/app.php ->withMiddleware()
    • RouteServiceProvider removed → routing in bootstrap/app.php ->withRouting()
    • Console/Kernel.php removed → schedule in routes/console.php via Schedule facade
    • Exception handling in bootstrap/app.php ->withExceptions()
  • Service provider consolidation: most providers merged into AppServiceProvider
  • Create bootstrap/providers.php for remaining providers
  • config/ files simplified (many optional, publishable on demand)
  • casts() method is now standard (not $casts property)
  • 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 web middleware group need ->withRouting(then:) callback — L11's api: parameter assumes the api middleware group
  • ⚠ BATTLE-TESTED: spatie/laravel-backup v9 adds required config keys — causes silent Undefined array key errors without config update
  • ⚠ BATTLE-TESTED: 14 files can be deleted (Kernels, Handler, providers, middleware that just extend framework defaults)

11.x → 12.0 (MAINTENANCE)

  • PHP 8.2+ required (same as 11)
  • Carbon 3 required (Carbon 2 dropped):
    • diffIn* methods return float instead of positive int
    • createFromTimestamp defaults to UTC (was date_default_timezone_get())
    • create/createFromFormat return null instead of false on failure
    • formatLocalized()/setUtf8() removed (use isoFormat())
    • setWeekStartsAt()/setWeekEndsAt() removed
    • addMinutes() etc. no longer accept strings
  • HasUuids trait generates v7 (ordered) instead of v4 — use HasVersion4Uuids to keep old behavior
  • route:list --compact flag removed
  • Database grammar constructors require Connection instance
  • Connection::withTablePrefix() removed
  • Grammar::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() and Carbon::parse() are safe. Only problematic if using diffIn*, formatLocalized, or setWeekStartsAt

Frontend Migration: Mix → Vite

Available from Laravel 9.19+. Required for new Laravel 10+ projects. node-sass death forces this migration at L9.

  • Replace webpack.mix.js with vite.config.js
  • Install vite + laravel-vite-plugin (+ @vitejs/plugin-vue2 for Vue 2, @vitejs/plugin-vue for Vue 3)
  • Replace mix() helper with @vite() directive in Blade
  • Update package.json scripts: devvite, buildvite build
  • Environment variable prefix: MIX_VITE_
  • Convert all require()import (CommonJS → ESM)
  • ⚠ BATTLE-TESTED: ESM strict mode gotchas — implicit globals, double super(), missing const on for...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 (/fonts not ../fonts)
  • ⚠ BATTLE-TESTED: reCAPTCHA needs render=explicittype="module" defers execution
  • ⚠ BATTLE-TESTED: Pin exact versions for CSS-sensitive packages (SweetAlert2 caret ranges resolve to visually different versions)

Package Compatibility Quick Reference

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

Common Abandoned Package Replacements

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.