Skip to content

Laravel 12 new features: why the boring release is the one I wanted

Laravel 12 new features: why the boring release is the one I wanted

I’ll be honest: when Laravel 12 dropped and the community response was mostly “wait, that’s it?”, I felt relieved. After the sweeping changes in Laravel 11, the last thing I wanted was another round of rewriting half my application structure. Laravel 12 is a polish release. And sometimes polish is exactly what you need.

Short version for the impatient: new starter kits with WorkOS auth, a cleaner default skeleton, PHP 8.2 as the floor, and Symfony 7 under the hood. If you’re on Laravel 11 with PHP 8.2+, upgrading is close to painless. If you’re still on 10, buckle up a bit.

The starter kits got a real overhaul

The biggest visible change is the new starter kit system. Laravel Breeze and Jetstream still exist, but the new default starters ship with React, Vue, or Livewire plus WorkOS AuthKit for authentication. That means you get social login, passkeys, and MFA basically for free out of the box.

Here’s what setting up auth looked like in Laravel 11 with Breeze:

composer require laravel/breeze --dev
php artisan breeze:install react
npm install && npm run dev

That gave you a basic email/password flow. Adding social login meant pulling in Socialite, configuring OAuth providers, writing the callback routes yourself.

In Laravel 12, the new starter kit approach:

laravel new my-app --react
# WorkOS auth is preconfigured
# Social login, passkeys, MFA all included

The auth scaffold now comes with a real authentication provider behind it. I was skeptical about tying auth to WorkOS at first. But you can swap it out, and for new projects the time savings are significant. I set up a side project last month and had Google + GitHub login working in under ten minutes. No Socialite configuration, no callback controllers.

PHP 8.2 minimum actually matters

Laravel 12 requires PHP 8.2 at minimum. This sounds like a footnote, but it has practical consequences. The framework internals now use readonly classes, DNF types, and the Random\Randomizer API everywhere. More importantly for your code, you can stop writing defensive checks for features you’re already using.

I wrote about PHP 8.4 property hooks a few weeks back, and if you’re running 8.4 on a Laravel 12 project, you get to combine both. Property hooks with Eloquent models are interesting:

// Laravel 11 / PHP 8.1 pattern
class Order extends Model
{
    protected $casts = [
        'total_cents' => 'integer',
    ];

    public function getTotalAttribute(): float
    {
        return $this->total_cents / 100;
    }

    public function setTotalAttribute(float $value): void
    {
        $this->attributes['total_cents'] = (int) ($value * 100);
    }
}
// Laravel 12 / PHP 8.4 with property hooks
class Order extends Model
{
    protected $casts = [
        'total_cents' => 'integer',
    ];

    public float $total {
        get => $this->total_cents / 100;
        set (float $value) => $this->total_cents = (int) ($value * 100);
    }
}

Fewer magic methods, more explicit contracts. The getter/setter accessor pattern that Laravel developers have been writing for a decade now has a language-level replacement. You don’t have to use it, but once you do, the old approach feels like busywork.

The application skeleton lost weight

Laravel 11 already trimmed the skeleton significantly (goodbye, app/Http/Kernel.php). Laravel 12 continues that trend. The default routes/ directory is simpler, the config files that ship by default are fewer, and the .env.example is shorter.

The thing I appreciate most: bootstrap/app.php is even more streamlined. In Laravel 11, you got a fluent configuration approach:

// Laravel 11 bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // middleware config here
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // exception handling here
    })
    ->create();

Laravel 12 keeps this pattern but the defaults are smarter. If you don’t customize middleware or exceptions, you can skip those closures entirely. The framework figures out the sensible defaults. It’s a small thing, but new projects feel less cluttered.

Symfony 7 components under the hood

Laravel 12 moves to Symfony 7.x components. Unless you’re reaching directly into Symfony internals (and you probably shouldn’t be), this is transparent. But it means better performance in the HTTP kernel and console components, and access to some Symfony 7 features if you need them.

The one place I noticed a difference: the Process component. If you’re shelling out to external commands, the Symfony 7 Process class has better timeout handling and signal support. I had a queued job that occasionally hung on a PDF generation command, and switching to the updated process handling fixed it without any custom timeout logic.

Upgrading from 11 is actually quick

I upgraded two production apps. One took 20 minutes, the other about an hour (it had custom middleware registration that needed adjusting). The official upgrade guide covers the breaking changes, and there aren’t many.

The checklist that matters:

  1. Make sure you’re on PHP 8.2+. If you’re not, that’s your real upgrade.
  2. Update composer.json to require laravel/framework: ^12.0.
  3. Run composer update and fix any dependency conflicts.
  4. Check your middleware registration if you customized it.
  5. Run your test suite. If tests pass, you’re probably fine.

From Laravel 10, the jump is bigger because you’re crossing two major versions. The skeleton changes from 10 to 11 were substantial, and you need to handle those first. I’d suggest going 10 to 11, stabilizing, then 11 to 12.

Should you upgrade right now?

If you’re starting a new project: yes, use 12. The starter kits alone save enough time to justify it.

If you’re on 11 and your tests pass after a composer update: ship it. There’s nothing in 12 that will break a well-tested 11 app, and you get the Symfony 7 performance improvements for free.

If you’re on 10 or earlier: plan the upgrade, but don’t rush. Going from 10 to 12 in one weekend is doable for small apps, risky for larger ones. Budget a sprint for it.

The Laravel release cycle has settled into a rhythm where even-numbered releases are the calm ones. I’m good with that. Not every release needs to reinvent the framework. Sometimes you just want to composer update and get back to building the thing your users actually care about.

I write about PHP, Laravel, and other framework updates as part of my ongoing work covering modern web development. If you’re planning an upgrade, the PHP 8.4 property hooks post pairs well with this one.