0

Route parameters — required, optional, regex constraints

Beginner5 min read·lv-06-002

Concept

Route parameters let you capture dynamic URI segments and inject them into your controller or closure. Laravel distinguishes between required parameters — {id} — and optional parameters — {name?}. Required parameters will cause a 404 if the segment is missing; optional parameters need a default value in the action signature.

Internally, when Laravel registers a route with parameters, Illuminate\Routing\Route::compileRoute() converts the URI pattern into a PCRE regex. The parameter name becomes a named capture group: {id} becomes (?P<id>[^/]+) by default. When the route matches an incoming request, Route::bindParameters() extracts the named groups and stores them so they can be injected into the controller method or passed to route model binding.

Regex constraints restrict what a parameter will match. Route::pattern() sets a global constraint for all routes sharing that parameter name. Per-route constraints use ->where() chaining. You can also use Laravel's built-in constraint helpers: ->whereNumber('id'), ->whereAlpha('slug'), ->whereAlphaNumeric('code'), ->whereUuid('uuid'). These helpers compile to the appropriate regex internally and improve readability without requiring raw regex strings.

A real-world pattern is using ->whereUuid('id') on API routes that identify resources by UUID rather than integer auto-increment. This prevents route collision where a slug might be confused for an ID, and it provides implicit validation — requests with a malformed UUID never reach the controller, returning 404 instead of triggering an exception.

One important gotcha: optional parameters are only valid at the end of a URI. /users/{id?}/posts is not valid because the router cannot determine where the segment boundary lies when id is absent. If you need optional mid-path segments, use separate route registrations or a query parameter instead.

Code Example

php
<?php

use App\Http\Controllers\UserController;
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

// Required parameter — {id} must be present
Route::get('/users/{id}', [UserController::class, 'show']);

// Optional parameter — {name?} with default in controller
Route::get('/greet/{name?}', function (string $name = 'Guest') {
    return "Hello, {$name}!";
});

// Regex constraint inline — only digits
Route::get('/users/{id}', [UserController::class, 'show'])
    ->where('id', '[0-9]+');

// Built-in constraint helpers (cleaner than raw regex)
Route::get('/users/{id}', [UserController::class, 'show'])
    ->whereNumber('id');

Route::get('/posts/{slug}', [PostController::class, 'show'])
    ->whereAlpha('slug');

Route::get('/resources/{uuid}', [UserController::class, 'showByUuid'])
    ->whereUuid('uuid');

// Multiple constraints on multiple parameters
Route::get('/teams/{teamId}/users/{userId}', [UserController::class, 'teamMember'])
    ->whereNumber('teamId')
    ->whereNumber('userId');

// Global pattern — applies to every route with {id}
// Defined in RouteServiceProvider::boot()
Route::pattern('id', '[0-9]+');

// Controller method receiving the parameter
// app/Http/Controllers/UserController.php
class UserController extends Controller
{
    public function show(int $id): JsonResponse
    {
        $user = User::findOrFail($id);
        return response()->json($user);
    }
}

Interview Q&A

Q: How does Laravel convert a URI pattern like /users/{id} into a regex for matching, and what default regex does it use for each parameter?

Illuminate\Routing\Route::compileRoute() delegates to Illuminate\Routing\RouteCompiler (which wraps Symfony's RouteCompiler). Each {param} is replaced with a named capture group (?P<param>[^/]+) by default — the pattern [^/]+ matches one or more characters that are not a forward slash. When a ->where('param', 'regex') constraint is set, the custom regex replaces [^/]+ inside the named group. Optional parameters {param?} wrap the named group in (?P<param>[^/]+)? and also make the preceding slash optional.


Q: What happens when a route parameter fails its regex constraint — does Laravel throw an exception or return a 404?

When a regex constraint fails, the route simply does not match. Router::findRoute() moves on to the next registered route. If no route matches, Laravel throws Symfony\Component\HttpKernel\Exception\NotFoundHttpException, which the exception handler renders as a 404 response. No exception bubbles from the route matching itself — the 404 is the correct semantic result because the resource at that URL shape does not exist.


Q: Can you set a global regex constraint and override it for a specific route?

Yes. Route::pattern('id', '[0-9]+') in RouteServiceProvider::boot() sets the global default. A per-route ->where('id', '[a-zA-Z0-9\-]+') on a specific route overrides the global pattern only for that route. The per-route constraint takes precedence because Route::compileRoute() checks the route's own $wheres array first before falling back to the router's global patterns.