Changelog

New updates and improvements to Laravel's open source products.

May 2026

Laravel Framework 13.x

Add @fonts Blade Directive and Vite Font Optimization Runtime

Pull request by @WendellAdriel

Loading web fonts has always involved a bit of ceremony - preload links, @font-face rules, HTTP/2 server push headers. The new @fonts Blade directive handles all of that automatically by reading font manifests generated by the Vite font plugin, injecting the right <link rel="preload"> tags, inline styles, and push headers in one shot.

1<!DOCTYPE html>
2<html>
3 <head>
4 @fonts
5 @vite("resources/js/app.js")
6 </head>
7</html>

Need only a subset of your configured families on a given page? Pass their aliases:

1@fonts(["sans", "mono"])

Add storage Cache Store

Pull request by @taylorotwell

Laravel's cache system now supports a storage driver backed by the filesystem, making it straightforward to use an existing disk - like S3 - as a key/value cache without any additional infrastructure.

[13.x] Add Optional Disk Storage for Large SQS Queue Payloads

Pull request by @Orrison

SQS has a 1 MB message size cap, and large job payloads will hit it eventually. This new opt-in feature automatically offloads payloads that exceed the limit to a configured filesystem disk - like S3 - sending a small pointer through the queue instead. Workers pick up the real payload transparently on the other side.

Add an extended_store_options block to your SQS connection config to get started:

1'sqs' => [
2 // ...
3 'extended_store_options' => [
4 'enabled' => env('SQS_STORE_ENABLED', false),
5 'disk' => env('SQS_STORE_DISK', 's3'),
6 'prefix' => env('SQS_STORE_PREFIX', ''),
7 'always' => false,
8 'cleanup' => true,
9 ],
10],

Set always to true to route every payload through disk regardless of size. With cleanup enabled, the stored file is removed once the job processes successfully.

[13.x] Add Method to Convert a Password Instance to a passwordrules String

Pull request by @imliam

Password managers like 1Password and Safari can read the passwordrules HTML attribute to generate passwords that satisfy your app's policy - no more rejected passwords and frustrated users at registration. The new toPasswordRulesString() method on the Password rule converts your validation constraints into the right format automatically.

1<input
2 type="password"
3 autocomplete="new-password"
4 passwordrules="{{ Password::defaults()->toPasswordRulesString() }}"
5/>

The method maps directly from your existing validation rules:

1Password::min(12)->max(64)->mixedCase()->numbers()->symbols()->toPasswordRulesString()
2// 'minlength: 12; maxlength: 64; required: lower; required: upper; required: digit; required: special;'

[13.x] Allow Jobs to React to Worker Signals

Pull request by @jackbayliss

When a queue worker receives a SIGTERM during a deployment, any running job normally has no idea it's about to be cut off. The new Interruptible interface lets jobs react - stop loops, release locks, save state - before the worker shuts down.

1use Illuminate\Contracts\Queue\Interruptible;
2use Illuminate\Contracts\Queue\ShouldQueue;
3use Illuminate\Foundation\Queue\Queueable;
4 
5class SignalJob implements ShouldQueue, Interruptible
6{
7 use Queueable;
8 
9 protected bool $stop = false;
10 
11 public function handle(): void
12 {
13 while (!$this->stop) {
14 sleep(1);
15 // Do work...
16 }
17 }
18 
19 public function interrupted(int $signal): void
20 {
21 $this->stop = true;
22 }
23}

[13.x] Add an Environment Filter to the schedule:list Command

Pull request by @m-fi

Running schedule:list on production used to show every registered task - including those that only run locally. A new --environment flag filters the output to just the tasks that actually run in the specified environment.

1php artisan schedule:list --environment=production

Stop When Empty for Queue Worker Option

Pull request by @taylorotwell

A new --stop-when-empty-for option on queue:work stops the worker after it has been idle for a configurable number of seconds. Useful for auto-scaling setups where workers should wind down rather than sit idle indefinitely.

1php artisan queue:work --stop-when-empty-for=60

[13.x] Add foreignUuidFor Schema Helper

Pull request by @Tresor-Kasenda

A new foreignUuidFor migration helper joins foreignIdFor in the schema builder. It infers the column name, related table, and referenced primary key from the model - the same convenience as foreignIdFor, but explicitly for UUID-backed relationships.

1$table->foreignUuidFor(User::class)->constrained();

[13.x] Make ClearCommand Prohibitable

Pull request by @jackbayliss

cache:clear now supports prohibit(), the same opt-in protection mechanism available on db:fresh, migrate:fresh, and other commands you'd want to block in hosted environments.

1ClearCommand::prohibit($inHostedEnvironments);

[13.x] Add normalize Parameter to Str::studly() and Str::pascal()

Pull request by @hotmeteor

All-caps strings like CBOR or ALL_CAPS used to pass through Str::studly() unchanged - ucfirst has nothing to do when every letter is already uppercase. An optional normalize: true parameter lowercases all-uppercase word segments before conversion, giving you the expected output.

1Str::studly('CBOR', normalize: true) // 'Cbor'
2Str::studly('ALL_CAPS', normalize: true) // 'AllCaps'

Mixed-case strings are left untouched, and the default is false so existing behavior is preserved.

[13.x] Render JSON Exceptions for API Routes by Default

Pull request by @LucasCavalheri

New Laravel 13.x applications now return JSON error responses from api/* routes by default - no Accept: application/json header required from the client. Tools like Postman, curl, and API clients get sensible responses right away. The behavior is configured in bootstrap/app.php using the existing shouldRenderJsonWhen() API:

1$exceptions->shouldRenderJsonWhen(
2 fn (Request $request) => $request->is('api/*') || $request->expectsJson(),
3);

[13.x] Adds pao by Default

Pull request by @nunomaduro

nunomaduro/pao is now included in the base Laravel skeleton, bringing structured AI agent output to every new application out of the box.

Inertia

[3.x] Add mode Option to router.poll

Pull request by @pascalbaljet

When a polled request takes longer than the configured interval, subsequent requests can pile up on the server. A new mode option on router.poll() and usePoll gives you control over how overlapping requests are handled - cancel aborts any in-flight request before starting the next, rest waits the full interval after each response so requests never overlap, and overlap preserves the existing behavior.

1router.poll(2000, {}, { mode: "cancel" });
2router.poll(2000, {}, { mode: "rest" });

[3.x] Support Dynamic Data in usePoll

Pull request by @pascalbaljet

usePoll previously captured its request options once at mount, so reactive values from props would go stale on every subsequent tick. You can now pass a function as the second argument - Inertia re-evaluates it on each poll, so your requests always carry fresh data.

1usePoll(10_000, () => ({
2 only: ["notifications"],
3 data: { since: lastSeenAt },
4}));

[3.x] Add Inertia.once for Events That Fire Once

Pull request by @sebastiandedeyne

Registering a one-time event listener previously meant calling remove() manually inside your own callback. router.once() handles the cleanup for you.

1// Before
2const remove = router.on("before", () => {
3 remove();
4 // ...
5});
6 
7// After
8router.once("before", () => {
9 // ...
10});

[3.x] Add rescue Slot to <Deferred> for Failed Deferred Props

Pull request by @pascalbaljet

When a deferred prop throws on the server using rescue: true, the frontend previously had no way to surface the failure - users would sit on the loading fallback indefinitely. The new rescue slot renders when a prop has been rescued, with a reloading boolean so you can build a proper retry UI.

1return Inertia::render('Dashboard', [
2 'stats' => Inertia::defer(fn () => Github::stats(), rescue: true),
3]);
1<Deferred data="stats">
2 <template #fallback>
3 <div>Loading...</div>
4 </template>
5 
6 <template #rescue="{ reloading }">
7 <button :disabled="reloading" @click="router.reload({ only: ['stats'] })">
8 Retry
9 </button>
10 </template>
11 
12 <StatsOverview :stats="stats" />
13</Deferred>

[3.x] Improve CSP Support for Progress Bar and Error Dialog

Pull request by @pascalbaljet

Apps enforcing a Content Security Policy can now configure a nonce once in createInertiaApp - it's applied automatically to the inline styles Inertia injects for the progress bar and error dialog.

1createInertiaApp({
2 nonce: "...",
3});

[3.x] Sandbox Error Dialog iFrame to Prevent window.parent Access

Pull request by @pascalbaljet

When Inertia displays a non-Inertia response in its error dialog, the content is rendered inside an iframe. This change adds sandbox="allow-scripts" to that iframe, blocking scripts inside from reaching window.parent and interacting with your application. No API changes required.

[3.x] Add host Option to SSR Server

Pull request by @pascalbaljet

The SSR server now accepts a host option to control which network interface it binds to. The default remains 0.0.0.0 so nothing changes unless you need it.

[3.x] Support Network URLs for Loading CSS Assets in SSR Dev Server

Pull request by @simonellensohn

When Vite runs inside a container or behind a reverse proxy with a custom server.host, it places the dev server address in resolvedUrls.network rather than resolvedUrls.local. The SSR dev server now falls back to the network URL when the local one isn't available, fixing SSL certificate errors in those container and proxy setups.

[3.x] Pass Props to withApp Callback

Pull request by @CL0Pinette

The withApp callback now receives the initial page props, giving it parity with setup. Use cases like setting a user locale in SSR mode - where the locale comes from page props - are now straightforward without falling back to setup.

Agent Skills

Add starter-kit-upgrade Skill for Laravel Starter Kits

Pull request by @pushpak1300

Pulling in upstream starter kit improvements without clobbering your own customizations has always been a manual, risky process. The new starter-kit-upgrade Claude Code skill gives you a guided, audit-friendly path to review what's changed in the laravel/vue-starter-kit, laravel/react-starter-kit, or laravel/livewire-starter-kit and apply only the specific features you want.

AI

Add Sub-Agent Support as Tools

Pull request by @JVillator0

Laravel AI agents can now delegate to other agents. Return any agent from your tools() method and the parent's LLM will invoke it like any other tool, passing a task description and receiving the result back. Sub-agents run with isolated context - no conversation history bleeds across from the parent.

1class ProjectManager implements Agent, HasTools
2{
3 use Promptable;
4 
5 public function tools(): iterable
6 {
7 return [
8 new WebSearch,
9 new ResearchAgent,
10 ];
11 }
12}

Sub-agents implement ActsAsTool to declare their tool-facing name and description, keeping internal instructions away from the parent model:

1class ResearchAgent implements Agent, HasTools, ActsAsTool
2{
3 use Promptable;
4 
5 public function name(): string { return 'research_agent'; }
6 
7 public function description(): string
8 {
9 return 'Research a topic in depth and return a summary.';
10 }
11 
12 // ...
13}

Enable Failover During Stream Iteration

Pull request by @kachelle

Streaming failover with withModelFailover() wasn't actually working - errors during generator iteration happened outside the try-catch, so a rate-limited provider mid-stream would throw instead of trying the next one. Failover now kicks in properly during stream iteration, with the AgentFailedOver event dispatched as expected.

Sync Conversation Metadata After Streamed Responses

Pull request by @dhrupo

When using a conversational agent with stream(), the conversationId on the outer StreamableAgentResponse was always null - the metadata was being written to the inner response only. After this fix, conversationId and conversationUser are available on the outer response object once the stream completes.

Retrieve Conversation List From ConversationStore and Agent Trait

Pull request by @barryvdh

Building a conversation history sidebar previously meant writing raw SQL against the agent_conversations table. A new HasConversations trait for your User model exposes a proper Eloquent relationship, with updated_at advancing as messages arrive so ordering by recency just works.

1class User extends Authenticatable
2{
3 use HasConversations;
4}
5 
6$conversations = $request->user()
7 ->conversations()
8 ->latest('updated_at')
9 ->paginate(20);

Broadcast stream_failed Event When BroadcastAgent Job Fails

Pull request by @sumaiazaman

When a BroadcastAgent job threw an unhandled exception, the frontend had no signal that the stream had failed - leaving the user staring at a spinner indefinitely. A stream_failed event is now broadcast via the job's failed() hook so clients can surface the error and let users retry.

Add toAudio Macro to Stringable

Pull request by @nhedger

Text-to-speech is now a fluent one-liner from any Stringable instance:

1$response = Str::of('Hello world')->toAudio(timeout: 45);
2 
3// With more control
4$response = Str::of('Hello world')->toAudio(
5 provider: Lab::ElevenLabs,
6 voice: 'alloy',
7 instructions: 'Speak slowly',
8 model: 'custom-model',
9 timeout: 45,
10);

Boost

Add Tinker MCP Tool (opt-in via Config)

Pull request by @pushpak1300

The Tinker MCP tool is back as an opt-in feature. When enabled, agents prefer the MCP tool over the php artisan tinker --execute '...' shell path - skipping the quoting failures that caused agents to retry a simple snippet two or three times.

1// config/boost.php
2'tinker_tool_enabled' => true,

Add Laravel Cloud Integration to Install Command

Pull request by @pushpak1300

The Boost install command now includes a Laravel Cloud option. Opt in during installation and the deploying-laravel-cloud skill is configured automatically alongside your other integrations.

Remove Model Discovery and Use Token-Based Class Parsing

Pull request by @pushpak1300

Model discovery via class_exists() was broken - only already-loaded classes were visible, and enabling autoloading caused crashes. Rather than chasing endless edge cases, model discovery has been removed entirely. Modern AI agents find models by searching the codebase directly, which works better in practice anyway.

Sync Flux UI Free/Pro Component Lists With fluxui.dev

Pull request by @pushpak1300

The Flux UI skill files now accurately reflect the component lists from fluxui.dev. Previously, components like table, card, pagination, progress, and toast were listed as Pro-only, causing agents to write custom Blade tables when free Flux components were available all along.

Installer

Emit Structured Json Output When Invoked by AI Agents

Pull request by @joetannenbaum

When laravel new runs inside an AI agent, it now suppresses interactive prompts and outputs a single JSON line describing the result - with the project name, directory, and on failure, a log path and output tail for debugging.

1{ "success": true, "name": "my-app", "directory": "/path/to/my-app" }

This makes the installer reliable in automated pipelines without any special wiring.

Starter Kits

Use the Vite Font Plugin in Starter Kits

Pull request by @WendellAdriel

All starter kit variants have been updated to load fonts through the Vite font plugin. The direct CDN <link> tags are gone - preloads, @font-face rules, and server push are all handled by @fonts now.

Add passwordrules Attribute to New-Password Inputs

Pull request by @joetannenbaum

Every new-password input across the starter kits - register, reset password, and update password pages - now includes a passwordrules attribute derived from Password::defaults()->toPasswordRulesString(). Password managers can generate a compliant password on the first try rather than failing against validation after the fact.

MCP

Pull request by @rupeshstha

Laravel MCP now supports the resource_link content type from the MCP spec. Unlike embedded resources that inline content, a resource link returns a URI pointer that the client fetches or subscribes to independently - better suited for large payloads, generated artifacts, and live subscriptions.

1Response::resourceLink(
2 uri: 'file:///data/report.json',
3 name: 'monthly-report',
4 mimeType: 'application/json',
5 title: 'Monthly Sales Report',
6 size: 2048,
7);

Resource links can also be built from a declared Resource class, inheriting its URI, name, description, and MIME type - with optional overrides:

1Response::resourceLink(WeatherForecastResource::class, title: 'Custom Title');

Add Icon and Implementation (MCP Spec 2025-11-25)

Pull request by @pushpak1300

MCP clients can now display icons for your servers, tools, resources, and prompts. Declare them via the #[Icon] attribute or a icons() method - both can be used together and their results are combined automatically.

1#[Icon('mcp/server.png', mimeType: 'image/png', sizes: ['48x48'])]
2#[Icon('mcp/server-dark.svg', theme: IconTheme::Dark)]
3class WeatherServer extends Server {}
1class WeatherTool extends Tool
2{
3 public function icons(): array
4 {
5 return [
6 Icon::from('mcp/tool.png', mimeType: 'image/png'),
7 ];
8 }
9}

Relative paths are resolved through Laravel's asset() helper; full URLs are used as-is.

Note: $context->serverName and $context->serverVersion have been removed from ServerContext. Use $context->implementation->name and $context->implementation->version instead.

Moat

Laravel Moat reviews the security posture of your GitHub organization and repositories, then surfaces recommendations to consider. It inspects the security controls GitHub already offers — 2FA enforcement, branch protection, signed commits, secret scanning, Dependabot alerts, workflow permissions, pinned actions, repository webhooks, and more — and reports which ones are not enabled or not configured in line with common recommendations.

Pao

Add Rector Support

Pull request by @cosmastech

PAO now includes a Rector driver, surfacing Rector's analysis in a structured, agent-friendly format. Rector's default console output can be noisy - this makes the results far easier for agents to act on.

Add Agent Guidance to PHPStan JSON Output

Pull request by @pushpak1300

PHPStan's agent-mode console output already includes instructions on how to handle errors correctly. PAO's JSON output now carries the same guidance in an instructions field, so agents using the JSON path get the same steer regardless of how they consume results.

1{
2 "tool": "phpstan",
3 "result": "failed",
4 "errors": 3,
5 "error_details": {},
6 "instructions": "Each error has an associated identifier... Do not add `@phpstan-ignore` comments..."
7}

Add PAO_FORCE Env

Pull request by @antonkomarev

Running tests inside Docker means agent environment variables often don't propagate into the container. A new PAO_FORCE=1 env var force-enables PAO regardless of agent detection, so you can drop it into a docker compose run command without enumerating every possible agent marker in your compose config.

1docker compose run --rm -e PAO_FORCE=1 app vendor/bin/pest

Disable All Agents When Using runWith()

Pull request by @cosmastech

The runWith() testing helper now correctly disables all agents during test execution, not just Claude Code. Previously, other agents could still interfere with test runs.

Passkeys

Allow Usage of Credentials: Include

Pull request by @RobertBoes

The passkeys package previously hardcoded credentials: "same-origin", which breaks Sanctum SPA setups where the frontend and API live on different domains. A new Passkeys.configure() method lets you override the fetch behavior for cross-domain setups.

1import { Passkeys } from "@laravel/passkeys";
2 
3Passkeys.configure({
4 fetch: (input, init) => fetch(input, { ...init, credentials: "include" }),
5});

Ranger

Collect API Resource and Arrayable Responses

Pull request by @joetannenbaum

Ranger now picks up Eloquent API resources, JSON:API resources, and any arrayable class when collecting route response types. Inertia prop collection also runs class types through the ArrayableResolver, so toArray-style props are unwrapped into their underlying shape for accurate TypeScript generation.

Respect $hidden, $visible, and $appends on Model Components

Pull request by @joetannenbaum

Eloquent's $hidden, $visible, and $appends properties now influence the types Ranger generates. Previously, the generated TypeScript could include attributes that would never appear in a serialized response, or miss custom appended accessors entirely. The resolved shape now matches what Eloquent actually serializes.

Reverb

Use Timing-Safe Comparison for Pusher HTTP API Signature Check

Pull request by @pushpak1300

The Pusher HTTP API signature verifier was using PHP's !== for HMAC comparison, which short-circuits on the first non-matching byte and leaks timing information that can be exploited to forge signatures. This switches to hash_equals() for constant-time comparison - consistent with how Reverb's WebSocket channel verifier was already handling it.

Sail

Add New AI Agent Env Vars

Pull request by @pushpak1300

Sail now forwards environment markers for Cowork, Copilot, and Kiro CLI into application containers alongside the existing agent detection variables. Packages like PAO rely on these to identify the active agent - without them, agent-mode features silently don't activate when running inside Sail.

Surveyor

Resolve Inertia Special Prop Types (defer, optional, lazy, always, merge)

Pull request by @joetannenbaum

Inertia's defer, optional, lazy, always, and merge prop wrappers were previously falling through to reflection, which returned the wrapper class itself rather than the actual type inside. Surveyor now resolves the inner type directly from each wrapper's callback. defer, optional, and lazy are marked as optional since those props may not be present in the initial response.

Allow @var Docblocks on Array Items to Override Inferred Type

Pull request by @JasBogans

Static analysis can't always follow deep Collection pipelines or SQL aliases down to a precise shape. A @var docblock placed immediately before an array item now overrides the inferred type, giving you an exact local escape hatch without changing any surrounding code.

1return Inertia::render('Authors/createOrEdit', [
2 'author' => $author,
3 /** @var list<array{value:int,label:string}> */
4 'categories' => Category::query()->orderBy('name')->get()
5 ->map(fn (Category $c) => ['value' => $c->id, 'label' => $c->name])
6 ->values()->all(),
7]);

Handle Inline Variable Assignments Inside Arrays

Pull request by @bakerkretzmar

Surveyor was mishandling inline variable assignments inside arrays - patterns like ['foo' => $bar = 42] resolved to a VariableState instead of a concrete type, causing subtle bugs downstream in Ranger and Wayfinder. These are now unwrapped correctly.