TLDR - Key takeaways
- Laravel 13 shipped on March 17, 2026 - it requires PHP 8.3+ and carries minimal breaking changes, making most upgrades achievable in under an hour.
- The headline addition is the first-party Laravel AI SDK, which lands as production-stable alongside v13 and gives you a provider-agnostic API for text generation, agents, images, audio, and vector-search - all with zero external service required for basic use.
- PHP Attributes replace class properties across 15+ framework locations (models, commands, listeners, mailables…), yielding cleaner, more readable class definitions without touching existing code.
- Real-time, security, and caching see meaningful upgrades - Reverb's new database driver, native WebAuthn passkeys,
Cache::touch(), session-scoped cache, and a failover queue driver ship in one release.
Introduction - the release that actually matters for production
Every Laravel release gets its headline feature, its Laracon demo moment, and its inevitable "is this actually worth upgrading?" discourse. Laravel 13 has all three - but this cycle is different.
Unlike the architectural upheaval of Laravel 11 or the quality-of-life blitz of Laravel 12, version 13 is a foundation release dressed as a feature release. On the surface, you get the Laravel AI SDK, JSON:API resources, passkey authentication, and native vector search. Underneath, the framework drops support for PHP 8.2, aligns with Symfony 7.4/8.0, and removes years of backward-compatibility shims. The result is a leaner, faster, more secure codebase that sets the table for whatever Taylor Otwell has planned for Laravel 14.
Whether you are starting a greenfield project today or planning a migration roadmap for a mature application, this guide covers every confirmed Laravel 13 feature - with real code, real pitfalls, and real performance context.
1. PHP 8.3 - the prerequisite that pays dividends
Laravel 13 drops PHP 8.2 and earlier. The minimum is PHP 8.3 (support extends through PHP 8.5).
This is not just a housekeeping line in the upgrade guide. PHP 8.3 brings three things that directly affect every Laravel application:
Typed class constants catch type errors at declaration time rather than at runtime:
// PHP 8.3 - caught before your application boots
class Status
{
const string ACTIVE = 'active';
const string INACTIVE = 'inactive';
}
json_validate() provides a zero-allocation way to validate JSON without decoding it - useful in queue workers that receive large payloads:
// Cheap guard before expensive deserialization
if (! json_validate($payload)) {
throw new InvalidPayloadException();
}
$data = json_decode($payload, associative: true);
Readonly class improvements make value objects and DTOs first-class citizens without boilerplate cloning methods.
Pitfall: package compatibility audit
Before upgrading, run:
composer why-not php 8.3
Flag every package that hasn't declared PHP 8.3 compatibility. Most major ecosystem packages (Livewire, Inertia, Spatie's suite, Filament) shipped 8.3 support in 2025, but a long-running enterprise application may depend on something obscure.
2. Laravel AI SDK - first-party AI, finally
This is the marquee feature of Laravel 13, and it graduates from beta to production-stable on the same day as the framework release.
The Laravel AI SDK gives you a provider-agnostic, Laravel-native API for every common AI workload: text generation, tool-calling agents, image creation, audio synthesis, embedding generation, and vector-store integration.
Text generation & agents
use App\Ai\Agents\SalesCoach;
$response = SalesCoach::make()->prompt('Analyze this sales transcript...');
return (string) $response;
Agents wrap system prompts, tool definitions, and retry logic. You define the agent once, swap providers via config, and the SDK handles the rest.
Image generation
use Laravel\Ai\Image;
$image = Image::of('A donut sitting on the kitchen counter')->generate();
// $image is a first-class object; cast to string for raw bytes
Storage::put('images/donut.png', (string) $image);
Audio synthesis
Great for accessibility features, voiceover narration, and voice-driven UIs:
use Laravel\Ai\Audio;
$audio = Audio::of('Welcome back to your dashboard.')->generate();
return response($audio)->header('Content-Type', 'audio/mpeg');
Embeddings
use Illuminate\Support\Str;
$vector = Str::of('Napa Valley has great wine.')->toEmbeddings();
// Store in your database's vector column, then query semantically
Real-world use case: AI-powered support ticket routing
class TicketController extends Controller
{
public function store(Request $request): RedirectResponse
{
$ticket = Ticket::create($request->validated());
// Classify intent without hardcoding keywords
$category = SupportRouter::make()
->prompt("Classify this ticket: {$ticket->body}");
$ticket->update(['category' => (string) $category]);
return redirect()->route('tickets.show', $ticket);
}
}
Pitfall: provider lock-in at the config layer
The SDK is provider-agnostic, but credentials are not. Store your provider keys in .env, reference them only through config/ai.php, and never hardcode them in agent classes. This lets you swap from OpenAI to Anthropic (or a self-hosted model) with a single config change.
Performance Consideration
Embedding generation is the SDK call most likely to become a bottleneck. Always dispatch embeddings to a queue worker rather than generating them inline on an HTTP request:
// Bad - blocks the HTTP response for 800ms+
$vector = Str::of($document->body)->toEmbeddings();
// Good - async, non-blocking
GenerateDocumentEmbedding::dispatch($document);
3. PHP attributes across the framework - cleaner classes, no migration required
Laravel 13 introduces PHP 8 Attributes as optional alternatives to class properties across more than 15 framework locations. This is a purely additive, non-breaking change.
Eloquent models
Old approach (still works perfectly):
class User extends Model
{
protected $table = 'users';
protected $primaryKey = 'user_id';
protected $keyType = 'string';
public $incrementing = false;
protected $fillable = ['name', 'email'];
protected $hidden = ['password'];
}
New attribute syntax:
#[Table('users', key: 'user_id', keyType: 'string', incrementing: false)]
#[Fillable('name', 'email')]
#[Hidden('password')]
class User extends Model {}
The class body shrinks by 60-70% for models with many configuration properties. This matters on large teams where models grow unwieldy over time.
Artisan commands
// Before
class SendWeeklyDigest extends Command
{
protected $signature = 'digest:send {--force}';
protected $description = 'Send the weekly email digest';
}
// After
#[Signature('digest:send {--force}')]
#[Description('Send the weekly email digest')]
class SendWeeklyDigest extends Command {}
Form requests
#[RedirectTo('/dashboard')]
#[StopOnFirstFailure]
class UpdateProfileRequest extends FormRequest
{
public function rules(): array
{
return ['name' => 'required|string|max:255'];
}
}
Pitfall: don't mix both syntaxes in one class
Attributes take precedence over properties, but mixing both in the same class is confusing and invites bugs during refactoring. Pick one per class and stick to it. In a large team, add a Pint/PHPStan rule to enforce consistency.
4. JSON:API resources - standard-compliant APIs out of the box
If you build APIs consumed by multiple clients - mobile apps, frontend SPAs, third-party integrations - JSON:API compliance is often a requirement. Previously, you either reached for a community package or hand-rolled it. Laravel 13 makes it a first-party feature.
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
class ArticleResource extends JsonApiResource
{
public function toAttributes(): array
{
return [
'title' => $this->title,
'body' => $this->body,
'created_at' => $this->created_at,
];
}
public function toRelationships(): array
{
return [
'author' => UserResource::make($this->author),
'tags' => TagResource::collection($this->tags),
];
}
}
The resource handles serialization, sparse fieldsets (?fields[articles]=title,body), and the Content-Type: application/vnd.api+json response header is automatically.
Real-world use case: multi-client API
When your Laravel app serves both a React SPA and a native mobile client, JSON:API's sparse fieldsets let each client request only the fields it needs - reducing payload size without maintaining separate endpoints.
// Mobile client hits: GET /articles/1?fields[articles]=title,created_at
// Only title and created_at are serialized - body (potentially huge) is excluded
5. Vector search - semantic queries in the Query Builder
Laravel 13 extends the query builder with native vector similarity support, targeting PostgreSQL with the pgvector extension.
// Find documents semantically similar to a natural language query
$documents = DB::table('documents')
->whereVectorSimilarTo('embedding', 'Best wineries in Napa Valley')
->limit(10)
->get();
Pair this with the AI SDK's embedding generation, and you have a complete semantic search pipeline entirely within the Laravel stack - no Elasticsearch, no Pinecone, no external service required for most use cases.
Setting Up the Pipeline
// 1. Migration - add a vector column
Schema::table('documents', function (Blueprint $table) {
$table->vector('embedding', dimensions: 1536);
});
// 2. Job - generate and persist the embedding
class GenerateDocumentEmbedding implements ShouldQueue
{
public function handle(): void
{
$this->document->update([
'embedding' => Str::of($this->document->body)->toEmbeddings(),
]);
}
}
// 3. Query - semantic search at query-builder level
$results = Document::whereVectorSimilarTo('embedding', $userQuery)
->limit(5)
->get();
Performance consideration
Without an index, vector similarity scans the entire table. For production tables with > 10,000 rows, create an HNSW or IVFFlat index via pgvector:
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
6. Enhanced security - passkeys, CSRF, and Cache hardening
WebAuthn Passkey Authentication
Laravel 13 integrates WebAuthn passkey support directly into the official starter kits and Laravel Fortify. Users authenticate with Face ID, Touch ID, Windows Hello, or a hardware security key instead of a password.
// Fortify configuration - enable passkeys
Fortify::authenticateUsing(function (Request $request) {
// Passkey verification handled by the framework
return Fortify::verifyPasskey($request);
});
The private key never leaves the user's device. Phishing attacks and credential-stuffing attacks stop working by design - a significant security posture improvement with zero application-level complexity.
Enhanced CSRF Protection (PreventRequestForgery)
The CSRF middleware has been formalized as PreventRequestForgery with origin-aware request verification, adding a second layer of validation alongside the traditional token check.
// config/middleware.php
'web' => [
// ...
\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
],
Pitfall: If you have subdomain setups (e.g., app.example.com posting to api.example.com) or rely on custom CSRF behavior, test this first. The origin verification may flag requests that the old token-only middleware accepted.
Cache Object Deserialization Hardening
Laravel 13 introduces a serializable_classes config option. If you store PHP objects in cache (a pattern that becomes a deserialization gadget chain vulnerability if your cache store is compromised), you now whitelist exactly which classes are allowed:
// config/cache.php
'serializable_classes' => [
App\ValueObjects\Money::class,
App\ValueObjects\Currency::class,
],
Any cached object not on the whitelist throws during deserialization rather than silently executing.
7. Queue & HTTP Client Improvements
Failover Queue Driver
// config/queue.php
'connections' => [
'resilient' => [
'driver' => 'failover',
'connections' => ['redis', 'database'],
],
],
If Redis goes down, jobs automatically route to the database queue - no lost work, no manual intervention.
Queue Routing by Class
use Illuminate\Support\Facades\Queue;
Queue::route(ProcessPayment::class, connection: 'redis', queue: 'payments');
Queue::route(SendWelcomeEmail::class, connection: 'database', queue: 'emails');
Define routing rules once in a service provider instead of setting $connection and $queue on every job class.
HTTP Client: afterResponse Hooks
Http::acceptJson()
->baseUrl("https://api.example.com/")
->afterResponse(function (Response $response) {
if ($header = $response->header('X-Deprecation-Notice')) {
Log::warning('API deprecation', ['notice' => $header]);
}
})
->afterResponse(
fn (Response $response) => new EnrichedResponse($response)
)
->get('/users');
Inspect, mutate, or wrap responses in a composable chain without subclassing the client.
Deferred HTTP Batches
// Batch fires after the HTTP response is sent - user sees no latency
Http::batch(fn (Batch $batch) => [
$batch->post('/analytics/pageview', ['path' => $request->path()]),
$batch->post('/cdn/invalidate', ['key' => $cacheKey]),
])->defer();
8. Cache & Session Improvements
Cache::touch() - Extend TTL Without a Round-Trip
// Old - fetch, then re-store (two round-trips)
$value = Cache::get('session:123');
Cache::put('session:123', $value, now()->addMinutes(30));
// New - single EXPIRE command, no value transfer
Cache::touch('session:123', now()->addMinutes(30));
In high-traffic applications where hundreds of session keys are being refreshed per second, this is a meaningful Redis throughput improvement.
Session-Scoped Cache
// Data is isolated per user session and deleted when the session ends
Session::cache()->put('wizard:step', $currentStep, ttl: 3600);
$step = Session::cache()->get('wizard:step');
Perfect for multi-step wizards, temporary upload state, and short-lived user preferences that don't belong in the database.
9. Reverb Database Driver - WebSockets Without Redis
Previously, scaling Laravel Reverb horizontally required a Redis message broker. Laravel 13 ships a database driver that uses your existing MySQL or PostgreSQL instance instead.
// config/broadcasting.php
'reverb' => [
'driver' => 'reverb',
'app_id' => env('REVERB_APP_ID'),
'app_key' => env('REVERB_APP_KEY'),
'app_secret' => env('REVERB_APP_SECRET'),
'driver_connection' => 'mysql', // <-- new in Laravel 13
],
Small-to-medium applications get real-time WebSocket features without provisioning a separate Redis cluster - one fewer moving part in production.
10. Starter Kits - Teams Are Back
The Laravel 13 starter kits (Breeze, Jetstream successor) bring team-based multi-tenancy back to official scaffolding, implemented more robustly than the old Jetstream Teams:
- Launch two different teams in separate browser tabs simultaneously (a bug in the old implementation)
- Team-scoped policies wired to authorization out of the box
- Passkey registration per team member
Upgrade Guide - How to Move from Laravel 12 to 13
The official estimate is 10 minutes for most applications. Here's where to spend those minutes:
Step 1 - Update composer.json:
{
"require": {
"php": "^8.3",
"laravel/framework": "^13.0"
}
}
Step 2 - Run the upgrade:
composer update
php artisan migrate
php artisan config:clear
php artisan cache:clear
Step 3 - Review these specific areas (highest risk):
- Request forgery protection - test any subdomain or cross-origin POST flows
- Cache serializable_classes - audit every
Cache::put() call that stores a PHP object - Cache prefixes and session cookie names - if you hardcoded framework-generated defaults in Redis config, those defaults may have changed
- Custom contracts - several framework contracts gained new method signatures; if you implement them yourself, update your implementations
- MySQL DELETE with JOIN + ORDER BY + LIMIT - queries previously compiled loosely may now throw on certain engines
Step 4 - Run your test suite in priority order:
php artisan test --testsuite=Unit
php artisan test --testsuite=Feature
php artisan dusk
Common Pitfalls Summary
| Pitfall | Impact | Fix |
|---|
| PHP 8.2 packages | App won't boot | Run composer why-not php 8.3 before upgrading |
| Mixing attribute + property syntax | Silent override bugs | Pick one per class; add PHPStan rule |
| Inline embedding generation | 800ms+ HTTP latency | Always dispatch to queue worker |
| Missing pgvector index | Full table scans | Create HNSW index for > 10k rows |
| Custom CSRF middleware | Origin check false positives | Test subdomain flows in staging first |
| Unconfigured serializable_classes | Cache deserialization errors | Audit all Cache::put() with objects |
Performance Considerations at a Glance
Cache::touch() reduces Redis round-trips from 2 to 1 for TTL extensions - meaningful at scale- Deferred HTTP batches keep response times tight by firing after the response is sent
- Failover queue driver eliminates manual intervention during Redis outages
- Vector search needs a pgvector HNSW index for production table sizes
- PHP 8.3's JIT improvements compound over time on CPU-bound workloads (Octane users benefit most)
Best Practices for New Laravel 13 Projects
- Use PHP Attributes from day one. Don't mix property and attribute syntax. Decide as a team and add a Pint rule.
- Route AI calls through the queue by default. Treat all SDK calls the way you'd treat external HTTP calls - never inline on a web request.
- Enable
serializable_classes even if your list starts empty. It forces you to be intentional about what goes in cache. - Adopt passkeys for new user-facing applications. The starter kit wires it up for you; there's no reason to default to password-only auth in 2026.
- Version your API with JSON:API resources from the start. Retrofitting sparse fieldsets and relationship serialization onto a mature API is painful.
- Store vector dimensions in a constant. When you switch embedding models, you'll change dimension size - having it in one place (
const VECTOR_DIMENSIONS = 1536) saves a multi-file refactor.
Conclusion
Laravel 13 is the kind of release that rewards teams who actually read the changelog. The flashy headline is the AI SDK, but the real story is what surrounds it: a framework that now speaks the language of modern AI-native development at every layer - from the query builder to the queue driver to the cache layer - without forcing you to strap on a dozen third-party packages and hope they play nicely together.
The PHP 8.3 requirement is the right call. The security improvements (passkeys, PreventRequestForgery, cache hardening) are the right defaults. And the PHP Attribute syntax - optional, non-breaking, immediately cleaner - is exactly the kind of thoughtful evolution that makes Laravel worth following year after year.
If you are on Laravel 12, plan your upgrade for Q2 2026. The estimated 10-minute migration estimate is accurate for most applications, and the support clock on Laravel 12 runs to August 2026 for bug fixes.
Start today: upgrade your local environment, run your test suite, and verify your three highest-risk areas (CSRF, cache serialization, custom contracts). The production cutover will be uneventful.
FAQ
Is Laravel 13 a major or minor upgrade from Laravel 12?
It is a major version (13.0), but the breaking changes are intentionally minimal. The official upgrade guide estimates under an hour for most applications. The most common friction points are custom CSRF middleware, cached PHP objects, and custom framework contract implementations.
Does the Laravel AI SDK lock me into a specific AI provider? No. The SDK is provider-agnostic by design. You configure your provider (OpenAI, Anthropic, or others) in config/ai.php, and swap it without changing application code. Agent, image, audio, and embedding APIs are the same regardless of which provider is underneath.
Can I use PHP Attribute syntax alongside the old property syntax in the same project?
Yes - it is a non-breaking, opt-in change. Existing property-based configuration continues to work exactly as before. However, it is strongly recommended to pick one style per class and enforce that choice through static analysis to avoid confusion.
Do I need pgvector to use the vector search features?
For PostgreSQL users, yes - you need the pgvector extension installed. The whereVectorSimilarTo() query builder method currently targets PostgreSQL + pgvector. MySQL users will need to wait for broader driver support or continue using an external service like Meilisearch or Elasticsearch.
When does Laravel 12 reach end-of-life? Laravel 12 receives bug fixes until August 13, 2026 and security fixes until February 24, 2027. You are not forced to upgrade immediately, but new projects should start on Laravel 13 today.