Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mikola_systems's avatar

How to add dynamic parameter to routes

Hello! Need help from experts.

My project has models: Orders, Products, Sales, etc. There is also a City model. I want each city to have its own Orders, Products, Sales, etc. In my administration panel, I plan to make a global switch that will remember the selected city. And depending on the selected city, Orders, Products, Sales, etc. should be displayed.

Right now, routes form paths:

http://localhost/orders
http://localhost/orders/status/1
http://localhost/orders/create
http://localhost/orders/1
http://localhost/products
http://localhost/sales
...

When selecting a city, I would like the following paths to be formed:

http://localhost/city-name/orders
http://localhost/city-name/orders/status/1
http://localhost/city-name/orders/create
http://localhost/city-name/orders/1
http://localhost/city-name/products
http://localhost/city-name/sales
...

List of routes:

Route::prefix('orders')->name('orders')->controller(OrderController::class)->group(function () {
        Route::get('/', 'index');
        Route::get('/status/{status}', 'index')->whereNumber('status')->name('.status');
        Route::get('/create', 'create')->name('.create');
        Route::get('/{order}', 'edit')->whereNumber('order')->name('.edit');
        Route::post('/mass-action', 'massAction')->name('.mass-action');
        Route::post('/store', 'store')->name('.store');
		...
});

Route::prefix('products')->name('products')->controller(ProductController::class)->group(function () {
        Route::get('/', 'index');
        Route::post('/', 'getProducts')->name('.list');
        Route::post('/mass-action', 'massAction')->name('.mass-action');
        Route::get('/create', 'createProduct')->name('.create');
        Route::post('/create', 'storeProduct')->name('.store');
        ...
});

...

Of course, I could pass an additional parameter {city} to each route, but this is somehow irrational. Maybe there are some ideas to implement my wish?

0 likes
7 replies
Glukinho's avatar

You need second layer (level) of grouping, first group by city, then by orders/products:

Route::prefix('{city}')->group(function() {
    Route::prefix('products')->name('products')->controller(ProductController::class)->group(function () {
        Route::post('/', 'getProducts')->name('.list');
        // ...
    });


    Route::prefix('orders')->name('orders')->controller(OrderController::class)->group(function () {
        Route::get('/create', 'create')->name('.create');
        // ...
    });
});

Having typed variable $city in controllers allows you to handle it in URLs (I have simple string type, you probably would have model type like YourCityModel):

class ProductController extends Controller
{
    public function createProduct(Request $request, string $city)
    {
        dd(__CLASS__ . ' | ' . __FUNCTION__ . ' | ' . $city);
    }
}

class OrderController extends Controller
{
    public function create(Request $request, string $city)
    {
        dd(__CLASS__ . ' | ' . __FUNCTION__ . ' | ' . $city);
    }
}
// http://127.0.0.1:8000/london/orders/create
"App\Http\Controllers\OrderController | create | london"

// http://127.0.0.1:8000/paris/products/create
"App\Http\Controllers\ProductController | createProduct | paris"

You may have any levels of grouping, for example to have URL /{client}/{city}/{action}/, just set another group of group of group of routes.

1 like
mikola_systems's avatar

@Glukinho thank you !

However, I thought that it would be necessary to pass this variable when calling each route:

route('products.list', ['city' => 'London']);

And if when choosing a city, save the choice, for example, in a database or even in a session. And then when receiving data, simply extract the selected city and pass it to the selection request. Like this:

$request->session()->put('selected_city',  1);  // 1 - London city ID
...
$selected_city_id = session('selected_city');

$orders = Order::orderBy($this->sort_by, $this->sort_order)->where('city_id', $selected_city_id)->get();

This way you won't have to bother with routes. What do you think about this option?

Glukinho's avatar

@shot_gamer If I get it right, you suggest not having city in routes but instead store it in session/cookies.

I don't like this approach as you will have identical URLs for different cities, so you will not be able to open the same URL and get the same result: an URL https://yoursite.com/products may lead to London products or Paris products depending on what is stored in user's session. This is not what users expect. Having explicit URLs with {city} is better.

1 like
mikola_systems's avatar

@Glukinho, thanks for the answer.

In my case, this is functionality for the admin panel. I will make an amendment for the products, they will be common for all cities, but it will be possible to set the price for each city, availability for the city, etc. But the orders will be tied to cities. What I suggested is already in the process of being implemented, in my opinion, it is perfect for my case.

In the application for users, which I will develop in the future, the city will already be used in the route parameters. Because everything will depend on it: the product catalog, prices, delivery and payment methods.

Glukinho's avatar

@shot_gamer Consider another approach, still have city in URLs, but as query parameter, not a part of path: https://yoursite.com/products?city=london

In this case you still have explicit city in request (as $request->input('city') or even $request->city) but you don't have to tune your routes.

Also this is useful if you would have more parameters to filter by.

1 like
Snapey's avatar

you could have a city of all to indicate no specific city ?

Otherwise you have to duplicate your routes with and without the city segment

martinbean's avatar

@shot_gamer If those models are nested by city, then I’d just including the city identifier in the route URI, i.e. /cities/{city}/orders/{order}.

Of course, I could pass an additional parameter {city} to each route, but this is somehow irrational.

What, exactly, makes this “irrational”? Having a nested parameter also means you get scoping out of the box (i.e. if order 2 belongs to the city of London, you would get an automatic 404 if you tried to access the order via a route like /cities/manchester/orders/2). You can also use the injected City parameter for scoping models when querying:

class CityOrderController extends Controller
{
    public function index(City $city)
    {
        // Query orders via parent city model instance...
        $orders = $city->orders()->paginate();

        // Return response...
    }

    public function show(City $city, Order $order)
    {
        // Return response...
    }
}

Please or to participate in this conversation.