Laravel 13: Every New Feature Explained with Code Examples (2026)

calendar_today July 18, 2026
person info@softcrony.com
folder DevOps

Laravel 13 was released March 17, 2026, announced live by Taylor Otwell at Laracon EU 2026. The headline promise was zero breaking changes — and it delivered. This is one of the most developer-friendly major releases in Laravel’s history.

This guide covers every confirmed new feature with before/after code examples, the requirements, the support timeline, and a step-by-step upgrade guide from Laravel 12.

Requirements

Requirement Laravel 12 Laravel 13
PHP Minimum 8.2 8.3
PHP Maximum 8.5 8.5
MySQL 8.0+ 8.0+
MariaDB 10.6+ 10.6+
SQLite 3.35+ 3.35+

The only infrastructure change: PHP 8.3 is now the minimum. If your server runs PHP 8.2, upgrade PHP before upgrading Laravel. Everything else is application-level and non-breaking.

Support Timeline

Version Release Bug Fixes Until Security Fixes Until
Laravel 11 Mar 12, 2024 Sep 3, 2025 Mar 12, 2026
Laravel 12 Feb 24, 2025 Aug 13, 2026 Feb 24, 2027
Laravel 13 Mar 17, 2026 Q3 2027 Q1 2028

Laravel 12 is fully supported until February 2027 — no emergency to upgrade. For new projects, start on Laravel 13 today.

Feature 1 — PHP Attributes for Everything

The biggest quality-of-life improvement in Laravel 13. PHP 8 Attributes are now an alternative to class properties for configuring Laravel components. This is a non-breaking change — existing property-based configuration continues to work.

Eloquent Models

Before (Laravel 12):

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'user_id';
    protected $keyType = 'string';
    public $incrementing = false;

    protected $hidden = ['password', 'remember_token'];

    protected $fillable = [
        'name',
        'email',
        'phone',
        'company_id',
    ];
}

After (Laravel 13):

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Table('users', key: 'user_id', keyType: 'string', incrementing: false)]
#[Hidden(['password', 'remember_token'])]
#[Fillable(['name', 'email', 'phone', 'company_id'])]
class User extends Model
{
    // Clean — no property clutter
}

All available model attributes:

#[Appends(['full_name', 'avatar_url'])]
#[Connection('mysql_readonly')]
#[Fillable(['name', 'email'])]
#[Guarded(['id', 'created_at'])]
#[Hidden(['password'])]
#[Table('users', key: 'user_id')]
#[Touches(['profile', 'posts'])]
#[Unguarded]
#[Visible(['name', 'email'])]

Queue Jobs

Before (Laravel 12):

class ProcessPodcast implements ShouldQueue
{
    public $connection = 'redis';
    public $queue = 'podcasts';
    public $tries = 3;
    public $timeout = 120;
    public $backoff = [1, 5, 10];
}

After (Laravel 13):

use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Backoff;

#[Connection('redis')]
#[Queue('podcasts')]
#[Tries(3)]
#[Timeout(120)]
#[Backoff([1, 5, 10])]
class ProcessPodcast implements ShouldQueue
{
    // No property clutter
}

All available queue attributes:

#[Backoff([1, 5, 10])]
#[Connection('redis')]
#[FailOnTimeout]
#[MaxExceptions(3)]
#[Queue('podcasts')]
#[Timeout(120)]
#[Tries(3)]
#[UniqueFor(3600)]

Console Commands

Before (Laravel 12):

class SendMailCommand extends Command
{
    protected $signature = 'mail:send {user} {--queue}';
    protected $description = 'Send a marketing email to a user';
}

After (Laravel 13):

use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Attributes\Description;

#[Signature('mail:send {user} {--queue}')]
#[Description('Send a marketing email to a user')]
class SendMailCommand extends Command
{
    // Signature and description live at the top, not buried in properties
}

Form Requests

#[RedirectTo('/dashboard')]
#[StopOnFirstFailure]
class CreateOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'product_id' => 'required|exists:products,id',
            'quantity'   => 'required|integer|min:1',
        ];
    }
}

API Resources

#[Collects(OrderResource::class)]
#[PreserveKeys]
class OrderCollection extends ResourceCollection {}

Attributes also work on Listeners, Notifications, Mailables, Broadcast Events, Factories, and Test Seeders — the full framework is covered.

Feature 2 — Cache::touch()

A small but frequently needed feature. The new Cache::touch() method extends a cached item’s TTL without fetching or re-storing the value.

Before (Laravel 12 — required a get + put):

// Old way — fetches the value unnecessarily
$value = Cache::get('user_session:123');
if ($value !== null) {
    Cache::put('user_session:123', $value, 3600); // Re-stores unnecessarily
}

After (Laravel 13):

// Just extend the TTL — no get, no re-store
Cache::touch('user_session:123', 3600);

// With a DateTime
Cache::touch('analytics_data', now()->addHours(6));

// Extend indefinitely (remove expiry)
Cache::touch('permanent_cache', null);

// Returns true if key exists, false if not
$extended = Cache::touch('user_session:123', 3600);
if (!$extended) {
    // Key expired before we could touch it — re-create it
}

This is implemented across all cache drivers: Array, APC, Database, DynamoDB, File, Memcached, Memoized, Null, and Redis. Redis uses a single EXPIRE command, Memcached uses TOUCH, and the database driver issues a single UPDATE.

Perfect for session extension, sliding cache windows, and keeping computed results alive while they’re being used.

Feature 3 — Laravel AI SDK (Stable)

The headline feature in the release notes is the new Laravel AI SDK. Previously in beta, it went production-stable with Laravel 13.

The AI SDK gives Laravel a first-party, provider-agnostic way to integrate AI — supporting Claude (Anthropic), OpenAI, and Gemini from a single unified API:

composer require laravel/ai
// config/ai.php
return [
    'default' => env('AI_PROVIDER', 'anthropic'),

    'providers' => [
        'anthropic' => [
            'api_key' => env('ANTHROPIC_API_KEY'),
            'model'   => env('ANTHROPIC_MODEL', 'claude-sonnet-4-5'),
        ],
        'openai' => [
            'api_key' => env('OPENAI_API_KEY'),
            'model'   => env('OPENAI_MODEL', 'gpt-4o'),
        ],
    ],
];
use Laravel\AI\Facades\AI;

// Simple completion
$response = AI::complete('Summarize this text: ' . $text);

// Chat with history
$response = AI::chat([
    ['role' => 'system', 'content' => 'You are a helpful assistant.'],
    ['role' => 'user', 'content' => 'What is Laravel?'],
]);

// Stream response
AI::stream('Write a product description for: ' . $product->name,
    function (string $chunk) {
        echo $chunk;
    }
);

// Switch provider on the fly
AI::provider('openai')->complete('Hello');
AI::provider('anthropic')->complete('Hello');

The SDK handles token counting, retry logic, streaming, and provider switching — without locking you to a specific AI company’s SDK.

Feature 4 — Passkey Authentication

Laravel 13 ships with first-party Passkey support — the WebAuthn-based authentication standard that replaces passwords with device biometrics (Face ID, fingerprint, Windows Hello).

composer require laravel/passkeys
php artisan passkeys:install
// Register a passkey
use Laravel\Passkeys\Facades\Passkeys;

// In your registration controller
public function startRegistration(Request $request)
{
    $options = Passkeys::generateRegistrationOptions($request->user());
    return response()->json($options);
}

public function finishRegistration(Request $request)
{
    $passkey = Passkeys::verifyRegistration(
        $request->user(),
        $request->all()
    );

    return response()->json(['passkey_id' => $passkey->id]);
}

// Authenticate with passkey
public function startAuthentication(Request $request)
{
    $options = Passkeys::generateAuthenticationOptions();
    return response()->json($options);
}

public function finishAuthentication(Request $request)
{
    $user = Passkeys::verifyAuthentication($request->all());
    Auth::login($user);

    return redirect('/dashboard');
}

Frontend integration uses the WebAuthn browser API — compatible with all modern browsers and devices with biometric sensors. No passwords stored, no password reset flows, no phishing risk.

Feature 5 — Queue::route()

The new Queue::route() method lets you define which queue and connection each job class uses from a single location in a service provider. Previously, teams either set queue properties on each job class or repeated the configuration at every dispatch site.

// Before — repeated at every dispatch
ProcessPodcast::dispatch($podcast)->onQueue('podcasts')->onConnection('redis');
SendWelcomeEmail::dispatch($user)->onQueue('emails')->onConnection('sqs');
GenerateReport::dispatch($report)->onQueue('reports')->onConnection('redis');

// After — define once in AppServiceProvider
use Illuminate\Support\Facades\Queue;

Queue::route([
    ProcessPodcast::class  => 'redis:podcasts',
    SendWelcomeEmail::class => 'sqs:emails',
    GenerateReport::class  => 'redis:reports',
]);

// Now just dispatch — routing is automatic
ProcessPodcast::dispatch($podcast);
SendWelcomeEmail::dispatch($user);
GenerateReport::dispatch($report);

Combined with the new PHP Attributes for queue configuration, this is the cleanest queue setup Laravel has ever had.

Feature 6 — JSON:API Support

Laravel 13 includes first-party support for the JSON:API specification. The new resource classes handle response object serialization, relationship inclusion, sparse fieldsets, links and compliant response headers automatically.

php artisan make:json-api-resource PostResource
use Illuminate\Http\Resources\Json\JsonApiResource;

class PostResource extends JsonApiResource
{
    public function toAttributes(Request $request): array
    {
        return [
            'title'      => $this->title,
            'content'    => $this->content,
            'published'  => $this->published_at?->toISOString(),
        ];
    }

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

    public function toLinks(Request $request): array
    {
        return [
            'self' => route('posts.show', $this->id),
        ];
    }
}

Response automatically follows JSON:API spec:

{
  "data": {
    "type": "posts",
    "id": "1",
    "attributes": {
      "title": "Laravel 13 Released",
      "content": "...",
      "published": "2026-03-17T09:00:00Z"
    },
    "relationships": {
      "author": {
        "data": { "type": "users", "id": "42" }
      }
    },
    "links": {
      "self": "https://api.example.com/posts/1"
    }
  }
}

Feature 7 — Typed Configuration

No more guessing whether a config value is a string, boolean, or integer:

// Before — could return string "true" or boolean true depending on env
$debug = config('app.debug'); // What type is this?

// Laravel 13 — typed retrieval
$debug = config()->boolean('app.debug');     // Always bool
$port  = config()->integer('database.port'); // Always int
$name  = config()->string('app.name');       // Always string
$hosts = config()->array('cache.stores');    // Always array

// Throws ConfigTypeMismatchException if type doesn't match
// Catches a whole class of bugs at boot time

Feature 8 — Middleware Simplification

Laravel 13 simplifies the middleware stack. The app/Http/Kernel.php file is gone (deprecated since Laravel 11, now removed). All middleware configuration lives in bootstrap/app.php.

// bootstrap/app.php — everything in one place
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware
        $middleware->append(ForceHttps::class);

        // Route group middleware
        $middleware->api(append: [
            EnsureApiToken::class,
        ]);

        // Middleware aliases
        $middleware->alias([
            'verified' => EnsureEmailIsVerified::class,
        ]);
    })
    ->create();

Feature 9 — Reverb Database Driver

Laravel Reverb (the WebSocket server) now includes a database driver — meaning you can run real-time WebSockets without Redis for smaller applications:

# .env — use database instead of Redis for WebSocket state
REVERB_DRIVER=database

# No Redis required for:
# - Channel presence tracking
# - Connection state
# - Message queuing (small scale)

For production at scale, Redis remains the recommended driver. The database driver is ideal for development environments and small-scale deployments where Redis is overkill.

Feature 10 — route:conflicts Command

The route:conflicts command detects overlapping routes — a common source of hard-to-debug bugs:

php artisan route:conflicts
+-------------------+--------+---------------------------+
| URI               | Method | Conflict                  |
+-------------------+--------+---------------------------+
| /users/{id}       | GET    | Overlaps with /users/new  |
| /products/{slug}  | GET    | Overlaps with /products/1 |
+-------------------+--------+---------------------------+

PHP 8.3 — What You Actually Gain

Upgrading to PHP 8.3 for Laravel 13 isn’t just a requirement — it brings real improvements:

// Typed class constants (PHP 8.3)
class Status
{
    const string ACTIVE   = 'active';
    const string INACTIVE = 'inactive';
    const int    MAX_RETRY = 3;
}

// json_validate() — no more json_decode() just to check validity
$valid = json_validate($jsonString); // true or false, no decode needed

// Readonly property improvements
class Order
{
    public function __construct(
        public readonly string $id,
        public readonly float  $total,
    ) {}
}

// Dynamic class constant fetch
$constant = Status::{$name}; // Valid in PHP 8.3

Laravel 12 vs Laravel 13 — Side by Side

Feature Laravel 12 Laravel 13
PHP minimum 8.2 8.3
Model configuration Class properties PHP Attributes (optional)
Job configuration Class properties PHP Attributes (optional)
Command signature Class property PHP Attribute (optional)
Cache TTL extension get + put Cache::touch()
AI integration Third-party packages First-party Laravel AI SDK
Passkeys Third-party only First-party support
Queue routing Per-dispatch Queue::route() central config
JSON:API Third-party First-party resources
Config types Untyped Typed retrieval
Middleware config Kernel.php (legacy) bootstrap/app.php only
WebSockets (Reverb) Redis only Redis + Database driver
Route conflicts Manual detection route:conflicts command

How to Upgrade from Laravel 12 to Laravel 13

Most applications upgrade in under 10 minutes. Here’s the process:

Step 1 — Check PHP Version

php -v
# Must show PHP 8.3.x or higher
# If not, upgrade PHP in your hosting panel (Hostinger/cPanel: PHP Configuration)

Step 2 — Check Package Compatibility

# Check which packages need updates
composer outdated

# Key packages to verify:
# - livewire/livewire
# - inertiajs/inertia-laravel
# - filament/filament
# - spatie/* packages
# - laravel/sanctum
# - laravel/horizon

Most major packages — Livewire, Inertia, Filament, Spatie — already have Laravel 13 support. Check each package’s GitHub releases page.

Step 3 — Update composer.json

{
    "require": {
        "php": "^8.3",
        "laravel/framework": "^13.0"
    }
}

Step 4 — Run Composer Update

composer update laravel/framework --with-dependencies

Step 5 — Run the Upgrade Command

php artisan upgrade

Laravel 13 includes an upgrade command that checks for common issues and guides you through any manual changes needed.

Step 6 — Clear and Test

php artisan optimize:clear
php artisan test
php artisan route:conflicts

Step 7 — Use Laravel Shift (Alternative)

For complex applications, Laravel Shift automates the upgrade — it opens a PR with atomic commits covering every change. Worth the small fee for large codebases.

Should You Upgrade Now?

New projects: Yes — always start new projects on the latest version. Laravel 13 is the current stable release.

Existing projects on Laravel 12: No emergency — Laravel 12 gets bug fixes until August 2026 and security fixes until February 2027. Plan the upgrade for your next maintenance window.

Existing projects on Laravel 11 or older: Upgrade now. Laravel 11’s security support ended March 2026.

The real reason to upgrade: PHP Attributes alone reduce boilerplate significantly. The Laravel AI SDK stable means AI integration is now a first-party, supported part of the framework. Queue::route() cleans up dispatch sites across large codebases. These are genuine improvements worth having.

If you need help upgrading a Laravel application to version 13, or want a new project built on the latest stack, our team at Softcrony is happy to help.

Leave a comment