Route to Controller with Optional Parameter(s) Using Laravel

The Laravel routing system is incredibly flexible, allowing you to define routes that lead to controllers and support parameters. In this article, we’ll learn the steps needed to create a route to a controller with optional parameter(s).

Optional route parameters in Laravel are defined using curly braces and an added question mark, for example: Route::get('/posts/{year?}', function() {}); We can also define multiple like: '/posts/{year?}/{month?}' This accepts URI’s like: /posts or /posts/2023 or /posts/2023/july

Let’s walk through building an example application. Imagine an e-commerce website with many products, spread out over multiple categories. We’d want to allow users to be able to browse through all products, products in a specific or products of a specific brand. With Laravel’s elegant routing system, we can achieve this using just one route with optional parameters for category and brand. Let’s get started!

Step 1: Create and Run the Migration

First, create the migration for the models:

php artisan make:migration create_products_table

Edit the migration as follows:


use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
     * Run the migrations.
    public function up(): void
        Schema::create('products', function (Blueprint $table) {
            // Add other columns for product details as needed

     * Reverse the migrations.
    public function down(): void

Run the migration using the following command:

php artisan migrate

Step 2: Create the Model

Create the models for Project by running the following Artisan command:

php artisan make:model Product

For the Project model, use the following code:


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
     * The attributes that are mass assignable.
     * @var array
    protected $fillable = [
        'name', 'category', 'brand',

Step 3: Define the Route

Open your routes/web.php file and define a route using the Route::get or Route::post method. For two optional parameters, enclose placeholders in curly braces {}.


use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('/products/{category?}/{brand?}', [ProductController::class, 'index']);

By making use of optional parameters, this definition will accept each of the following route examples:

Example RoutePurpose
/productsList all products
/products/ClothingList all products in “Clothing”
/products/Clothing/NikeList all products in “Clothing” where the brand is “Nike”
Example Route Variations That Will Match Our Definition in routes/web.php

Step 4: Create the Controller

Generate a controller named ProductController to manage the logic for displaying product details based on the optional parameters.

php artisan make:controller ProductController

Step 5: Add Controller Code

In the ProductController.php file, define a method for the route, and include parameters for the two optional segments: $category and $brand.


namespace App\Http\Controllers;

use App\Models\Product;

class ProductController extends Controller
    public function index($category = null, $brand = null)
        $query = Product::query();

        if ($category) {
            $query->where('category', $category);

        if ($brand) {
            $query->where('brand', $brand);

        $products = $query->get();

        return view('products.index', compact('products'));

Understanding the Logic

In this controller method, we begin by constructing a query for the Product model. If a category is provided, the query narrows down the products to that category. Similarly, if a brand is specified, the query refines the results to products from that brand. When neither parameter is given, the query fetches all products.

Step 6: Create a View

As defined in the ProductController queried products are passed to the view products.index, where they are displayed in your preferred format.

For the sake of this example, I used a plain table. To follow along, create a file /resources/views/products/index.blade.php and add the following code:

    @forelse ($products as $product)
            {{ $loop->iteration }}
            {{ $product->name }}
            <td colspan="2">
                No products were found.

Step 7: Create Test Data

We will now use Laravel Tinker to add some data which we can use to test the cloning later on. Laravel Tinker is an interactive shell for Laravel that allows you to interact with your application’s code and data.

To start Laravel Tinker run the following command:

php artisan tinker

In the Laravel Tinker prompt you can now copy/paste the following code:

use App\Models\Product;

// Add products with different categories and brands
    'name' => 'Laptop',
    'category' => 'Electronics',
    'brand' => 'Dell',

    'name' => 'Smartphone',
    'category' => 'Electronics',
    'brand' => 'Samsung',

    'name' => 'T-Shirt',
    'category' => 'Clothing',
    'brand' => 'Nike',

    'name' => 'Jeans',
    'category' => 'Clothing',
    'brand' => 'Wrangler',

You should now see the following data in your database:

Screenshot Showing Rows in Table products After Creating Records Using artisan tinker

Step 8: View Results

Run the following command to launch Laravel’s built-in webserver:

php artisan serve

Now when opening each of the URL’s below we can see the correct products are being filtered based on the optional route parameters being filled:

UrlResults in Browser
Table With Example URI’s And How They Look in The Browser

Further Examples

Optional route parameters can be useful in many cases for browsing various types of data. Consider the following real-world examples:

// Browsing events
Route::get('/events/{year?}/{month?}', [EventController::class, 'index']);

// Browsing movies
Route::get('/movies/{genre?}/{rating?}', [MovieController::class, 'index']);

// Browsing courses
Route::get('/courses/{subject?}/{level?}', [CourseController::class, 'index']);

// Browsing posts
Route::get('/posts/{year?}/{month?}', [PostController::class, 'index']);

// Browsing products
Route::get('/products/{category?}/{brand?}', [ProductController::class, 'index']);

// Browsing albums
Route::get('/albums/{artist?}/{year?}', [AlbumController::class, 'index']);


We’ve seen how to define routes with optional parameters using curly braces, like normal route parameters, with an added question mark. This enables you to craft routes like “/products/{year?}/{month?}” which support multiple use cases – from exploring all products to narrowing down by category and brand. This allows you to add flexible navigation features to your own applications. Happy coding!


Johan van den Broek

Johan is the creator of As a child, he began tinkering with various programming languages, many of which have been long forgotten today. Currently, he works exclusively with PHP, and his passion for programming remains to this day.

2 thoughts on “Route to Controller with Optional Parameter(s) Using Laravel

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts