Skip to content

Laravel 12 New Features: What Actually Changed Worth Caring About

Laravel 12 New Features: What Actually Changed Worth Caring About

Confession: I blocked off a whole Saturday to upgrade a chunky Laravel 11 app to Laravel 12. I had coffee, snacks, a fresh branch, and the mental posture of a man preparing for war. Three hours later I was bored. The composer update finished, the tests passed, deployment went green, and I stared at the screen wondering if I had upgraded anything at all.

That is basically the Laravel 12 story. It is a tiny release pretending to be a major one. There are a few real changes worth knowing about, a couple of things that quietly improved underneath, and one big “where’s the beef” moment that I think the framework deserves a little gentle pushback on. Here is what I actually found after running 12 in production for a few weeks.

What Laravel 12 actually ships

Laravel 12 dropped in February 2025 as the next in the yearly major cadence. The headline from the official release notes is honest, which I appreciate: this is a “maintenance” release. The internal skeleton got bumped, dependencies got newer, and a handful of starter kits got rewritten. There is no big paradigm shift in the framework core.

The biggest concrete changes:

  • New first-party starter kits for React, Vue, and Livewire, all wired with WorkOS AuthKit as an optional auth backend.
  • A move to Carbon 3 as the only supported date library (Carbon 2 is out).
  • PHP 8.2 is now the minimum, with continued support for 8.3 and 8.4.
  • Stricter dependency constraints across the first-party packages, which catches some upgrade issues earlier.
  • A pile of small DX improvements: better dump-server output, cleaner exception pages, a few new test helpers.

If you came in expecting the equivalent of the Laravel 11 streamlined application structure, you are going to leave a little hungry. There is no new top-level concept here.

The upgrade from 11 to 12 is genuinely boring

In the best possible way. I bumped versions in composer.json, ran composer update, ran the test suite, and dealt with maybe four lines of code total across a 60k LOC app. Compare that to the Laravel 10 to 11 jump, which restructured the entire app/ directory and rewrote how providers and middleware register, and 12 feels like a service patch wearing a tuxedo.

Here is what a typical 11 to 12 diff looks like for an existing app. The before code in 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) {
        // ...
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // ...
    })->create();

The after code is the same. That is the joke. The structure introduced in Laravel 11 is the same one Laravel 12 uses, and the upgrade guide really does read like “bump versions, ship”.

The few real code changes most apps will hit:

// Before (Carbon 2 patterns still tolerated)
$user->created_at->diffInDays(now());

// After (Carbon 3 returns floats by default, casts matter now)
(int) $user->created_at->diffInDays(now());

Carbon 3 returning floats from diff* methods is the single thing that bit me. If you have logic comparing dates as integers, audit those call sites. The Carbon 3 upgrade notes cover the full list.

The starter kit story is where the real work went

The framework core barely moved, but the starter kits got a full rewrite. The new kits use Inertia 2, Tailwind v4, shadcn-style components, and ship with a WorkOS-backed auth flow you can swap in without touching application code. The React kit in particular is closer to a production scaffold than the old Breeze starter, which was always a “delete two-thirds of this before you ship” situation.

If you have not picked a frontend approach for a fresh Laravel app yet, the new kits are worth a look. I wrote about how I think about that choice in my post on picking a Laravel frontend in 2026, and 12’s starter kits change my recommendation slightly: the React kit is now strong enough that I would default to it for anything beyond a small admin tool.

One thing I want to call out: the WorkOS auth integration is opt-in, not the default. Some commentary online made it sound like Laravel was getting into bed with WorkOS. It is not. The default starter still ships with regular session auth and a local users table. WorkOS is a one-line config swap if you want it. That is the right design.

Pennant, Pulse, and the other “this finally feels stable” wins

The Laravel 12 cycle is also when a bunch of the first-party packages stopped feeling like beta software. Pennant is the one I keep recommending to people. It is a feature flag system that integrates with Eloquent and queues without you having to think about it.

A simple gated feature looks like this:

// AppServiceProvider.php
use Laravel\Pennant\Feature;

Feature::define('new-billing-flow', fn (User $user) => match (true) {
    $user->isInternal() => true,
    $user->onPlan('enterprise') => Lottery::odds(1, 100),
    default => false,
});

Then in a controller:

if (Feature::for($user)->active('new-billing-flow')) {
    return $this->newBillingFlow($user);
}

return $this->legacyBillingFlow($user);

The thing that makes Pennant click for me is that flag values are cached per-request, so you can check the same flag fifty times in a render path without forty-nine extra queries. I used to roll this myself with a custom service and Redis. I have deleted that service.

Pulse, the first-party application monitoring tool, also matured a lot. It is not a Datadog replacement, but for “I want to see slow queries, slow jobs, and which routes are getting hammered, and I do not want to pay $400 a month”, it is now genuinely usable in production. Pair it with Reverb for realtime and you can keep a surprising amount of the modern Laravel stack first-party.

Where I think Laravel 12 fell short

I said I would push back a little, so here goes. The framework had a real opportunity in 12 to formalize a few things people keep asking for, and it punted on most of them.

The biggest miss for me: no first-party answer for typed config. Every Laravel codebase past a certain size ends up with someone writing a wrapper around config() that returns typed DTOs, because raw config('services.stripe.secret') returning mixed is annoying with modern static analysis. PHP 8.4 has the type system for this. The framework does not yet meet it halfway. I would have happily traded the third starter kit refactor for config() returning typed objects from a schema.

The second miss: the Eloquent ORM still does not have a great answer for relationships that span multiple databases. If you have read replicas plus a separate analytics database, the workarounds are the same workarounds we were using in Laravel 8. This is a genuinely hard problem, so I do not blame the team, but it is the place I most often wish for a first-party blessing.

I do not think either of these is a reason to skip the upgrade. I do think they are reasons to be patient with people who shrug at 12.

Should you upgrade?

Short answer: yes, but you can take your time. Laravel 11 still gets bug and security fixes under the framework’s support policy, so there is no fire under you. If you are on 10 or earlier, 12 is the right target because the 11 to 12 jump is trivial, and you get to skip the bigger 10 to 11 restructure mid-step (you still have to do it, but only once).

My order of operations when I do an upgrade like this:

  1. Make sure the test suite actually runs and passes on 11 first. A flaky test suite turns an easy upgrade into a guessing game.
  2. Bump to 12 in a branch, run composer update, run the test suite, and fix what surfaces.
  3. Audit Carbon 3 date math. Look specifically for diff* methods feeding into integer comparisons.
  4. Run Larastan at level 6 or higher. Tightened first-party type hints in 12 will surface real issues that 11 was tolerating.
  5. Deploy to staging, run it for a week, then ship. Do not pair the upgrade with a feature release. Boring upgrades stay boring when they are the only thing in the diff.

If you write Laravel for a living and want more posts like this, I keep notes on the rest of the modern PHP stack in my work archive, and the PHP 8.4 property hooks post pairs well with this one because most of the language-level wins for Laravel 12 apps come from PHP, not Laravel.

One concrete thing to do this week

Open your composer.json, change "laravel/framework": "^11.0" to "^12.0", run composer update --dry-run, and read the output. Not to upgrade right now. Just to see what would break. Fifteen minutes, no commit, full picture of the work in front of you. That is the most useful Laravel 12 task I can give you, because most of the surprise in any framework upgrade is dependency resolution, not the framework itself.