skip to content

Path Segments & Regex Rules

Route parameters are required {id} or optional {id?} segments, constrained by where() regexes, whereNumber-style helpers or global Route::pattern rules. Interviewers ask why a URL 404s.

on this pageshow

explore

questions

5

In Laravel, how do you declare required and optional route parameters, and what must the handler supply for an optional one?

level: juniorimportance: must knowfreq 70%

answer

  1. segments in curly braces
  2. question mark makes it optional
  3. handler argument needs a default
  4. values arrive by position
  5. ->defaults() on the route itself

basics

~20 s

Wrap a URI segment in braces, {year}, to capture it, and add a question mark, {month?}, to make it optional; the handler argument for an optional segment needs a default value, such as ?string $month = null.

solid answer

~40 s

A route parameter is a URI segment in braces: `Route::get('/news/{year}', ...)` captures `2026` from `/news/2026` and passes it to the closure or controller action as a string. Names should be letters and underscores. Adding `?`, as in `/news/{year}/{month?}`, lets the segment be absent; when it is, Laravel drops it from the route's parameters, so the handler argument must declare a default, typically `?string $month = null`, or PHP throws an `ArgumentCountError`. You can also give the route itself a default with `->defaults('month', '01')`. Keep optional segments at the end of the URI, and remember that plain parameters are passed by position, not by argument name.

code

php · 15 lines
php
<?php

// routes/web.php
use Illuminate\Support\Facades\Route;

Route::get('/news/{year}/{month?}', function (string $year, ?string $month = null) {
    return $month
        ? "Archive for {$year}-{$month}"
        : "Archive for {$year}";
});

// Route-level default instead of a handler default
Route::get('/news/{year}/page/{page?}', function (string $year, string $page) {
    return "{$year}, page {$page}";
})->defaults('page', '1');

go deeper

for a junior

Know the brace syntax, the question mark for optional segments, and that an optional argument needs a default value.

for a middle

Explain that absent segments are left out of the argument list, why that causes ArgumentCountError without a default, and when a route-level default is better.

for a senior

Design archive-style URLs with optional segments only at the end, keep argument order aligned with the URI, and read parameters in middleware through the request.

for a principal

Set URL conventions for optional filters versus query strings so public URLs stay stable and predictable across teams.

## Capturing URI segments A **route parameter** captures part of the URL so the handler can use it. In Laravel you write it in curly braces inside the route's URI: ```php use Illuminate\Support\Facades\Route; Route::get('/news/{year}/{slug}', function (string $year, string $slug) { return "Article {$slug} from {$year}"; }); ``` A request to `/news/2026/budget-vote` runs the closure with `$year = '2026'` and `$slug = 'budget-vote'`. Key facts: - **Values are strings.** The router extracts text from the path; any conversion to a number, enum or model happens later. - **Names should be letters and underscores**, per the routing docs: `{news_id}`, not `{news-id}`. - **By default a parameter matches anything except `/`**, so each parameter covers exactly one path segment. - **Values are URL-decoded**, because the router matches against the decoded path. ## Optional segments Appending `?` makes a parameter optional: `/news/{year}/{month?}` matches both `/news/2026` and `/news/2026/05`. What happens when the segment is missing: 1. The router still matches the route, and the missing parameter is simply **absent** (not an empty string). 2. Laravel leaves absent parameters out of the list it passes to the handler. 3. If the handler's argument has a **default value**, that default is used. 4. If it has none, PHP receives too few arguments and throws an **`ArgumentCountError`**. That is why the docs insist on a default for every optional parameter: ```php Route::get('/news/{year}/{month?}', function (string $year, ?string $month = null) { // /news/2026 -> $month is null }); ``` Keep optional segments **at the end** of the URI. An optional segment followed by a required one cannot be left out in practice, and it makes URL generation confusing. ## How values reach the handler Laravel passes plain route parameters to the closure or controller method **by position**: the first captured segment goes to the first non-service argument, and so on. The argument names do not have to match the placeholders. Class-typed arguments such as `Illuminate\Http\Request` are resolved from the container and slotted in at their positions, and the docs recommend listing them **before** the route parameters. The exact splicing rules, and the bugs they produce when ignored, are a topic of their own. ## Two ways to default a missing segment | Where | How | Effect | |---|---|---| | Handler argument | `?string $month = null` or `string $page = '1'` | used when the segment is absent; the route knows nothing about it | | Route definition | `->defaults('month', '01')` | the route fills the parameter itself, so every consumer sees `'01'` | `->defaults()` is a method on the route instance. Laravel uses the same mechanism internally: `Route::redirect` and `Route::view` store their settings as route defaults. A route-level default is useful when middleware or `$request->route('month')` should see the value too, not just the handler. ## Reading a parameter outside the handler Any code with the request can read a parameter: `$request->route('year')` returns the value, and `$request->route('month', '01')` supplies a fallback. This is handy in middleware, where the handler's arguments are not available. ## A news-archive example ```php use App\Http\Controllers\ArchiveController; Route::get('/news/{year}/{month?}', [ArchiveController::class, 'index']) ->whereNumber(['year', 'month']); // ArchiveController public function index(string $year, ?string $month = null) { /* ... */ } ``` `/news/2026` lists the whole year; `/news/2026/05` lists May. ## Optional segments or a query string? Optional path segments are not the only way to express "maybe filter by month". The alternative is a query string, `/news/2026?month=05`, read with request input instead of a route parameter. - **Path segments** suit values that identify a resource or a canonical page: an archive for a year, or a year and a month. They are part of the route, show up in `route:list`, and can be constrained. - **Query strings** suit optional, combinable filters and sort orders: `?section=politics&sort=oldest`. They do not multiply route definitions, and their order does not matter. - A rule of thumb in interviews: if removing the value leaves a meaningful page and several such values combine freely, it belongs in the query string; if it names a distinct page, it belongs in the path. ## Pitfalls interviewers probe - **No default on an optional argument**: works for `/news/2026/05`, crashes with `ArgumentCountError` on `/news/2026`. - **Expecting an empty string** for a missing segment: the parameter is absent, so the default applies. - **Hyphens in parameter names**: stick to letters and underscores. - **Relying on argument names** to map values: plain values are positional, so keep argument order identical to the URI order.

  • What exactly happens in Laravel when {month?} is absent and the handler declares string $month with no default?
    The router matches the route, but the month parameter is absent, so Laravel passes only the year. The handler requires two arguments, and PHP throws an `ArgumentCountError` (too few arguments), which surfaces as a 500. Declaring `?string $month = null`, or adding `->defaults('month', ...)` to the route, fixes it.
  • How does middleware read the year from /news/{year} before the controller runs?
    Through the request: `$request->route('year')` returns the captured value, and a second argument gives a fallback, as in `$request->route('month', '01')`. Middleware never sees the handler's arguments, so this is the standard way to read parameters there.

saying these in an interview costs you the question

  • An optional parameter works without a default in the handler
  • A missing optional segment arrives as an empty string
  • Laravel maps plain route parameters to arguments by name
  • Route parameter names may contain hyphens
  • Route parameters arrive already cast to int
open as a page

In Laravel, how do where() and helpers such as whereNumber constrain route parameters, and what happens when a URL fails them?

level: middleimportance: must knowfreq 60%

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.

open as a page

In Laravel, what does Route::pattern() do, where should you call it, and how does it interact with a route's own where()?

level: middleimportance: should knowfreq 30%

basics

~10 s

Route::pattern('id', '[0-9]+') applies that regex to every route parameter named id; call it in AppServiceProvider::boot so it exists before routes load, and a where() on a group or a single route overrides it.

open as a page

A Laravel news-archive route injects a service beside {year} and {month?}, and the handler gets swapped values or crashes when month is absent; why, and how do you fix it?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Laravel passes route values to the handler by position and splices container-resolved services in; an absent optional segment is dropped, so a missing default crashes. List services first, then parameters in URI order, optional ones last with defaults.

open as a page

In Laravel, how do you let a route parameter capture slashes, such as a nested topic path, and what limits apply?

level: middleimportance: nice to knowfreq 20%

basics

~10 s

A route parameter matches everything except a slash by default; ->where('path', '.*') lets it capture slashes, and the routing docs support that only when the parameter is the last segment of the URI.

open as a page