What's New in Laravel 13.15: Typed Translations and More

php dev.to

Originally published at hafiz.dev


Most point releases are safe to skim. Laravel 13.15 has one change that's a reason to update today, not eventually, so it's worth two minutes even if you skip the rest.

That change is a security fix for the date_equals validation rule, where an invalid date could slip past validation entirely under the right conditions. If you validate dates anywhere near authentication, scheduling, or access windows, read that section. The other three headline changes (typed translation accessors, JSON Schema unions, and a dedicated Cloud queue driver) are quality-of-life improvements you'll appreciate but won't lose sleep over. Here's what each one actually does.

Quick note on timing: Laravel ships minor releases weekly, so by the time you read this, 13.16 through 13.19 are already out too (13.19 added HTTP query method support). None of that changes the 13.15 features below, and since these are all backward-compatible minor releases, a composer update gets you everything at once.

The Security Fix You Should Actually Care About

Start here because it's the one with teeth.

The date_equals validation rule was using loose comparison under the hood. That sounds harmless until you trace what PHP does with a bad date string. An invalid date parses to null. And in PHP, null == 0 evaluates to true. So if your reference date parsed to something falsy, like the Unix epoch 1970-01-01 00:00:00, an invalid date string could satisfy date_equals and pass validation.

Picture a rule like this guarding a form:

$request->validate([
    'event_date' => 'required|date_equals:'.$referenceDate,
]);
Enter fullscreen mode Exit fullscreen mode

If $referenceDate resolves to a falsy timestamp and an attacker submits garbage that parses to null, the loose comparison waves it through. That's a validation bypass, and validation bypasses are exactly the kind of quiet bug that doesn't show up until someone goes looking for it.

The fix uses strict comparison for the equality check while keeping loose comparison for legitimate DateTime objects, so real dates still compare the way you expect. There's nothing to change in your code. You update the framework and the hole closes. That's the whole reason this release jumps the queue.

The same release also tightened route unserialization, restricting which classes the router will accept when unserializing during route caching and resolution. It's a smaller surface than the date bug, but it shrinks the room for object-injection tricks during route:cache. Another reason the update is worth doing sooner rather than later.

Typed Translation Accessors

Now the headline feature, and it's one that anyone running strict static analysis will like.

Laravel's translation helpers have always returned broad types. __() returns array|string|null, and trans() returns Translator|array|string. That's fine in a Blade template where you're just echoing a string. It's friction everywhere else, because a method that's typed to return string can't just return __('some.key') without PHPStan or Psalm complaining that it might get an array or null.

13.15 adds two typed accessors on the Translator that return a concrete type:

public function label(): string
{
    return trans()->string($this->name);
}

public function options(): array
{
    return trans()->array($this->options_key);
}
Enter fullscreen mode Exit fullscreen mode

trans()->string() guarantees a string back. trans()->array() guarantees an array. The pattern mirrors the typed accessors Laravel already ships on config() and request(), so if you've used config()->string('app.name'), this will feel familiar immediately.

It's a small thing. But if your codebase runs Larastan at a high level (and the starter kits now ship at level 7), these two methods delete a category of annoying casts and assert() calls you were writing just to satisfy the analyzer. Small friction, removed everywhere at once.

JSON Schema Unions for AI Structured Output

This one's narrow but it matters if you're building with the Laravel AI SDK.

The JsonSchema component picked up two related capabilities. First, a fromArray() deserializer that turns a raw JSON Schema array back into Type objects, which is the inverse of the serialization that was already there. Second, and more useful day to day, the schema builder now supports multi-type unions through anyOf.

Why does anyOf matter? Structured output is where you force a model to return JSON matching a schema you define. Sometimes a field really can be one of several shapes. A support ticket's resolution might be a string when it's a simple note, or an object when it's a structured escalation. Before anyOf, you couldn't express "this field is one of these distinct schemas" cleanly. Now you can, and since both OpenAI and Gemini support anyOf in their structured output APIs, it passes straight through to the provider.

If you're already returning typed responses from agents, this widens what you can model without dropping back to a loose string field and parsing it yourself. If you haven't touched the AI SDK yet, this is a detail to file away rather than act on.

A Dedicated Cloud Queue Driver

If you deploy on Laravel Cloud and use its managed queues, several changes in 13.15 add up to a proper first-party queue driver rather than a generic one bent to fit.

The practical differences: managed queues now boot before service providers, so the queue connection is ready earlier in the lifecycle. A missing configured queue throws a clear ManagedQueueNotFoundException instead of failing in some vaguer way. FIFO queue name normalization was corrected. And the request ID header was renamed from X-Request-ID to Cloud-Request-ID, now surfaced in your logs, which is the kind of thing you'll be grateful for the first time you're tracing a request through a queued job in production.

There's a related nicety too: Queue::route() now accepts enum cases for both the queue name and the connection. If you've moved your queue names into a backed enum to stop typing magic strings, you can pass the enum directly instead of calling ->value everywhere.

If you're not on Laravel Cloud, none of this affects you. If you are, it's a quiet reliability upgrade to the part of your stack you least want surprises in. The broader queue setup worth knowing is in the queue jobs guide, and the managed queues vs Horizon comparison covers the trade-off if you're deciding between them.

The Smaller Fixes Worth Knowing

A few more changes that won't headline a release but will save someone an afternoon:

The Number helper got three edge cases fixed. Number::fileSize() now handles negative byte values instead of producing nonsense, Number::trim() stops returning null for INF and NAN, and Number::pairs() handles negative step values while throwing a clear exception on a zero step so you don't get an infinite loop. If you've ever fed user-derived numbers into these, the hardening is welcome.

Two infinite-recursion bugs got squashed: one when a model scope is defined using a private attribute, and one when a middleware group references itself. Both are the kind of mistake that's easy to make in a large app and miserable to debug, so having the framework fail sanely instead of hanging is a real improvement.

And a small operational win: queue:failed now shows the real class name of failed jobs instead of an unhelpful placeholder, which makes triaging a failed queue that much faster. The full list of queue and Artisan commands is in the Laravel Artisan Commands reference if you want to see what else is available there.

Should You Update?

Yes, and specifically for the date_equals fix if you validate dates anywhere sensitive. That's not a "nice to have when you get around to it" change, it's a closed security hole, and the update is a backward-compatible composer update with no code changes required.

The rest is upside you collect for free on the way. Typed translation accessors clean up strict-typed code, anyOf widens what you can model with AI structured output, and the Cloud queue driver makes managed queues more predictable. None of it breaks anything, because these are minor releases and Laravel doesn't ship breaking changes in those.

If you're several minor versions behind, the update also pulls in everything from 13.16 through the latest, so it's worth checking the changelog for anything relevant to your app beyond what's here. If you're on an older major version entirely, the Laravel 12 to 13 upgrade guide covers that jump first.

FAQ

Do I need to change any code to get the date_equals security fix?

No. The fix is entirely inside the framework's validation logic. Running composer update to pull in Laravel 13.15 or later closes the bypass, and legitimate DateTime comparisons keep working exactly as before.

What's the difference between trans()->string() and __()?

__() returns array|string|null, which forces casts or assertions in strictly-typed code. trans()->string() guarantees a string return type, and trans()->array() guarantees an array. They're for typed method bodies and static analysis, not for replacing __() in Blade, where the broad type is fine.

Does the JSON Schema anyOf support work with all AI providers?

The anyOf support is in Laravel's schema builder, and both OpenAI and Gemini accept anyOf in their structured output APIs, so it passes through to those providers. Support for other providers depends on whether their structured output API accepts unions.

Is the Cloud queue driver useful if I don't use Laravel Cloud?

No. These changes specifically target Laravel Cloud's managed queues. If you run your own queue infrastructure with Redis, SQS, or a database driver, they don't apply, though Queue::route() accepting enums is a general improvement everyone gets.

Is Laravel 13.15 the latest version?

Probably not by the time you read this. Laravel ships minor releases roughly weekly, so 13.16 and beyond are likely already out. Because minor releases are backward-compatible, updating gets you 13.15's features plus everything newer in one step.

Wrapping Up

Laravel 13.15 is mostly a quiet release, and that's fine. Not every version needs a marquee feature. The typed translation accessors are a clean quality-of-life win, the anyOf support quietly expands what the AI SDK can do, and the Cloud queue driver makes managed queues more predictable.

But the date_equals fix is the one that changes your calendar. Update for that, collect the rest as a bonus, and move on.

Source: dev.to

arrow_back Back to Tutorials