
Everything New in Laravel 13: The Complete Guide
Laravel 13 dropped on March 17, 2026, and I've spent the last two days digging through every commit, PR, and doc page. There's a lot to unpack here. This isn't one of those "minor version bump" releas

Laravel 13 dropped on March 17, 2026, and I've spent the last two days digging through every commit, PR, and doc page. There's a lot to unpack here. This isn't one of those "minor version bump" releas
Laravel 13 dropped on March 17, 2026, and I've spent the last two days digging through every commit, PR, and doc page. There's a lot to unpack here. This isn't one of those "minor version bump" releases. The framework took some real swings this time around.
I'm going to walk you through every major feature, the stuff that matters, and the stuff you'll actually use in your day-to-day work. We're talking PHP attributes across the entire framework, a first-party AI SDK, native vector search, passkey auth, and a bunch of smaller quality-of-life improvements that add up fast.
If you want the official changelog, check out the Laravel 13 release notes. But if you want the practical breakdown with real code examples and honest opinions, keep reading.
Let's get into it.
Photo by Lukas Blazek on Pexels
This is the biggest developer experience change in Laravel 13, and honestly, it's the one I'm most excited about. The framework now supports PHP attributes in over 15 different locations. All of them are fully optional and backward-compatible. Your existing code keeps working. You just get a cleaner way to do things if you want it.
Let me show you what this looks like in practice. PR #58578 is the main one to follow.
Eloquent models can now use attributes for observers and scopes:
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ObservedBy(UserObserver::class)]
#[ScopedBy(ActiveScope::class)]
class HrrUser extends Model
{
// No more boot() boilerplate
}
Queue jobs get rate limiting and overlap prevention right on the class:
use Illuminate\Queue\Attributes\WithoutOverlapping;
use Illuminate\Queue\Attributes\RateLimited;
#[WithoutOverlapping(key: 'user-{userId}')]
#[RateLimited('default')]
class HrrProcessPaymentJob implements ShouldQueue
{
public function __construct(public int $userId) {}
public function handle(): void
{
// payment logic
}
}
Controllers can apply middleware to individual methods instead of the constructor:
use Illuminate\Routing\Attributes\Middleware;
class HrrOrderController extends Controller
{
#[Middleware(['auth', 'verified'])]
public function store(Request $request): Response
{
// create order
}
}
And Artisan commands can define their schedule right on the class:
use Illuminate\Console\Attributes\Schedule;
#[Schedule('daily')]
class HrrSendDailyReportCommand extends Command
{
protected $signature = 'hrr:daily-report';
public function handle(): void
{
// send report
}
}
So what's actually going away? Nothing. The old $casts array, the boot() method, the Kernel.php scheduling, all of that still works. Attributes are just a cleaner alternative that keeps your configuration closer to the code it affects. No breaking changes.
This is my favorite change in this release. It makes your code more scannable and cuts down on the boilerplate you have to remember.
Photo by Bibek ghosh on Pexels
The laravel/ai package hit stable 1.0 alongside this release, and it's a big deal. Laravel now has first-party support for AI across multiple providers. OpenAI, Anthropic, and Gemini all work out of the box. You get text generation, image generation, audio transcription, embeddings, and even agents with tool support.
The API is clean and fluent, exactly what you'd expect from a Laravel package:
use Laravel\AI\Facades\AI;
// Text generation
\(response = AI::text('Write a product description for ' . \)product->name);
// With a specific provider
\(response = AI::provider('anthropic')->text('Summarize this document: ' . \)doc->content);
// Embeddings for semantic search
$vector = AI::embeddings('Laravel is a PHP framework');
// Agent with tools
$result = AI::agent()
->tools([new HrrSearchTool(), new HrrCalculateTool()])
->run('Find the best product for under $50 and calculate shipping');
Setting up multiple providers is straightforward in the config:
// config/ai.php
'providers' => [
'openai' => [
'driver' => 'openai',
'api_key' => env('OPENAI_API_KEY'),
'model' => 'gpt-4o',
],
'anthropic' => [
'driver' => 'anthropic',
'api_key' => env('ANTHROPIC_API_KEY'),
'model' => 'claude-sonnet-4-6',
],
],
'default' => env('AI_PROVIDER', 'openai'),
The agents feature is the one that caught my eye. You define tool classes, hand them to the agent, and it figures out when to call them. It's the kind of thing you'd normally need LangChain or a custom orchestrator for. Now it's built into your Laravel app with a one-liner.
Check out the full package at github.com/laravel/ai. I'll be writing a deeper dive on this one soon.
Photo by Vladimir Srajber on Pexels
Laravel 13 ships first-class pgvector support, and it pairs perfectly with the AI SDK embeddings from the previous section. If you've ever wanted to build "search by meaning" into your app instead of just keyword matching, this is how you do it now.
Start with a migration. The new vector column type handles everything:
Schema::create('hrr_articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 1536);
$table->timestamps();
});
Your model uses the new HasVector attribute to declare the vector column:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\HasVector;
#[HasVector('embedding', dimensions: 1536)]
class HrrArticle extends Model
{
protected $fillable = ['title', 'content', 'embedding'];
}
Now querying by similarity is just a few lines:
use Laravel\AI\Facades\AI;
// Get embedding for the search query
\(queryVector = AI::embeddings(\)request->query('q'));
// Find similar articles
\(articles = HrrArticle::whereVectorSimilarTo('embedding', \)queryVector)
->orderByVectorDistance('embedding', $queryVector)
->limit(10)
->get();
Notice how the AI SDK embeddings feed directly into the vector query. Generate the embedding, pass it to the Eloquent query builder, done. No external vector databases. No complicated setup. Just PostgreSQL with the pgvector extension and your normal Laravel app.
You will need PostgreSQL with pgvector installed. If you're on a managed database like AWS RDS or Neon, pgvector is usually available as an extension you can enable. For local development, the official pgvector Docker image works great.
Two distinct features here, but both are things people have been asking for. Let's cover them together.
Laravel 13 adds a JsonApiResource class that produces spec-compliant JSON:API responses. If you've ever had to build a JSON:API backend by hand or pull in a third-party package, you know how tedious that envelope format can be. Now it's built in.
use Illuminate\Http\Resources\Json\JsonApiResource;
class HrrUserResource extends JsonApiResource
{
public function toAttributes(Request $request): array
{
return [
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
];
}
public function toRelationships(Request $request): array
{
return [
'posts' => HrrPostResource::collection($this->posts),
];
}
}
That automatically produces the proper JSON:API envelope with data.type, data.id, data.attributes, and data.relationships. No manual wrapping. No string concatenation for the type field. It just works.
Laravel 13 starter kits now include passkey authentication out of the box. Touch ID, Face ID, hardware security keys. No third-party packages. No paid services. It's all built in.
// routes/web.php
Route::post('/passkeys/register', [PasskeyController::class, 'store'])
->middleware('auth');
Route::post('/passkeys/authenticate', [PasskeyController::class, 'authenticate']);
use Laravel\Auth\Passkeys\PasskeyRegistration;
use Laravel\Auth\Passkeys\PasskeyAuthentication;
class HrrPasskeyController extends Controller
{
public function store(Request $request): JsonResponse
{
\(passkey = PasskeyRegistration::verify(\)request);
\(request->user()->passkeys()->save(\)passkey);
return response()->json(['registered' => true]);
}
}
I've been waiting for native passkey support in Laravel for a while. The WebAuthn spec is complex and the existing packages required a lot of configuration. This implementation is clean. You add a few routes, call the verify method, and your users can log in with their fingerprint or face. That's it.
Photo by cottonbro studio on Pexels
This one is interesting. Laravel 13 ships laravel/boost, a first-party MCP (Model Context Protocol) server that lets AI agents interact with your Laravel application. It comes with 15 built-in tools for things like running Artisan commands, querying your database, inspecting routes and models, running migrations, and executing tests.
# Install Boost
composer require laravel/boost
# Start the MCP server
php artisan boost:serve
# Or use the CLI directly
./vendor/bin/boost
To connect it to your AI editor (Claude Desktop, Cursor, or anything that supports MCP), just add it to your config:
// .mcp.json (for Claude Desktop, Cursor, etc.)
{
"mcpServers": {
"laravel-boost": {
"command": "./vendor/bin/boost",
"args": [],
"cwd": "/path/to/your/app"
}
}
}
The 15 built-in tools cover most of what you'd want an AI agent to do with your app. Artisan runner, route inspector, model explorer, migration runner, test runner, log reader, and more. But here's the thing that really stands out. Boost includes a /upgrade-laravel-v13 prompt that walks AI agents through the entire upgrade process automatically. It inspects your codebase, identifies deprecated patterns, and suggests the changes you need.
If you're already using AI-assisted development, this is a natural extension of your workflow. And if you're not, this might be the feature that gets you started.
Check out the full tool list at github.com/laravel/boost.
There are a handful of smaller changes in Laravel 13 that don't warrant their own section but are still worth knowing about. Let me run through them quickly.
You can now explicitly opt in or out of CSRF-like verification on specific routes:
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
Route::post('/webhook/stripe', HrrStripeWebhookController::class)
->withoutMiddleware(PreventRequestForgery::class);
Route::post('/api/internal', HrrInternalController::class)
->middleware(PreventRequestForgery::class);
Route jobs to different queues based on their type or data. No more hardcoding queue names in every job class:
use Illuminate\Support\Facades\Queue;
Queue::route(function ($job) {
if ($job instanceof HrrProcessLargeFileJob) {
return 'heavy';
}
if ($job->priority === 'high') {
return 'critical';
}
return 'default';
});
Reset the TTL on a cache key without fetching and re-storing the value. Simple but useful for sliding window patterns:
// Extend cache entry TTL by another hour without re-fetching
Cache::touch('hrr_user_session_' . $userId, now()->addHour());
// Useful for sliding window expiration
Cache::touch('hrr_rate_limit_' . $ip, 60);
You can now use your existing database as a Reverb backend instead of Redis. Great for smaller apps where you don't want to run a separate Redis instance:
// config/broadcasting.php
'reverb' => [
'driver' => 'reverb',
'storage' => env('REVERB_STORAGE', 'database'), // was 'redis' only before
],
The Breeze and Jetstream-style starter kits now include Teams support out of the box. One flag and you're set:
php artisan install:breeze react --teams
php artisan install:api --teams
Laravel 13 uses PHP 8.5's new closure attributes internally. This enables cleaner attribute-based hooks across the framework:
// New in PHP 8.5 + Laravel 13 internals
$fn = #[SomeAttribute] function() { return 42; };
Each of these is a small win on its own. Together, they add up to a release that touches almost every part of the framework.
Photo by Tima Miroshnichenko on Pexels
Now for the part nobody loves but everybody needs. Let's talk about what breaks and what you need to do about it.
Minimum requirements have changed:
Here's a quick breakdown of the breaking changes:
| Change | Impact | Fix |
|---|---|---|
| PHP 8.1/8.2 dropped | Medium | Upgrade PHP to 8.3+ |
| Symfony 7.4/8.0 required | Medium | Update via composer |
Model::unguard() removed |
Low | Use $guarded = [] instead |
Old Schedule kernel removed |
Low | Move to route or attribute scheduling |
assertStatus() strict mode |
Low | Check test assertions |
To upgrade, start with composer and then verify your tests:
# Upgrade Laravel to 13
composer require laravel/framework:^13.0
# Run the upgrade checks via Boost (if installed)
php artisan boost:check-upgrade
# Run existing tests to find breaking changes
php artisan test
# Check for deprecated usages
php artisan about
The impact here is moderate. If you're already on PHP 8.3 and your tests pass, you're probably fine. The Model::unguard() removal and schedule kernel changes are easy fixes. And if you installed Boost, the upgrade prompt can walk you through the rest automatically.
Laravel 13 is a clear signal that the framework is moving toward AI-native PHP. The AI SDK, vector search, and MCP server aren't experiments. They're first-party packages with long-term support. Taylor and the team are betting that AI features belong in the framework, not just in third-party packages.
Hope you enjoyed this rundown of Laravel 13. There's a lot here, and I'll be writing deeper dives on the AI SDK and pgvector integration in future posts.
Follow me on Twitter/X for more Laravel and PHP content. Drop a comment if you've already upgraded or if you have questions.
I email when a new post goes up. One send a week, and only if there's something new.
Want this applied on your account? Start with DevOps.
Related reading
Kept below the post instead of in a sidebar, with a slow continuous motion for a cleaner editorial feel.
On this post
Comments
A reply stays under the note it answers.
No comments yet.
If you have a note on Everything New in Laravel 13: The Complete Guide, sign in and leave it.