Laravel Routes with Optional Parameters
Tuesday, October 29, 2024
// 2 min read
Table of Contents
This article focuses on optional parameters for Laravel Routes.
If you're looking for a general explanation of how Laravel routing works, read my previous article.
Define Route parameters
To add a parameter in a route definition, you can use curly braces, like this:
Route::get('/post/{id}', [PostController::class, 'show']);
However, this parameter will be required.
Syntax for Optional Parameters
To indicate that a parameter is optional, you can use a question mark (?) after the parameter name.
Let's imagine a scenario in which we need to create a route to list products for an e-commerce store. We also want to allow users to filter products by category. The category will be an optional parameter. Here’s how the route can be defined:
Route::get('/products/{category?}', [ProductController::class, 'index']);
In the controller, you can set the category
parameter optional. When defined, the products can be filtered by category.
<?php
namespace App\Http\Controllers;
use App\Models\Product;
class ProductController extends Controller
{
public function index(?string $category = null)
{
$products = Product::query()
->when($category, function ($query) use ($category) {
return $query->whereHas('category', function ($q) use ($category) {
return $q->where('slug', $category);
});
})
->get();
return view('products.index', compact('products'));
}
}
When the user loads the /products
page, all products are displayed.
However, when the user navigates to the /products/shoes
page, only the products with the shoes
category are shown.
Default value
You can also set a default value for an optional parameter when it is not specified in the URL. For instance, you can create routes for paginated post pages where the page parameter is optional:
Route::get('/posts/{page?}', [PostController::class, 'index']);
In the controller, you can set a default value (1
)for the page
parameter. This way, it will always query the first page if no page is specified (/posts
).
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(int $page = 1)
{
$posts = Post::paginate(perPage: 5, page: $page);
return view('post.index', [
'posts' => $posts,
]);
}
}
Rules for Ordering Optional and Required Parameters
All parameters after the first optional parameter must be optional. You cannot place a required parameter after an optional parameter, if you do, the parameter before the required one also becomes required.
For instance, if you have a route for calendar events that accepts a year
and a month
parameter, you can choose to make either both parameters optional or both required. You can make the year
required and the month
optional. However, you cannot make the year
optional while requiring the month
.
The following route definition won't work:
Route::get('/events/{year?}/{month}', [EventController::class, 'index']);
If you change both parameters to optional in the route definition but still keep the month
parameter required in your controller method, you will receive an error.
public function index(int $year = null, int $month)
The correct route definition for this is:
Route::get('/events/{year?}/{month?}', [EventController::class, 'index']);
This is how I handle it in the controller method:
<?php
namespace App\Http\Controllers;
use App\Models\Event;
class EventController extends Controller
{
public function index(?int $year = null, ?int $month = null)
{
$events = Event::when($year, function ($query, $year) {
return $query->where(function ($q) use ($year) {
return $q->whereYear('start_date', $year)->orWhereYear('end_date', $year);
});
})->when($month, function ($query, $month) {
return $query->where(function ($q) use ($month) {
return $q->whereMonth('start_date', $month)->orWhereMonth('end_date', $month);
});
})->get();
return view('event.index', compact('events'));
}
}
It's important to understand that you cannot place an optional parameter between required route segments.
For instance, in the following URL pattern for events, the year
parameter must be required, otherwise, the route won't match for either events
or events/2024
.
This won't work:
Route::get('/events/{year?}/month/{month?}', [EventController::class, 'index']);
But this will for the following events/2024/month
:
Route::get('/events/{year}/month/{month?}', [EventController::class, 'index']);
Here are the most important things you need to know about optional routes. For more information, read my article about the basics of routing in Laravel and refer to the routing section of the official Laravel documentation.