In Laravel, how do where() and helpers such as whereNumber constrain route parameters, and what happens when a URL fails them?
answer
- a regex per parameter
- whereNumber is [0-9]+
- whereIn: fixed values or enum cases
- failed constraint: route does not match
- routing check, not validation
basics
~20 s->where('id', '[0-9]+') attaches a regex to one parameter, and whereNumber, whereAlpha, whereAlphaNumeric, whereUuid, whereUlid and whereIn are shortcuts; a URL that fails the regex does not match that route, so it usually ends in 404.
solid answer
~30 sConstraints are part of route matching. `->where('year', '[0-9]{4}')` or `->where(['year' => '[0-9]{4}', 'slug' => '[a-z0-9-]+'])` attaches regexes, and the helpers expand to fixed ones: `whereNumber` is `[0-9]+`, `whereAlpha` `[a-zA-Z]+`, `whereAlphaNumeric` `[a-zA-Z0-9]+`, plus `whereUuid`, `whereUlid` and `whereIn($param, $values)`, which also accepts enum cases. When a segment fails, that route simply does not match; the router tries the others and, if none fits, returns 404. It is not validation, so there is no 422 and no error message. Watch the helpers' exact regexes: `whereAlpha` rejects hyphenated slugs such as `budget-vote`, and `whereNumber` accepts `0042`.
code
php · 15 lines<?php
// routes/web.php - news archive
use App\Enums\Section;
use App\Http\Controllers\NewsController;
use Illuminate\Support\Facades\Route;
Route::get('/news/{year}', [NewsController::class, 'year'])
->where('year', '[0-9]{4}');
Route::get('/news/{slug}', [NewsController::class, 'show'])
->where('slug', '[a-z0-9-]+');
Route::get('/sections/{section}', [NewsController::class, 'section'])
->whereIn('section', Section::cases());go deeper
Know where() and the helper names, and that a URL failing a constraint does not reach the route.
Explain the exact regexes behind the helpers, why a failed constraint ends in 404 rather than 422, and how constraints separate look-alike routes.
Diagnose unexplained 404s by reading constraints, avoid helpers whose regex is wrong for the data, and keep constraints as routing guards rather than validation.
Standardise URL shapes and identifier formats so constraints stay simple, and decide which identifiers appear in public URLs at all.
## Why constrain a parameter By default a route parameter matches any run of characters except `/`. That is often too loose: - `/news/{year}` would treat `/news/budget-vote` as a year. - An ID route would try to load `/invoices/abc` and fail deep in the code instead of at the router. - Two routes with the same shape, such as `/news/{year}` and `/news/{slug}`, cannot be told apart. A **constraint** is a regular expression the segment must match for the route to match. ## Writing constraints with where() `where()` is a method on the route instance, so it chains after the definition: ```php use Illuminate\Support\Facades\Route; Route::get('/news/{year}/{month}', $action) ->where(['year' => '[0-9]{4}', 'month' => '0[1-9]|1[0-2]']); Route::get('/news/{year}/{month}/{slug}', $action) ->where('slug', '[a-z0-9-]+'); ``` You write only the pattern; Laravel places it inside the compiled route regex, so no delimiters are needed. ## The helper methods The helpers come from the `CreatesRegularExpressionRouteConstraints` trait, which routes, resource registrations and group registrars share. Each takes one parameter name or an array of names: | Helper | Regex it applies | Notes | |---|---|---| | `whereNumber('id')` | `[0-9]+` | any length, leading zeros allowed, no sign | | `whereAlpha('name')` | `[a-zA-Z]+` | ASCII letters only: no hyphens, digits or accents | | `whereAlphaNumeric('code')` | `[a-zA-Z0-9]+` | still no hyphens or underscores | | `whereUuid('id')` | the 8-4-4-4-12 hex pattern | either case | | `whereUlid('id')` | 26 Crockford base32 characters, first one `0`-`7` | | | `whereIn('section', [...])` | the values joined with `\|` | accepts backed enum cases too | `whereIn` joins the values into an alternation without escaping them, so a value containing a regex character, such as a dot, matches more than the literal text. Use plain slugs as values. ## What a failed constraint does Constraints are checked while the router looks for a matching route: 1. The router compares the request path with each route's compiled regex, constraints included. 2. A segment that fails its constraint makes that route a non-match, exactly as if the URI were different. 3. The router continues with the remaining routes. 4. If no route matches the path under any verb, the result is a **404 Not Found**. There is no special error for "constraint failed": no exception names the parameter, and no 422 response is produced. That is the key difference from validation. | | Route constraint | Request validation | |---|---|---| | Checks | path segments | query, body and file input | | Runs | while matching routes | inside the matched route | | Failure | route skipped, usually 404 | 422 or redirect with errors | | Purpose | route selection | rejecting bad input with feedback | ## Using constraints to separate routes In a news archive, `/news/{year}` and `/news/{slug}` look identical to the router. Constraining the first with `->where('year', '[0-9]{4}')` makes `/news/budget-vote` fail it and fall through to the slug route, while `/news/2026` matches the year route. The constrained route must be the one registered first; an unconstrained route registered earlier would still catch everything. ## Constraints beyond a single route The same helpers work wherever the constraints trait is used: - **Groups**: `Route::whereNumber('year')->prefix('news/{year}')->group(...)` constrains the parameter for every route in the group. - **Resource registrations**: a pending resource registration accepts `where()` and the helpers too, constraining the resource's parameters in one call. - **Global rules**: `Route::pattern()` applies a regex to a parameter name across the whole app. Constraining at the group level keeps a family of archive routes consistent: one change to the year rule reaches them all, and a single route can still override it with its own `where()`. ## Pitfalls interviewers probe - **`whereAlpha` on slugs.** `budget-vote` contains a hyphen, so the route 404s. Use `->where('slug', '[a-z0-9-]+')`. - **`whereNumber` as validation.** `[0-9]+` accepts `0000` and 30-digit numbers; range checks belong elsewhere. - **Expecting a cast.** A constrained parameter still arrives as a string. - **Silent 404s while debugging.** When a URL 404s unexpectedly, compare the segment with the route's constraints before suspecting the controller.
- Why can whereIn('format', ['tar.gz', 'zip']) match more than the two listed values?`whereIn()` joins the values with `|` to build the regex and does not escape them. The dot in `tar.gz` is therefore a regex wildcard, so `tarXgz` also matches. Keep `whereIn` values to plain slugs, or write an explicit, escaped pattern with `where()`.
- A teammate adds ->whereNumber('id') and calls it input validation. What do you point out?The constraint only decides whether the route matches: failures produce a 404 with no message, it accepts values like `0000` or 40 digits, and it does nothing for query or body input. It is a routing guard. Real rules such as ranges or existence belong in validation or in route model binding.
saying these in an interview costs you the question
- A failed where() constraint returns a 422 validation error
- whereAlpha accepts slugs that contain hyphens
- whereNumber limits the value to a valid integer range
- whereIn escapes regex characters in its values
- A constrained parameter arrives already cast to an int