In Laravel, how do you declare required and optional route parameters, and what must the handler supply for an optional one?
answer
- segments in curly braces
- question mark makes it optional
- handler argument needs a default
- values arrive by position
- ->defaults() on the route itself
basics
~20 sWrap 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 sA 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
// 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
Know the brace syntax, the question mark for optional segments, and that an optional argument needs a default value.
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.
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.
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