laravel/framework v13.5.0 → v13.6.0

Laravel 13.6.0 is a small feature release with a few useful additions for queues, API-first apps, logging, and mail transport support. It also includes a solid batch of targeted fixes in validation, Vite asset loading, pagination cursors, and request validation behavior.

What's new

  • Adds debounceable queued jobs so repeated dispatches of the same job within a time window collapse down to the last dispatch. You can opt in with a #[DebounceFor] attribute on any ShouldQueue job or apply debouncing at dispatch time; earlier queued copies are skipped when they reach execution, and missing cache entries fail open so jobs are not silently lost after cache eviction or cache:clear (#59507).

  • Adds prefersJsonResponses() to the application builder for API-first apps that get Accept: */* from clients like curl, mobile SDKs, or generic HTTP libraries. Enabling it in bootstrap/app.php prepends middleware that rewrites ambiguous Accept headers to application/json, while leaving requests that already express a specific media type unchanged and preserving the original header in X-Original-Accept (#59753).

  • The built-in health route now returns JSON when the request expects JSON. /up responds with {"status":"Application is up"} on success or {"status":"Application experiencing problems"} when diagnostics fail outside debug mode, while keeping the existing HTML response for non-JSON requests and the same 200/500 status codes (#59710).

  • Introduces Illuminate\Log\Formatters\JsonFormatter for structured JSON logs that include enriched exception context, including context from previous exceptions. To use it, set 'formatter' => Illuminate\Log\Formatters\JsonFormatter::class in config/logging.php instead of Monolog’s JSON formatter (#59756).

  • Adds first-party support for Cloudflare Email Service as a mail transport. It uses Symfony’s HTTP client, so you’ll need composer require symfony/http-client, then configure account_id and token under the cloudflare service in config/services.php (#59735).

  • SQS queue connections can now use named credential providers via the credentials config key, including ecs and instance. This also improves Laravel Cloud managed queues by forcing ecs credentials and overriding the SQS region from LARAVEL_CLOUD_REGION when LARAVEL_CLOUD_MANAGED_QUEUES=1 is enabled (#59733).

  • More manager APIs now accept enums for driver or connection names. This release adds enum support to BroadcastManager, PasswordBrokerManager, and NotificationChannelManager, so enum-backed names can be passed directly to methods like Broadcast::connection(), Password::broker(), and notification channel/driver resolution methods (#59713, #59714, #59783).

  • Eloquent and testing ergonomics got a few small improvements. Child models can now use a #[Table] attribute to override a parent model’s table definition, hasAttached() accepts an array of pivot attribute arrays for building many attached records more cleanly, and assertDatabaseHas() / assertDatabaseMissing() can now accept multiple record arrays in one call (#59701, #59723, #59752).

Fixes

  • Fixes number abbreviation rollover so values like 999500 format as 1M or 1 million instead of 1,000K or 1,000 thousand (#59692).

  • Fixes Cursor::fromEncoded() so malformed but validly decoded payloads now return null instead of triggering warnings and producing broken cursor instances (#59699).

  • Fixes FormRequest::failOnUnknownFields() so query string parameters like page, perPage, expires, and signature are no longer treated as unknown payload fields (#59728).

  • Fixes a TypeError in the digits_between validation rule when non-string, non-numeric values such as arrays are passed (#59717).

  • Fixes Vite asset resolution so CSS from nested chunk imports is included instead of only CSS from direct imports of an entrypoint (#59662).

  • Fixes Cache::flushLocks() when using the failover cache driver by adding lock flushing support to FailoverStore (#59738).

  • Fixes decryption key validation so MAC checks run across all configured keys rather than stopping based on key position in the rotation list (#59742).

  • Fixes assertModelMissing() and assertModelExists() so they do not silently pass on empty results (#59772).

Other notable changes

  • The jobs.attempts column type was widened from tiny integer to small integer, allowing more than 255 attempts in database-backed queues (#59718).

  • Queue::route() now treats a string mapping as a queue name only, rather than also applying it as the connection name (#59711).

  • Validation for decimal, max_digits, and min_digits now explicitly casts numeric values to strings before preg_match() (#59739).

Contributors

@lucasmichot, @Button99, @jackbayliss, @bipinks, @paulandroshchuk, @matthewnessworthy, @WendellAdriel, @cyrodjohn, @sumaiazaman, @ju-gow, @karim1999, @kieranbrown, @ma32kc, @jnoordsij, @cosmastech, @dwightwatson, @yousefkadah