Laravel 13 was released on March 17, 2026 — and it's one of the most developer-friendly releases yet. No architectural rewrites, no migration nightmares. Two headline features that clean up patterns you write every day, a PHP version bump, and several well-considered quality-of-life improvements across the framework.
Here's every confirmed feature with before/after code examples.
Laravel 13 — Feature Summary
| Feature | Type | Breaking? |
|---|---|---|
| PHP 8.3 minimum requirement | Requirement | Runtime only |
| PHP Attributes (15+ locations) | New | No — optional |
Cache::touch() |
New | No |
| Reverb database driver | New | No |
| Passkey authentication | New | No |
| Laravel AI SDK stable | New | No |
| Teams support in starter kits | New | No |
| MySQL DELETE…JOIN with ORDER BY / LIMIT | Improved | No |
| HTTP pool concurrency default = 2 | Improved | No |
| Symfony 7.4 and 8.0 support | Improved | No |
1. PHP 8.3 Minimum Requirement
Laravel 13 drops PHP 8.2 and requires PHP 8.3 as the minimum. This is the only change that affects your infrastructure — everything else is application-level and non-breaking.
PHP 8.3 brings typed class constants, improved json_validate(), readonly property improvements, and JIT optimizations. Laravel 13 removes old polyfills and backcompat code that existed purely to support PHP 8.2, making the framework leaner and faster.
Action required If your server runs PHP 8.2, you must upgrade it before upgrading to Laravel 13. Check with php -v. Laravel 12 stays supported until August 2026 — no rush.
2. PHP Attributes — The Headline Feature
This is the biggest developer experience change in Laravel 13. Instead of cluttering your models, jobs, and commands with a wall of class property declarations, you can now use native PHP #[Attribute] syntax. Fully optional, fully backward compatible — your existing code works exactly as before.
Models
Before — Laravel 12
class Invoice extends Model
{
protected $table = 'invoices';
protected $primaryKey = 'invoice_id';
protected $keyType = 'string';
public $incrementing = false;
protected $fillable = ['amount', 'status', 'user_id'];
protected $hidden = ['internal_notes'];
// Your actual model logic starts way down here...
}
After — Laravel 13
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
#[Table('invoices', key: 'invoice_id', keyType: 'string', incrementing: false)]
#[Fillable('amount', 'status', 'user_id')]
#[Hidden('internal_notes')]
class Invoice extends Model
{
// Clean. Your real logic is immediately visible.
}
Jobs
Before — Laravel 12
class ProcessPayment implements ShouldQueue
{
public $connection = 'redis';
public $queue = 'payments';
public $tries = 3;
public $timeout = 60;
}
After — Laravel 13
use Illuminate\Queue\Attributes\WithQueue;
#[WithQueue(connection: 'redis', queue: 'payments', tries: 3, timeout: 60)]
class ProcessPayment implements ShouldQueue
{
// Queue config declared, not buried in properties
}
Commands
Before — Laravel 12
class SendReportCommand extends Command
{
protected $signature = 'report:send {--force}';
protected $description = 'Send the weekly report email';
public function handle() { ... }
}
After — Laravel 13
use Illuminate\Console\Attributes\Command;
#[Command(signature: 'report:send {--force}', description: 'Send the weekly report email')]
class SendReportCommand extends Command
{
public function handle() { ... }
}
Also applies to: listeners, notifications, mailables, broadcast events, requests, and more — around 15 locations total across the framework.
3. Cache::touch() — Extend TTL Without Re-Fetching
Before Laravel 13, extending a cache item's expiry meant fetching the full value, then re-storing it — two network round trips and full payload transfer just to update a timestamp.
Before — Laravel 12 (inefficient)
// Three operations: get value, hold in memory, put it back
$value = Cache::get('user_session:123');
Cache::put('user_session:123', $value, now()->addHour());
After — Laravel 13
// One operation: just update the expiry
Cache::touch('user_session:123', 3600);
// Also works on specific store
Cache::store('redis')->touch('user_session:123', 3600);
Under the hood: Redis gets a single EXPIRE command. Memcached uses native TOUCH. Database driver runs a single UPDATE. No value retrieval, no payload transfer, no wasted bandwidth.
Where this matters most: sliding session expiration, active subscription windows, hot dashboard data, API rate limit windows — anything where you extend TTL on every request at scale.
4. Reverb Database Driver — Real-Time Without Redis
Previously, scaling Laravel Reverb horizontally required Redis as a message broker. Laravel 13 introduces a database driver — horizontal scaling using your existing MySQL or PostgreSQL database instead.
config/broadcasting.php
'reverb' => [
'driver' => 'reverb',
'scaling' => [
'driver' => 'database', // New in Laravel 13, previously required Redis
],
],
5. Passkey Authentication
Passkeys — WebAuthn-based passwordless authentication — are now integrated into Laravel's starter kits and Fortify. Users authenticate with biometrics (Face ID, fingerprint) or hardware keys instead of passwords. Available out of the box on new Laravel 13 applications, no third-party packages required.
6. Laravel AI SDK — Out of Beta
The Laravel AI SDK is now stable as of March 17, alongside Laravel 13. It provides a first-class interface for integrating LLMs (OpenAI, Anthropic, and others) into Laravel applications — with proper queue support, error handling, and Laravel-native conventions.
7. Teams Support Returns to Starter Kits
Jetstream had Teams — the new starter kits didn't. Laravel 13 brings team-based multi-tenancy back to the official starter kits, with a cleaner implementation than Jetstream's version.
8. Other Improvements
MySQL DELETE…JOIN with ORDER BY / LIMIT
Laravel's MySQL grammar now compiles full DELETE…JOIN queries including ORDER BY and LIMIT clauses. Previously these were silently ignored, forcing developers to write raw SQL for complex delete operations.
Now works correctly in Laravel 13
// ORDER BY and LIMIT now apply correctly on DELETE...JOIN
User::join('orders', 'users.id', '=', 'orders.user_id')
->where('orders.status', 'cancelled')
->orderBy('users.created_at')
->limit(100)
->delete();
HTTP Pool Concurrency Default = 2
Previously, Http::pool() left concurrency at null, causing pooled requests to run serially — a silent footgun. Laravel 13 defaults concurrency to 2, so pooled requests are actually concurrent out of the box.
Symfony 7.4 and 8.0 Support
Laravel 13 supports Symfony 7.4 and 8.0, keeping the dependency chain modern and future-proof.
How to Upgrade to Laravel 13
- Update composer.json
"require": {
"php": "^8.3",
"laravel/framework": "^13.0"
}
- Run the upgrade
composer update
php artisan config:clear
php artisan cache:clear
php artisan view:clear
Important Make sure your server runs PHP 8.3 before running composer update or Composer will fail immediately. Always test on staging before upgrading production.
You can also use Laravel Shift for automated upgrades.
Frequently Asked Questions
When was Laravel 13 released?
Laravel 13 was released on March 17, 2026, as announced by Taylor Otwell at Laracon EU 2026.
Does Laravel 13 have breaking changes?
No breaking changes to application code. The only requirement change is PHP 8.3 minimum. All new features including PHP Attributes are optional and fully backward compatible.
Should I upgrade immediately?
No rush. Laravel 12 receives bug fixes until August 2026 and security fixes until February 2027. Test on staging, upgrade production when ready.
Will my packages break on Laravel 13?
Most major packages — Livewire, Inertia, Filament, Spatie — already have Laravel 13 support. Check each package's GitHub releases before upgrading.
Work with me
Need help with your Laravel architecture?
I build clean, maintainable Laravel backends for SaaS products — REST APIs, queue systems and production patterns like the one in this article.