skip to content

In Laravel, how do nested route groups combine their prefix, name, middleware, where, controller and domain attributes?

level: middleimportance: must knowfreq 55%

answer

  1. prefixes joined with a slash
  2. names concatenated exactly as written
  3. middleware and where merged
  4. controller and domain replaced by the inner group
  5. check with route:list --name

basics

~20 s

Nested Laravel groups append prefixes with a slash and concatenate name prefixes exactly as written, merge middleware (outer first) and where constraints (inner wins on a clash), and let an inner controller or domain replace the outer one.

solid answer

~30 s

Each `group()` pushes its attributes onto the router's group stack, merged with the parent by `RouteGroup::merge()`. **Prefixes** are joined with a single slash: `admin` then `orders` gives `admin/orders`. **Names** are concatenated with no separator, so `name('admin.')` then `name('orders.')` plus `->name('index')` gives `admin.orders.index`, and a missing dot gives `adminorders.index`. **Middleware** lists are merged, outer first, and a route's own `->middleware()` comes after. **`where`** arrays merge, the inner value winning for the same parameter. **`controller`** and **`domain`** are replaced by the inner group's value. For a franchise dashboard, an outer `domain('{franchise}.example.com')->prefix('admin')->name('admin.')` group with an inner `prefix('orders')->name('orders.')->controller(OrderController::class)` group yields URIs like `admin/orders/{order}` named `admin.orders.show`.

code

bash · 4 lines
bash
php artisan route:list --name=admin.orders -v

# only routes on the franchise host pattern
php artisan route:list --domain=example.com

go deeper

for a junior

Know that prefix, name and middleware groups share attributes, and that name prefixes need a trailing dot.

for a middle

Explain the merge rule for each attribute: prefixes and names accumulate, middleware and where merge, controller and domain are replaced.

for a senior

Keep nesting shallow, verify merged routes with route:list in reviews, and spot missing dots or replaced domains before they ship.

for a principal

Decide how route files and group conventions divide areas such as admin and franchise dashboards across teams, so names and URLs stay consistent.

## Groups and the group stack A **route group** shares attributes across the routes defined inside it. With the fluent registrar you chain attributes and finish with `group()`: ```php Route::domain('{franchise}.example.com') ->prefix('admin') ->name('admin.') ->middleware(['auth']) ->group(function () { // routes here share all four attributes }); ``` Internally the router keeps a **group stack**. Entering a group merges its attributes with the group above it and pushes the result; every route created inside is merged with the top of the stack; leaving the group pops it. The merge rules live in `Illuminate\Routing\RouteGroup::merge()`. ## The merge rules | Attribute | Set with | Nested behaviour | |---|---|---| | prefix | `prefix('orders')` | appended: `admin` + `orders` = `admin/orders`, slashes normalised | | name | `name('orders.')` | concatenated exactly: `admin.` + `orders.` = `admin.orders.` | | middleware | `middleware([...])` | merged into one list, outer entries first | | where | `where([...])` or `where*` helpers | merged; the inner value wins for the same parameter | | controller | `controller(OrderController::class)` | the inner group's controller replaces the outer one | | domain | `domain('...')` | the inner group's domain replaces the outer one | The route itself is merged last: - `->name('index')` appends to the inherited name prefix, giving `admin.orders.index`. - `->middleware(...)` on the route is added after the group's middleware. - `->where(...)` on the route overrides the group's constraint for that parameter. - An action given only as a method name (`'show'`) is resolved against the group's `controller`. ## The trailing-dot rule Because names are concatenated with no separator, the dot is your job: 1. `Route::name('admin')` plus `->name('users')` gives `adminusers`. 2. `Route::name('admin.')` plus `->name('users')` gives `admin.users`. The routing docs call this out and always show the trailing dot. A missing dot is invisible until some `route('admin.users')` call throws `RouteNotFoundException`. ## A franchise dashboard, end to end ```php use App\Http\Controllers\OrderController; use Illuminate\Support\Facades\Route; Route::domain('{franchise}.example.com') ->prefix('admin')->name('admin.')->middleware(['auth']) ->group(function () { Route::prefix('orders')->name('orders.') ->controller(OrderController::class) ->group(function () { Route::get('/', 'index')->name('index'); Route::get('/{order}', 'show')->name('show')->whereNumber('order'); }); }); ``` The inner show route ends up with: - domain `{franchise}.example.com` - URI `admin/orders/{order}` - name `admin.orders.show` - middleware `auth` (plus the `web` group from its route file) - action `OrderController@show` ## Why the rules differ by attribute The merge rules follow what each attribute means: - **Hierarchical values accumulate.** A URI prefix and a name prefix describe a position in a tree, so each level adds to the one above. - **Lists merge.** Middleware and `where` constraints are collections; an inner group adds entries, and for constraints the inner, more specific rule wins for the same parameter. - **Single values are replaced.** A route has one controller and one host, so the innermost declaration is the one that applies. The order of chained calls before `group()` does not matter: `Route::prefix('admin')->name('admin.')` and `Route::name('admin.')->prefix('admin')` collect the same attributes, because the registrar applies them together when `group()` is called. ## Verifying the result `php artisan route:list --name=admin.orders -v` prints the merged URI, name, action and middleware for every matching route; `--domain=` filters by host pattern. Reading the merged result is faster than reasoning about four levels of nesting, and it catches missing dots and doubled prefixes immediately. ## Pitfalls interviewers probe - **Missing trailing dot** in a name prefix. - **Expecting an inner `controller()` to add to the outer one.** It replaces it. - **Expecting an inner `domain()` to nest under the outer host.** It replaces it; hosts do not concatenate. - **Deep nesting.** Three or more levels make the merged result hard to see; prefer flatter groups, several route files, or `route:list` checks in review. - **Array-style groups.** `Route::group(['prefix' => 'admin', 'as' => 'admin.'], ...)` still works and uses the same merge rules; `as` is the array key for the name prefix.

  • Inside Route::controller(OrderController::class)->group(...), what happens if one route passes [RefundController::class, 'store'] as its action?
    A full controller-and-method action is used as written; the group controller only fills in actions given as a bare method name such as `'store'`. So that route runs `RefundController@store` while its siblings use `OrderController`. Mixing both styles in one group is legal but easy to misread.
  • An inner group sets ->domain('reports.example.com') inside an outer {franchise}.example.com group. Which host do its routes use?
    Only `reports.example.com`. `RouteGroup::merge()` drops the outer domain whenever the inner group sets one, so hosts are replaced, not combined, and the `{franchise}` parameter no longer exists for those routes. Prefixes and names, by contrast, keep accumulating.

saying these in an interview costs you the question

  • Laravel inserts the dot between nested name prefixes automatically
  • An inner group's controller is added to the outer group's controller
  • Nested domain() calls combine into one longer host
  • A route's own middleware replaces the group's middleware
  • Inner prefixes replace outer prefixes instead of appending