New updates and improvements to Laravel's open source products.
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 @fonts5 @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"])
storage Cache StorePull 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.
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.
passwordrules StringPull 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<input2 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;'
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(): void12 {13 while (!$this->stop) {14 sleep(1);15 // Do work...16 }17 }18 19 public function interrupted(int $signal): void20 {21 $this->stop = true;22 }23}
schedule:list CommandPull 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
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
foreignUuidFor Schema HelperPull 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();
ClearCommand ProhibitablePull 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);
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.
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);
pao by DefaultPull 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.
mode Option to router.pollPull 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" });
usePollPull 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}));
Inertia.once for Events That Fire OncePull 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});
rescue Slot to <Deferred> for Failed Deferred PropsPull 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>
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});
window.parent AccessPull 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.
host Option to SSR ServerPull 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.
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.
withApp CallbackPull 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.
starter-kit-upgrade Skill for Laravel Starter KitsPull 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.
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}
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.
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.
ConversationStore and Agent TraitPull 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 Authenticatable2{3 use HasConversations;4}5 6$conversations = $request->user()7 ->conversations()8 ->latest('updated_at')9 ->paginate(20);
stream_failed Event When BroadcastAgent Job FailsPull 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.
toAudio Macro to StringablePull 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);
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.php2'tinker_tool_enabled' => true,
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.
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.
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.
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.
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.
passwordrules Attribute to New-Password InputsPull 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.
ResourceLink Content Type (MCP Spec 2025-06-18)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');
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 Tool2{3 public function icons(): array4 {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.
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.
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.
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}
PAO_FORCE EnvPull 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
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.
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});
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.
$hidden, $visible, and $appends on Model ComponentsPull 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.
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.
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.
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.
@var Docblocks on Array Items to Override Inferred TypePull 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]);
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.