Laravel 13.37: strict Rule::numeric(), queue filters and JPEG XL

Laravel 13.37 adds Rule::numeric(strict: true), queue:failed --queue, multi-id queue:forget, JPEG XL in the Image API, and withWhereHas column selection with a callback.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/laravel-13-37-numeric-strict-queue-jxl.md
Laravel 13.37 adds Rule::numeric(strict: true), queue:failed --queue, multi-id queue:forget, JPEG XL in the Image API, and withWhereHas column selection with a callback.
Show the diff. Do not commit.
On this page · 14 sections
  1. What’s new at a glance
  2. Strict Rule::numeric()
  3. Filter failed jobs by queue
  4. Forget several failed jobs at once
  5. JPEG XL in the Image API
  6. withWhereHas column selection with a callback
  7. SFTP/FTP mime type from the path
  8. RedirectResponse implements Responsable
  9. Smaller changes worth knowing
  10. Upgrade notes
  11. Recap
  12. FAQ
  13. Next steps
  14. Sources

You typed 42 into a JSON API and Laravel accepted "42" as numeric. Your failed_jobs table is a mess of queues you cannot filter. And you still cannot ask withWhereHas('items:id,order_id,sku', …) without a RelationNotFoundException. Laravel 13.37 fixes each of those, and none of them change behaviour until you opt in.

TL;DR

  • 13.37.0 was released on October 16, 2026: a real minor with opt-in features plus a long list of fixes.
  • Rule::numeric(strict: true) is the object form of numeric:strict. String "42" fails; int/float 42 passes. Rule::when() / Rule::unless() also accept Stringable.
  • php artisan queue:failed --queue=emails filters failed jobs. php artisan queue:forget 1 2 3 deletes several IDs in one go.
  • Image API: ->toJxl()->quality(80) next to HEIC/AVIF. withWhereHas('items:id,order_id,sku', fn…) no longer throws. RedirectResponse implements Responsable.

What you'll learn

  • Rule::numeric(strict: true) vs plain numeric
  • queue:failed --queue and multi-id queue:forget
  • JPEG XL (toJxl()) on the Image API
  • withWhereHas column selection with a callback
  • RedirectResponse as Responsable, SFTP mime-by-path, and the smaller fixes

Who it's for: Laravel 13 developers (validation, queues, Image API, Eloquent, FTP/SFTP)

Tested with: Laravel 13.37.0 · PHP 8.4 · Pest 4 (6 tests / 20 assertions: Rule::numeric strict, queue command signatures, RedirectResponse instanceof Responsable). Image JXL / FTP mime-by-path applies (API present; no Imagick JXL encode or live SFTP run).

What’s new at a glance#

FeaturePRReplaces
Rule::numeric(strict: true)#62031Remembering the string 'numeric:strict'
queue:failed --queue=#61971Grepping the failed-jobs table by hand
queue:forget with multiple IDs#61901One queue:forget call per failed job
Image API JPEG XL (toJxl())#62019Converting to JXL outside Laravel
withWhereHas + column list + callback#62039RelationNotFoundException on items:id,…
SFTP/FTP detect_mime_type_using_path#61972Downloading the whole file just for mimeType()
RedirectResponse implements Responsable#62036Controllers typed Responsable|Renderable rejecting redirect()

Strict Rule::numeric()#

Laravel already had Rule::numeric() as a fluent builder (->min(), ->max(), ->integer(), …). What it could not say was the strict mode that rejects numeric strings. In 13.37, pass strict: true:

Laravel 13.36, then Laravel 13.37:

app/Http/Requests/StorePaymentRequest.phpphp
public function rules(): array
{
    return [
        'amount' => ['required', 'numeric:strict', 'min:0.01'],
    ];
}
app/Http/Requests/StorePaymentRequest.phpphp
use Illuminate\Validation\Rule;

public function rules(): array
{
    return [
        'amount' => [
            'required',
            Rule::numeric(strict: true)->min(0.01), 
        ],
    ];
}

(string) Rule::numeric(strict: true) is numeric:strict. We ran it on 13.37:

PayloadRule::numeric()Rule::numeric(strict: true)
42 (int)passespasses
3.14 (float)passespasses
"42" (string)passesfails
"not-a-number"failsfails

Use strict mode on JSON APIs where you want the client to send real numbers, not quoted ones. Keep the loose rule when forms (or query strings) always arrive as strings.

Tip: Rule::when / unless accept Stringable too

#62030 widens the $rules / $defaultRules parameter types on Rule::when() and Rule::unless() to include Stringable. Handy when a rule object (or a helper that returns one) is Stringable.

Filter failed jobs by queue#

queue:failed listed every failed job. On a busy app that mixes emails, default and horizon, that table is noisy. 13.37 adds --queue:

bash
php artisan queue:failed --queue=emails
php artisan queue:failed --queue=emails --json

Only rows whose queue column matches are shown. We checked the command signature on 13.37: the option is there and filters with ->where('queue', $queue).

Forget several failed jobs at once#

Before, queue:forget took a single ID. Clearing a bad deploy meant a loop. Now the id argument is an array:

bash
php artisan queue:forget 12 45 91

Each ID is forgotten in turn. Missing IDs print an error and the command exits with failure so scripts can detect a partial clear.

JPEG XL in the Image API#

HEIC and AVIF already had helpers. JPEG XL gets the same treatment:

app/Actions/StoreAvatar.phpphp
use Illuminate\Support\Facades\Image;

$path = Image::fromStorage('avatars/photo.jpg', 'public')
    ->cover(400, 400)
    ->toJxl()
    ->quality(80)
    ->storePublicly('avatars', 'public');

toJxl() sets the output format to jxl (Intervention’s JxlEncoder under the hood). Your Imagick/GD build must actually support JXL encoding; we confirmed the method exists on Illuminate\Image\Image in 13.37 but did not run an encode on this box.

Heads up: needs a JXL-capable image driver

If Imagick was built without JPEG XL, toJxl() will fail at encode time the same way an unsupported HEIC would. Check your driver before flipping production uploads to JXL.

withWhereHas column selection with a callback#

with('items:id,order_id,sku') has worked for years. withWhereHas accepted the same column list without a callback, but threw when you passed both:

php
// Before 13.37 — RelationNotFoundException:
// Call to undefined relationship [items:id,order_id,sku]
Order::withWhereHas('items:id,order_id,sku', function ($query) {
    $query->where('status', 'backordered');
})->get();

#62039 parses the column list the same way with() does, so the snippet above loads only those columns on matching items. Use it when you want the constraint and a lean eager load in one call.

SFTP/FTP mime type from the path#

On SFTP and FTP disks, Storage::mimeType($path) used to download the file into memory so Flysystem could sniff bytes. For big assets that is expensive. 13.37 adds an opt-in config flag:

config/filesystems.phpphp
'sftp' => [
    'driver' => 'sftp',
    // ...
    'detect_mime_type_using_path' => true, 
],

When true, Flysystem guesses from the extension/path instead of reading the body. Default stays false, so existing apps are unchanged. Same key works on the ftp driver and via Storage::build([...]).

RedirectResponse implements Responsable#

Controllers typed as returning Responsable|Renderable could return a view, but redirect()->back() failed the type check because RedirectResponse was not Responsable. Now it is:

app/Http/Controllers/WelcomeController.phpphp
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Contracts\Support\Responsable;

class WelcomeController
{
    public function __invoke(): Responsable|Renderable
    {
        if (! auth()->check()) {
            return redirect()->route('login');
        }

        return view('welcome');
    }
}

We asserted redirect('/dashboard') instanceof Responsable and that toResponse() still produces a redirect. Your Vue/Inertia apps are unaffected; this is a PHP typing win for classic controllers and action classes.

Smaller changes worth knowing#

ChangePRWhat it fixes
ScheduledTaskFailed::$runtime#61970Failed schedule events now carry how long they ran (nullable float), like the finished event
Exception reporting#62047Cleaner reporting around Cloud / cached config / avoiding double reports
Unique lock release on failed push#62010, #62045Unique job/listener locks are released when the push to the queue fails
encrypted:object / encrypted:collection always dirty#62032Unchanged encrypted casts no longer mark the model dirty
Cached Attribute accessor stale after set#62035Setting the underlying attribute refreshes a cached accessor
Eloquent cursor() extra models#62001Avoids creating spare model instances while iterating
Soft-delete restore() with joins#61998Restore works when the query has joins
Nested JSON:API include N+1#62000Nested includes no longer fire N+1 queries
migrate:status skipped ≠ pending#61989Skipped migrations are not listed as pending
DynamoDbStore::many() numeric keys#62029Numeric cache keys keep the correct values
latest() / oldest() with joins#62024Default order column is qualified
Faked Sleep in limiter block()#62017Rate-limiter timeouts respect Sleep::fake()
onOneServer + withoutOverlapping race#62007Fixes the race when both are used on a job

Upgrade notes#

Run composer update laravel/framework (13.37.0 or later). Everything above is opt-in: loose numeric still accepts strings, queue:failed without --queue lists everything, JXL is only used when you call toJxl(), and detect_mime_type_using_path defaults to false.

Two fixes can change behaviour you might have been living with: encrypted object/collection casts stop looking dirty when unchanged, and skipped migrations no longer show as pending in migrate:status. Both match what the APIs already promised.

See strict numeric reject a string

bash
php artisan tinker --execute="
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
dump(Validator::make(['n' => '42'], ['n' => Rule::numeric(strict: true)])->fails());
dump((string) Rule::numeric(strict: true)->min(1));
"

You should see true (the string failed) and numeric:strict|min:1.

Recap#

  • Rule::numeric(strict: true) is the typed form of numeric:strict; string numbers fail, real ints/floats pass.
  • queue:failed --queue=… filters the failed list; queue:forget 1 2 3 clears several IDs at once.
  • Image API gains toJxl()->quality(…), same shape as HEIC/AVIF.
  • withWhereHas('rel:cols', fn) works; SFTP/FTP can sniff mime from the path; RedirectResponse is Responsable.
  • Plenty of solid fixes: unique lock release, encrypted dirty tracking, cursor memory, soft-delete restore with joins, and more.

FAQ#

Will Rule::numeric(strict: true) break my HTML forms?

Possibly, if the browser sends numbers as strings (it usually does). Use strict mode on JSON APIs; keep the loose rule for classic form requests.

Does queue:failed --queue change the failed_jobs table?

No. It only filters what is printed (or returned as JSON). The rows stay until you retry or forget them.

Do I need a special PHP extension for JPEG XL?

You need an image driver (typically Imagick) built with JXL support. The Laravel helper is there in 13.37; the encode still depends on the underlying library.

Is detect_mime_type_using_path on by default?

No. It defaults to false so mimeType() behaviour stays byte-based until you opt in on the SFTP/FTP disk config.

Why did redirect() fail my Responsable return type before?

RedirectResponse did not implement Responsable. In 13.37 it does, so controllers typed Responsable|Renderable can return redirect() or a view.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.