Laravel upgrade guide

How to upgrade Laravel to the latest version

Upgrading Laravel is not a single leap — it is a disciplined series of one-version hops, each paired with the PHP version that release demands. Do it in order and every step is small and mechanical. Try to skip ahead and you inherit years of compounded breaking changes with no map back. This guide walks the whole ladder from Laravel 9 to the current release, with the exact config diffs, the PHP coupling that trips most teams, and an honest account of where it gets tedious.

The stakes: EOL is coupled to PHP

Laravel follows a fixed support window. Each major gets roughly 18 months of bug fixes and 2 years of security fixes from its release, and a new major ships every year in Q1. In practical terms that means Laravel 9 (Feb 2022) and Laravel 10 (Feb 2023) are already past their security end-of-life, and only Laravel 11 and 12 still receive patches. Running an EOL framework version is not just a missing-feature problem — unpatched security advisories in the framework, in first-party packages, and in the underlying runtime simply never reach you.

The runtime is the catch. Each Laravel major raises its minimum PHP, so an old Laravel almost always sits on an old, equally-EOL PHP. You cannot bump Laravel without first satisfying its PHP floor, which is why a Laravel upgrade is usually a PHP upgrade wearing a trench coat. Check where you actually stand with our EOL checker before you plan anything, and read the PHP upgrade guide first if your runtime is behind.

The version-and-PHP ladder

This is the whole coupling in one place. Read it top to bottom — your target's minimum PHP is a hard gate, and if your current PHP is below the next rung, PHP goes first.

LaravelMinimum PHPHeadline changes
9.xPHP 8.0+Symfony 6.0 components, Flysystem 3.0, new default landing route, anonymous migration stubs by default
10.xPHP 8.1+Native argument & return types across the framework, invokable/ValidationRule validation rules, Process facade
11.xPHP 8.2+Streamlined skeleton (bootstrap/app.php, no HTTP/Console kernels), per-second rate limiting, built-in health route /up, SQLite default
12.xPHP 8.2+Current/maintenance release — deliberately minimal breaking changes, refreshed starter kits (React, Vue, Livewire), dependency bumps

The one rule that matters: upgrade exactly one major version at a time. For every hop, open the official upgrade guide for that specific release and skim the GitHub upgrade diff for the app skeleton. Jumping two or more majors at once means debugging overlapping breaking changes with no clean intermediate to bisect against — you lose the ability to isolate which change broke what.

Per-version highlights

Laravel 9

The Symfony jump (5 to 6) is the real work here — it pulls in new HTTP, console, and mailer internals, and forces PHP 8.0. Flysystem moves 1.x to 3.x, so custom filesystem drivers and a handful of Storage return values change. The default welcome route and route file layout were refreshed, and anonymous class migrations became the default stub.

Laravel 10

Mostly a quality release: the framework gained native PHP type declarations on arguments, return values, and (where possible) properties, which can surface type mismatches in your own subclasses and overrides. The minimum PHP rises to 8.1. make:rule now scaffolds invokable rules implementing ValidationRule instead of the old Rule interface, and the new Process facade replaces ad-hoc Symfony Process usage.

Laravel 11

The big structural release. The application skeleton is drastically slimmed: app/Http/Kernel.php and app/Console/Kernel.php are gone, middleware and exceptions are now configured fluently in a new bootstrap/app.php, config files are trimmed to what you override, and there is a single routes/web.php + routes/console.php by default. PHP 8.2 becomes the floor. You also get per-second rate limiting, a built-in health-check route (/up), and SQLite as the default database.

Laravel 12

The current maintenance release. Its headline feature is restraint — Laravel 12 is intentionally light on breaking changes, focused on dependency updates and new first-party starter kits. If you are already on 11 with a clean skeleton, the hop to 12 is typically a composer bump and a test run.

How to upgrade, step by step

Run this loop once per major version. Do not batch two majors into one pass.

  1. Get tests green and pin Composer. A red suite before you start guarantees you cannot tell your changes from pre-existing breakage. Commit a clean baseline on a dedicated branch and record your current lockfile so you can roll back.
  2. Bump exactly one major in composer.json. Raise laravel/framework and the php constraint to the next rung, then resolve.
  3. Apply that release's upgrade guide. Work through the official guide's "high/medium impact" items by hand, or run Laravel Shift to automate the mechanical edits and produce a reviewable diff.
  4. Update config and skeleton diffs. Diff your app against the matching version of laravel/laravel — this is where the Laravel 11 bootstrap/app.php restructure lives.
  5. Update first- and third-party packages to matching majors. Sanctum, Horizon, Telescope, Nova, Cashier, Livewire, and community packages each have a version aligned to the Laravel major. Mismatched majors are the most common cause of a failed resolve.
  6. Run the test suite and Rector. Fix failures, then let a Rector ruleset mop up deprecations and type changes across the codebase.
  7. Repeat for the next major. Merge the passing branch, then start the loop again at the next rung.

Step 2 — the Composer bump (Laravel 10 → 11)

"require": {
    "php": "^8.1",
    "php": "^8.2",
    "laravel/framework": "^10.0",
    "laravel/framework": "^11.0",
    "laravel/sanctum": "^4.0"
}
composer update laravel/framework --with-all-dependencies

Step 4 — the Laravel 11 skeleton restructure

Middleware that lived in app/Http/Kernel.php moves into the fluent bootstrap/app.php. The kernel files are deleted entirely.

// app/Http/Kernel.php  (removed in Laravel 11)
protected $middleware = [
    \App\Http\Middleware\TrustProxies::class,
    \Illuminate\Http\Middleware\HandleCors::class,
];
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->trustProxies(at: '*');
    })
    ->withExceptions(function (Exceptions $exceptions) {})
    ->create();

Per-second rate limiting is a small but welcome addition in the same release:

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60);
    return Limit::perSecond(10);
});

Invokable validation rules (Laravel 10)

The old Rule interface still works, but new rules use ValidationRule with a single validate method and a $fail closure:

class Uppercase implements Rule
{
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }
    public function message() { return 'The :attribute must be uppercase.'; }
}
class Uppercase implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}

Where it actually gets hard

The framework upgrade is rarely the bottleneck. The pain lives in your dependencies and your own code: a package with no release for the target major forces you to fork, replace, or wait; native type declarations in Laravel 10 break subclasses that overrode methods with looser signatures; and the Laravel 11 skeleton move means any package or tooling that assumed the kernel files exist needs adjusting. Budget most of your time for the ecosystem, not the core.

Frequently asked questions

Can I jump from Laravel 8 straight to 12?

No. Upgrade one major at a time — 8 → 9 → 10 → 11 → 12 — running the test suite between each hop. Each release's breaking changes are documented and tooled for a single-version transition. Skipping intermediate versions stacks unrelated breakages on top of each other and removes any clean point to bisect against, turning a series of small, verifiable steps into one large, unverifiable one.

Do I need to upgrade PHP first?

Usually yes. Every Laravel major raises its minimum PHP (8.0 for 9, 8.1 for 10, 8.2 for 11 and 12), and Composer will refuse to install a framework version your runtime cannot satisfy. If your PHP is below the next rung's floor, upgrade PHP first — ideally getting the app green on the new PHP before touching the framework constraint. See the PHP upgrade guide.

What is Laravel Shift?

Laravel Shift is a paid, automated upgrade service that applies the mechanical parts of each release's upgrade guide for you and opens a pull request with the changes. It handles config diffs, skeleton restructures, and common code migrations, leaving you to review the PR and resolve anything project-specific. It does not replace running your own tests or updating third-party packages, but it removes most of the tedious, error-prone hand edits — especially valuable for the Laravel 11 skeleton move.

Should I use Rector as part of the upgrade?

Yes, after the framework bump and once your suite runs. Rector's Laravel and PHP rule sets can automatically rewrite deprecated calls, apply new type declarations, and modernise syntax across the whole codebase in one pass, which is far faster and more consistent than hand-editing. Treat its output as a reviewable diff, not a black box.

How do I know which version I'm on is still supported?

Check the release against Laravel's support policy: bug fixes for roughly 18 months and security fixes for 2 years from release. As a rule of thumb at the time of writing, only Laravel 11 and 12 receive support; 9 and 10 are past security EOL. Run your app through the EOL checker to confirm your framework, PHP, and key packages in one place.

Don't want to do this by hand?

Mira runs this exact migration for you — detect, modernize, add tests, run a security pass — on your own servers, in hours, not months.

Scan your repo free → Check your version's EOL →