In Laravel, how do nested route groups combine their prefix, name, middleware, where, controller and domain attributes?
answer
- prefixes joined with a slash
- names concatenated exactly as written
- middleware and where merged
- controller and domain replaced by the inner group
- check with route:list --name
basics
~20 sNested 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 sEach `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 linesphp artisan route:list --name=admin.orders -v
# only routes on the franchise host pattern
php artisan route:list --domain=example.comgo deeper
Know that prefix, name and middleware groups share attributes, and that name prefixes need a trailing dot.
Explain the merge rule for each attribute: prefixes and names accumulate, middleware and where merge, controller and domain are replaced.
Keep nesting shallow, verify merged routes with route:list in reviews, and spot missing dots or replaced domains before they ship.
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