Laravel 开源生态更新日志(2026年5月)
Laravel 开源生态的最新更新和改进。
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.
<!DOCTYPE html>
<html>
<head>
@fonts
@vite("resources/js/app.js")
</head>
</html>Need only a subset of your configured families on a given page? Pass their aliases:
@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:
'sqs' => [
// ...
'extended_store_options' => [
'enabled' => env('SQS_STORE_ENABLED', false),
'disk' => env('SQS_STORE_DISK', 's3'),
'prefix' => env('SQS_STORE_PREFIX', ''),
'always' => false,
'cleanup' => true,
],
],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.
<input
type="password"
autocomplete="new-password"
passwordrules="{{ Password::defaults()->toPasswordRulesString() }}"
/>The method maps directly from your existing validation rules:
Password::min(12)->max(64)->mixedCase()->numbers()->symbols()->toPasswordRulesString()
// '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.
use Illuminate\Contracts\Queue\Interruptible;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class SignalJob implements ShouldQueue, Interruptible
{
use Queueable;
protected bool $stop = false;
public function handle(): void
{
while (!$this->stop) {
sleep(1);
// Do work...
}
}
public function interrupted(int $signal): void
{
$this->stop = true;
}
}[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.
php artisan schedule:list --environment=productionStop 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.
php 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.
$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.
ClearCommand::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.
Str::studly('CBOR', normalize: true) // 'Cbor'
Str::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:
$exceptions->shouldRenderJsonWhen(
fn (Request $request) => $request->is('api/*') || $request->expectsJson(),
);[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.
router.poll(2000, {}, { mode: "cancel" });
router.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.
usePoll(10_000, () => ({
only: ["notifications"],
data: { since: lastSeenAt },
}));[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.
// Before
const remove = router.on("before", () => {
remove();
// ...
});
// After
router.once("before", () => {
// ...
});[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.
return Inertia::render('Dashboard', [
'stats' => Inertia::defer(fn () => Github::stats(), rescue: true),
]);<Deferred data="stats">
<template #fallback>
<div>Loading...</div>
</template>
<template #rescue="{ reloading }">
<button :disabled="reloading" @click="router.reload({ only: ['stats'] })">
Retry
</button>
</template>
<StatsOverview :stats="stats" />
</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.
createInertiaApp({
nonce: "...",
});[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.
class ProjectManager implements Agent, HasTools
{
use Promptable;
public function tools(): iterable
{
return [
new WebSearch,
new ResearchAgent,
];
}
}Sub-agents implement ActsAsTool to declare their tool-facing name and description, keeping internal instructions away from the parent model:
class ResearchAgent implements Agent, HasTools, ActsAsTool
{
use Promptable;
public function name(): string { return 'research_agent'; }
public function description(): string
{
return 'Research a topic in depth and return a summary.';
}
// ...
}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.
class User extends Authenticatable
{
use HasConversations;
}
$conversations = $request->user()
->conversations()
->latest('updated_at')
->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:
$response = Str::of('Hello world')->toAudio(timeout: 45);
// With more control
$response = Str::of('Hello world')->toAudio(
provider: Lab::ElevenLabs,
voice: 'alloy',
instructions: 'Speak slowly',
model: 'custom-model',
timeout: 45,
);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.
// config/boost.php
'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.
{ "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
Add 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.
Response::resourceLink(
uri: 'file:///data/report.json',
name: 'monthly-report',
mimeType: 'application/json',
title: 'Monthly Sales Report',
size: 2048,
);Resource links can also be built from a declared Resource class, inheriting its URI, name, description, and MIME type - with optional overrides:
Response::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.
#[Icon('mcp/server.png', mimeType: 'image/png', sizes: ['48x48'])]
#[Icon('mcp/server-dark.svg', theme: IconTheme::Dark)]
class WeatherServer extends Server {}class WeatherTool extends Tool
{
public function icons(): array
{
return [
Icon::from('mcp/tool.png', mimeType: 'image/png'),
];
}
}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.
{
"tool": "phpstan",
"result": "failed",
"errors": 3,
"error_details": {},
"instructions": "Each error has an associated identifier... Do not add `@phpstan-ignore` comments..."
}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.
docker compose run --rm -e PAO_FORCE=1 app vendor/bin/pestDisable 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.
import { Passkeys } from "@laravel/passkeys";
Passkeys.configure({
fetch: (input, init) => fetch(input, { ...init, credentials: "include" }),
});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.
return Inertia::render('Authors/createOrEdit', [
'author' => $author,
/** @var list<array{value:int,label:string}> */
'categories' => Category::query()->orderBy('name')->get()
->map(fn (Category $c) => ['value' => $c->id, 'label' => $c->name])
->values()->all(),
]);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.