Laravel Updates

What Changed in Laravel 13 That Most Developers Are Still Ignoring

Laravel 13 shipped with zero breaking changes, which means most developers upgraded and moved on. This covers the features that should actually change how you write code.

· 7 mins read
Summarize and analyze this article with:
Share this

Laravel 13 shipped on March 17, 2026, announced by Taylor Otwell at Laracon EU in Amsterdam. Zero breaking changes. If your project runs PHP 8.3, the upgrade is largely a composer update away.

That smoothness is also why most developers haven't actually changed how they write code since upgrading. They glanced at the release notes, saw nothing that broke their app, and moved on. Six months later they're still writing model configuration the old way, still provisioning Redis just to scale a handful of WebSocket connections, and still reaching for third-party packages for things the framework now handles natively.

Here's what actually matters in Laravel 13 and where it should change how you write code.


PHP 8.3 is now the floor

The only infrastructure requirement that touches your servers. Laravel 13 drops PHP 8.2 support entirely.

PHP 8.3 isn't a dramatic language update, but it brings typed class constants, a cleaner json_validate() that doesn't need a try-catch wrapper, better readonly property support, and JIT improvements that show real gains in compute-heavy operations.

One thing worth knowing: upgrade PHP first, then upgrade Laravel. Trying to do both at once via composer update while still on PHP 8.2 will cause dependency resolution failures. Handle the PHP version separately on staging, confirm it, then upgrade the framework.


PHP Attributes for model and class configuration

This is the feature that will clean up the most code over the next few years.

Laravel 13 introduces native PHP attribute syntax as an optional alternative to class property declarations across 15+ framework locations: models, controllers, jobs, commands, listeners, mailables, notifications, broadcast events, and more.

Before:

class Post extends Model
{
    protected $table = 'posts';
    protected $primaryKey = 'post_id';
    protected $fillable = ['title', 'body', 'user_id'];
    protected $hidden = ['internal_notes'];
}

After:

#[Table('posts', key: 'post_id')]
#[Fillable('title', 'body', 'user_id')]
#[Hidden('internal_notes')]
class Post extends Model {}

Controllers get the same treatment. Middleware and authorization rules can now sit directly on the class or method:

#[Middleware('auth')]
class CommentController
{
    #[Middleware('subscribed')]
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post) { ... }
}

Jobs too:

#[Tries(3)]
#[Backoff(30)]
#[Timeout(60)]
class ProcessInvoice implements ShouldQueue { ... }

None of this is mandatory. Existing property-based configuration still works. But on any new class you write, or any class you're already touching for another reason, attributes make the intent readable at the top without scrolling through property declarations.


The Laravel AI SDK is production-stable

The SDK went from beta to stable on the same day as Laravel 13. It's a first-party package that gives you a single provider-agnostic interface for text generation, agents, image creation, audio, and embedding generation.

A basic agent call:

use App\Ai\Agents\SalesCoach;

$response = SalesCoach::make()->prompt('Analyze this sales transcript...');
return (string) $response;

Image and audio generation follow the same pattern:

use Laravel\Ai\Image;
$image = Image::of('A product shot on white background')->generate();

use Laravel\Ai\Audio;
$audio = Audio::of('Welcome to your dashboard.')->generate();

The SDK supports OpenAI, Anthropic, and other providers. Switching providers is a config change. Retry logic, error normalization, and queue integration are handled for you.

The most practically useful addition is vector similarity search directly from the query builder, using PostgreSQL with pgvector:

$results = DB::table('articles')
    ->whereVectorSimilarTo('embedding', 'best practices for API security')
    ->limit(10)
    ->get();

Semantic search without an external service. For content-heavy applications, support tools, or anything where keyword search falls short, that's a real simplification.

If you've been wiring up LLM calls through third-party packages, there's no longer a good reason to. The SDK follows Laravel conventions throughout and handles the parts that are annoying to get right manually.


Reverb now runs without Redis

Scaling Laravel Reverb for real-time broadcasting previously required Redis as the underlying driver. Laravel 13 ships a native database driver for Reverb. Your existing MySQL or PostgreSQL connection works:

'reverb' => [
    'scaling' => [
        'driver' => 'database',
    ],
],

For most projects, this removes a real infrastructure dependency. One less server to provision, one less thing to configure, one less thing that can go wrong at 2am.

High-throughput apps with thousands of concurrent WebSocket connections should still use Redis. But that's a much smaller percentage of what most of us are building. If you're running a modest SaaS or an internal tool, the database driver is sufficient.


Cache::touch() for TTL extension

Previously, extending a cached item's TTL meant fetching the value and putting it back:

// Old — fetches the whole payload over the wire
$data = Cache::get('dashboard_data');
Cache::put('dashboard_data', $data, now()->addMinutes(30));

Laravel 13 adds Cache::touch(), which extends the TTL without retrieving the value:

Cache::touch('dashboard_data', now()->addMinutes(30));

Under the hood, Redis fires a single EXPIRE command, Memcached uses native TOUCH, and the database driver runs a single UPDATE. No value retrieval, no payload transfer.

This is worth thinking about anywhere you have sliding TTL patterns: session windows, rate limit resets, subscription status checks. Any case where you need to keep a cache entry alive based on activity without actually caring about the value. At scale, avoiding unnecessary reads adds up.

The method returns true if the key exists and was extended, false if the key doesn't exist.


Queue::route() for centralized job routing

Before this, defining where a job runs required either setting properties on each job class or repeating configuration at every dispatch call. Neither works well when queue topology changes.

Queue::route() lets you define routing rules in one place, usually a service provider:

Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
Queue::route(SendInvoice::class, connection: 'sqs', queue: 'billing');

When you need to move a job to a different connection, it's one line in one file. Infrastructure concern separated from business logic.


Native Passkeys

Passkeys are WebAuthn-based passwordless auth. Users authenticate with biometrics or hardware keys instead of passwords. Phishing-resistant by design because there's no credential to steal.

This is now built into the starter kits and Fortify. No third-party packages. Scaffolded on fresh Laravel 13 applications automatically.

If you're starting a new consumer-facing project in 2026, there's a legitimate case for skipping password auth entirely and shipping passkeys from day one. Better UX, better security, no password reset flows to build.


JSON:API Resources

Building a strictly JSON:API-compliant API in Laravel used to mean manually constructing resource objects, handling relationship inclusion, building sparse fieldsets, and getting the response headers right. Tedious.

Laravel 13 ships first-party JSON:API resource classes:

class ArticleResource extends JsonApiResource
{
    public function toAttributes(): array
    {
        return [
            'title' => $this->title,
            'body' => $this->body,
        ];
    }

    public function toRelationships(): array
    {
        return [
            'author' => AuthorResource::make($this->author),
        ];
    }
}

Response serialization, relationship inclusion, sparse fieldsets, links, and compliant response headers are handled automatically. If you're building an API where spec compliance matters to the team consuming it, this saves real work.


On upgrading

The upgrade from Laravel 12 to 13 is low-risk. Zero breaking changes means most applications won't touch their application code at all. PHP 8.3 is the only hard requirement.

Major packages including Livewire, Inertia, Filament, and Spatie already have Laravel 13 support. Check the GitHub releases on anything you depend on before upgrading, but the ecosystem is largely caught up.

Laravel 12 gets bug fixes until August 2026 and security patches until February 2027, so nothing is on fire. That said, if your test coverage is reasonable, there's no good reason to wait. The PHP Attributes alone are worth getting into your next project today.

Read next

Laravel 13 Passkey Authentication: Setup Guide for Modern Apps

Learn how to set up Passkey authentication in Laravel 13, step-by-step guide covering WebAuthn, Fortify integration, database setup, frontend wiring, and production tips for passwordless login.

· 8 mins read