Laravel v13.42.0 was tagged on 10 November 2026. It is a feature-packed minor release: typed uuid() and ulid() helpers for request input, a new MigrationFailed event, reasons on skipped scheduled tasks, a hook for custom lost-connection detection, Uri::origin() and macros on PendingProcess. This tutorial walks through each one with code you can drop into a Laravel 13 app today.
Note: the same release day also shipped a security fix for temporary signed URLs (backported to 12.72.0), which we covered in a separate post. Updating to 13.42 gives you that fix as well.
Upgrade first
composer update laravel/framework
php artisan --version # Laravel Framework 13.42.0 or later
One dependency change to watch: the framework now requires league/uri 7.6 or later (needed by Uri::origin()). Composer will pull it in, but check if you pin that package yourself.
1. Typed UUID and ULID input with $request->uuid() and ulid()
Laravel already had $request->date() and $request->enum() for typed input. Now you get two more:
use Illuminate\Http\Request;
public function show(Request $request)
{
$orderId = $request->uuid('order_id'); // Ramsey\Uuid\UuidInterface|null
$invoice = $request->ulid('invoice'); // Symfony\Component\Uid\Ulid|null
if ($orderId === null) {
abort(422, 'order_id is required');
}
return Order::findOrFail($orderId->toString());
}
The rules are simple:
- Missing or empty input returns
null. - Invalid input throws an exception, so validate first if you want a friendly 422 message.
ulid()only accepts a real ULID string. A UUID passed to it throws instead of being silently converted.
Because the methods live in the InteractsWithData trait, they also work on validated input, Fluent, Uri query strings, component attributes and console command input:
$validated = $request->validate(['order_id' => ['required', 'uuid']]);
$orderId = $request->safe()->uuid('order_id');
Pairing the uuid validation rule with safe()->uuid() gives you a clean error for bad input and a typed object for good input.
2. React to broken deploys with the MigrationFailed event
On MySQL, schema changes such as ALTER TABLE are not wrapped in a transaction, so a migration that fails halfway can leave your schema half-applied. The new Illuminate\Database\Events\MigrationFailed event fires when a migration throws. It carries the same migration, method and name properties as the other migration events, plus the exception. The exception is still re-thrown after the event fires, so your deploy still fails loudly.
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Events\MigrationFailed;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
public function boot(): void
{
Event::listen(function (MigrationFailed $event) {
// Put the app into maintenance mode before users hit a broken schema
Artisan::call('down');
Log::critical("Migration [{$event->name}] failed", [
'method' => $event->method,
'exception' => $event->exception->getMessage(),
]);
});
}
Whether you call down automatically is a team decision. At minimum, sending an alert to your logging or chat channel gives you a head start on the rollback.
3. Know why a scheduled task was skipped
The ScheduledTaskSkipped event now has a reason property. It holds a ScheduledTaskSkipReason enum with three cases: ConditionNotMet, Paused and RanOnAnotherServer.
use Illuminate\Console\Events\ScheduledTaskSkipped;
Event::listen(function (ScheduledTaskSkipped $event) {
Log::info('Scheduled task skipped', [
'task' => $event->task->getSummaryForDisplay(),
'reason' => $event->reason?->value,
]);
});
This is especially handy with onOneServer(): you can now tell the difference between “the when() condition was false” and “another server already ran it”.
schedule:list –json now keeps each task’s timezone
php artisan schedule:list --json now returns each task’s own cron expression and timezone instead of converting everything to the app timezone. Passing --timezone still converts every task. If a monitoring script parses this JSON, re-check tasks that call ->timezone(), because their expression, timezone and next_due_date values will change.
4. Custom lost-connection detection
Laravel reconnects to the database when an exception matches a list of known “lost connection” messages. If your database proxy produces its own error text, you can now teach Laravel to recognise it without binding a custom detector class:
use Illuminate\Database\LostConnectionDetector;
// In a service provider's boot() method
LostConnectionDetector::detectUsing(function (Throwable $e) {
return str_contains($e->getMessage(), 'vttablet: rpc error');
});
If your callback returns false, Laravel falls back to its built-in message list, so you only need to handle the extra cases.
5. Compare URL origins with Uri::origin()
Uri::origin() returns the scheme, host and port, normalised. For example, https://App.example.com:443 becomes https://app.example.com. That makes same-origin checks easy and less error-prone than string comparison:
use Illuminate\Support\Uri;
function isSameOrigin(string $redirect, string $appUrl): bool
{
return Uri::of($redirect)->origin() === Uri::of($appUrl)->origin();
}
isSameOrigin('https://App.example.com:443/dashboard', 'https://app.example.com'); // true
A typical use is validating a ?redirect= parameter before sending the user there, which helps prevent open redirects.
6. Macros on PendingProcess
Before this release, a macro registered on the Process factory only worked as the first call in a chain. PendingProcess is now macroable, so your helpers work anywhere in the chain:
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
PendingProcess::macro('withNodeBin', function () {
return $this->env(['PATH' => '/opt/node/bin:'.getenv('PATH')]);
});
Process::timeout(120)->withNodeBin()->run('npm run build');
Testing the new features
The new helpers are easy to cover in feature tests. For example, a quick Pest test for typed UUID input:
use Illuminate\Support\Str;
it('accepts a valid order uuid', function () {
$order = Order::factory()->create();
$this->getJson('/orders/show?order_id='.$order->id)
->assertOk();
});
// Assumes the route validates order_id with the 'uuid' rule, as shown earlier
it('rejects an invalid order uuid', function () {
$this->getJson('/orders/show?order_id=not-a-uuid')
->assertStatus(422);
});
For the MigrationFailed listener, use Event::fake([MigrationFailed::class]) in a test that runs a deliberately broken migration, then assert the event was dispatched. That way you know your alerting works before a real deploy depends on it.
Other fixes worth knowing
cursor()now respectsModel::preventLazyLoading().TokenGuard::validate()now hashes the token when hashing is enabled (it previously returnedfalsefor valid tokens).foreignIdFor()creates abigintcolumn for models with$keyType = 'integer'.php artisan install:broadcasting --ablynow installs the renamedably/pubsub-serverpackage. Apps onably/ably-php1.x keep working.
Key takeaway
Laravel 13.42 is a quality-of-life release you can adopt with a plain composer update. Start using $request->uuid() for typed IDs, add a MigrationFailed listener to your deploy safety net, and log skip reasons for scheduled tasks. Full details are in the laravel/framework releases and the Laravel News release write-up.
