Route::where

Constrains a route parameter with a regular expression so non-matching URIs 404.

Signature
where(array|string $name, string|null $expression = null): $this

Parameters

ParameterTypeDescription
$$namearray|stringThe route parameter name, or an associative array of parameter name to expression for several at once.
$$expressionoptionalstring|null= nullThe regular expression the parameter must match, written without delimiters or anchors. Omitted when the first argument is an array.

Return value

$thisThe Route instance, so further constraints and modifiers can be chained. Called on the facade before group() it returns a RouteRegistrar and takes a single array: where(array $where).

Errors

Nothing. A request whose parameter fails the constraint does not match the route at all, and if no other route matches, Laravel returns a 404 HTTP response.

where narrows what a {parameter} will accept. Its real value is disambiguation: with /user/{id} constrained to [0-9]+ and /user/{name} to [A-Za-z]+, both can coexist and the router picks the right one.

The shortcuts

Common patterns have named helpers, all chainable: whereNumber, whereAlpha, whereAlphaNumeric, whereUuid, whereUlid, and whereIn($param, ['movie', 'song']), which also accepts a backed enum's cases(). Reach for these before writing a regex by hand.

Group and global forms

Before group() the facade form takes an array and applies to every route inside. For an application-wide rule, call Route::pattern('id', '[0-9]+') in AppServiceProvider::boot() — every {id} anywhere is then constrained.

What trips people up

  • A failed constraint is a 404, not a validation error. The route simply does not match, so a mistyped pattern looks exactly like a missing page.
  • No delimiters or anchors. Write [0-9]+, not /^[0-9]+$/.
  • Slashes are excluded by default. A parameter matches anything but / unless you allow it with where('search', '.*'), and that only works in the final segment.

Example

Example · php
<?php

use App\Enums\CategoryEnum;
use Illuminate\Support\Facades\Route;

Route::get('/user/{id}', [UserController::class, 'showById'])
    ->where('id', '[0-9]+')
    ->name('user.by-id');

Route::get('/user/{name}', [UserController::class, 'showByName'])
    ->where('name', '[A-Za-z]+')
    ->name('user.by-name');

// Several at once, and the named shortcuts.
Route::get('/user/{id}/{name}', fn (string $id, string $name) => $id.$name)
    ->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

Route::get('/category/{category}', [CategoryController::class, 'show'])
    ->whereIn('category', CategoryEnum::cases())
    ->name('category.show');

echo route('user.by-id', ['id' => 12]);
echo PHP_EOL;
echo route('user.by-name', ['name' => 'ada']);
// http://example.com/user/12
// http://example.com/user/ada

When to use it

  • Force an id segment to be numeric so a slug never reaches the controller as an id.
  • Let /user/{id} and /user/{name} share a path by constraining one to digits and the other to letters.
  • Restrict a category segment to a fixed list with whereIn or a backed enum's cases().
  • Allow a full path inside the last segment of a search route with where('search', '.*').

Frequently asked questions

Why does my route 404 instead of showing a validation error?

A where() constraint is part of route matching, not validation — if the parameter does not match the pattern the route is skipped entirely, and a 404 is returned when nothing else matches.

What are the shortcuts for common route constraints?

whereNumber, whereAlpha, whereAlphaNumeric, whereUuid, whereUlid and whereIn. They chain like where() and cover most cases without writing a regular expression.

How do I allow a slash inside a route parameter?

Constrain it with ->where('search', '.*'). Encoded forward slashes are only supported within the last route segment.

More examples

Shortcuts, group constraints and global patterns

Three scopes of the same idea, narrowest first: per-route shortcuts, a group-wide array, and Route::pattern in a service provider which constrains every {id} in the application without touching a route file. Nested groups merge where conditions rather than replacing them.

Example · php
<?php

use Illuminate\Support\Facades\Route;

// Chainable named shortcuts.
Route::get('/user/{id}/{name}', fn (string $id, string $name) => $id)
    ->whereNumber('id')
    ->whereAlpha('name');

Route::get('/orders/{order}', [OrderController::class, 'show'])->whereUuid('order');
Route::get('/events/{event}', [EventController::class, 'show'])->whereUlid('event');

// Applied to every route in a group.
Route::where(['id' => '[0-9]+'])->group(function () {
    Route::get('/posts/{id}', [PostController::class, 'show'])->name('posts.show');
    Route::get('/pages/{id}', [PageController::class, 'show'])->name('pages.show');
});

// app/Providers/AppServiceProvider.php — application-wide.
public function boot(): void
{
    Route::pattern('id', '[0-9]+');
}

echo route('posts.show', ['id' => 5]);
// http://example.com/posts/5

Allowing slashes in the final segment

The .* pattern is the documented way to let a parameter swallow slashes, turning /search/docs/routing/where into a single $search value of 'docs/routing/where'. The commented line is the limitation: a slash-bearing parameter must be the final segment, otherwise the router cannot tell where the parameter ends.

Example · php
<?php

use Illuminate\Support\Facades\Route;

// By default {search} would stop at the first slash.
Route::get('/search/{search}', function (string $search) {
    return $search;
})->where('search', '.*')->name('search');

// Encoded forward slashes are only supported in the LAST route segment,
// so this does NOT work as intended:
// Route::get('/search/{path}/results', ...)->where('path', '.*');

echo route('search', ['search' => 'docs/routing/where']);
// http://example.com/search/docs/routing/where

Discussion

  • Be the first to comment on this lesson.