laravel/framework: Middleware priority fix: appendToPriorityList() now works when the anchor is the first item
Contributed by GabeSilvaDev
Part of the framework v13.36.0 release
What changed
The fix lives entirely in src/Illuminate/Foundation/Http/Kernel.php, in the shared helper addToMiddlewarePriorityRelative() that powers both addToMiddlewarePriorityAfter() and addToMiddlewarePriorityBefore() — and therefore both appendToPriorityList() and prependToPriorityList() in your bootstrap/app.php middleware configuration.
The root cause was a sentinel collision: the insertion index was initialized to 0 for "after" insertions, but 0 is also a real position in the priority list. A match against the first item ($middlewareIndex > $index → 0 > 0) never registered, so the code fell through to the "$index === 0 means nothing matched" branch and appended the middleware to the end. The strict > comparison had a second effect: a reference sitting exactly at the current insertion point was skipped too.
The patch replaces the sentinel with null:
$indexstarts asnulland is only set once a reference actually matches.- The "after" comparison becomes
is_null($index) || $middlewareIndex >= $index, so a match at the insertion point now counts. - The special-case
array_unshift()branch is gone; appending now happens only when$indexisnull(nothing matched) or when the index equals the end of the list. addToMiddlewarePriorityBefore()behavior is preserved — a "before" insertion at position 0 now goes througharray_splice(), which is equivalent to the oldarray_unshift().
Two regression tests were added to KernelTest: one inserting after the first priority item, and one inserting after adjacent references. Both fail on 13.x without this patch.
The bug in practice
The default priority list starts with HandlePrecognitiveRequests, which makes the first-item case easy to hit. Say you register a tenant initializer that must run before cookies, session, and auth:
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToPriorityList(
HandlePrecognitiveRequests::class,
EnsureTenantIsSet::class,
);
})
Before the fix, EnsureTenantIsSet silently landed after Authorize::class, at the very end of the priority list — running behind session, auth, and throttling. After the fix, it sits directly after HandlePrecognitiveRequests, before EncryptCookies, exactly where the call says it should.
The same defect distorted the array form when references were adjacent. On a list [A, B, C, D]:
addToMiddlewarePriorityAfter(['B', 'C'], 'X')produced[A, B, X, C, D]— wrong.addToMiddlewarePriorityAfter(['C', 'B'], 'X')produced[A, B, C, X, D]— correct, but only by accident.
The result depended on the order you listed the references. With the >= comparison and the null sentinel, both orderings now yield [A, B, C, X, D], placing X after the highest-indexed reference.
Why it matters
Middleware ordering is load-bearing, often in security-sensitive ways: session must start before auth reads it, throttling should run before expensive work, tenant context usually needs to exist before authorization checks. A primitive that promises "insert after X" but quietly appends to the end turns those guarantees into bugs that no exception will surface. Worse, the array form was nondeterministic with respect to input order, so refactoring how you listed references could change runtime behavior. This fix makes the API do what it documents, deterministically.
Upgrade impact
There are no signature changes and no new API — this is purely a behavioral correction on the 13.x line. Practically:
- If you never touched the priority list, nothing changes.
- If you worked around the bug — anchoring to a later middleware, reordering the whole list by hand, or calling
prependToPriorityList()to compensate — your workaround keeps working, but it may now be redundant. Revisit it and simplify if you can. - If an app relied on the buggy end-of-list placement (unlikely, but possible if someone noticed the wrong position and coded around it elsewhere), the affected middleware will now run earlier. Audit any
appendToPriorityList()call whose anchor isHandlePrecognitiveRequestsor whose references are adjacent in the list, and confirm the new, earlier position is safe for your middleware. prependToPriorityList()/addToMiddlewarePriorityBefore()is functionally untouched, including its insert-at-front path.
Takeaways
appendToPriorityList()no longer silently appends to the end when the reference middleware is the first priority-list item.- Adjacent references in the array form now insert after the last of them, regardless of the order you list them.
- The fix is internal to
Kernel::addToMiddlewarePriorityRelative(); no API changes, and "before" insertions behave exactly as before. - When upgrading from v12.70.0, review priority-list customizations — middleware anchored near the top of the list will now genuinely run near the top.
- Two new
KernelTestcases lock the behavior in: after the first item, and after adjacent items.