Route::group

Applies shared attributes such as middleware, prefixes and names to a set of routes.

Signature
group(array $attributes, Closure|string $routes): void

Parameters

ParameterTypeDescription
$$attributesarrayShared attributes for every route in the group: middleware, prefix, as (name prefix), domain, controller, namespace, where, and the resource-style options.
$$routesClosure|stringA closure containing the route definitions, or the path to a route file to load — for example base_path('routes/webhooks.php').

Return value

voidNothing. The routes defined inside the closure or file are registered with the group's attributes merged in.

Errors

Nothing. Route definitions inside the closure are evaluated immediately, so a fatal error inside it surfaces at boot.

Route::group shares attributes across many routes so you do not repeat them on each definition. The fluent form — Route::middleware('auth')->prefix('admin')->name('admin.')->group(fn () => ...) — is what almost all Laravel code uses; the array form above is what the fluent builders ultimately call.

The merge rules

These are the part worth memorising. Quoting the docs: middleware and where conditions are merged while names and prefixes are appended. So nesting a group named reports. inside one named admin. gives admin.reports., and both groups' middleware apply. Namespace delimiters and URI slashes are inserted automatically; name segments are not, which is why name prefixes end in a dot.

What trips people up

  • The closure runs at boot, every request. Anything expensive inside it — a database query to build routes — runs on every request that is not served from the route cache.
  • A group cannot un-apply middleware except with withoutMiddleware(), which only removes middleware the group itself added.
  • Subdomain groups must be registered before root-domain routes with the same path, or the root routes win.
  • Passing a file path instead of a closure is how bootstrap/app.php loads extra route files.

Example

Example · php
<?php

use App\Http\Controllers\Admin\ReportController;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth', 'can:access-admin'])
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('/', [DashboardController::class, 'index'])->name('dashboard');

        // Nested: middleware merges, prefix and name append.
        Route::prefix('reports')->name('reports.')->group(function () {
            Route::get('/{year}', [ReportController::class, 'show'])
                ->whereNumber('year')
                ->name('show');
        });
    });

echo route('admin.dashboard');
echo PHP_EOL;
echo route('admin.reports.show', ['year' => 2026]);
// http://example.com/admin
// http://example.com/admin/reports/2026

When to use it

  • Put every admin screen behind auth and a role middleware with one wrapper.
  • Version an API by prefixing a group with v1 and naming it v1..
  • Load routes/webhooks.php with its own middleware and name prefix from bootstrap/app.php.
  • Capture a tenant subdomain as a route parameter available to every route in the group.

Frequently asked questions

How do nested route groups combine their attributes?

Middleware and where constraints are merged, while name prefixes and URI prefixes are appended in order. Namespace separators and URI slashes are inserted for you; the dot in a name prefix is not.

What is the array key for a group's route name prefix?

It is 'as', not 'name' — Route::group(['as' => 'admin.'], ...). The fluent equivalent Route::name('admin.') and Route::as('admin.') both do the same thing.

Can a route group load an external route file?

Yes. Pass a path instead of a closure: Route::middleware('api')->prefix('webhooks')->group(base_path('routes/webhooks.php')). This is how bootstrap/app.php registers additional route files.

More examples

The array form, and loading a route file

The array keys differ from the fluent method names in one place that costs people time: the name prefix is 'as', not 'name'. The second half shows the string form of the second argument — every route defined in routes/webhooks.php inherits the api middleware, the webhooks prefix and the webhooks. name prefix.

Example · php
<?php

use Illuminate\Support\Facades\Route;

// The array form: 'as' is the name prefix, not 'name'.
Route::group([
    'middleware' => ['auth', 'verified'],
    'prefix'     => 'account',
    'as'         => 'account.',
], function () {
    Route::get('/billing', [BillingController::class, 'show'])->name('billing');
});

// bootstrap/app.php — a group can load a whole file instead of a closure.
->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    then: function () {
        Route::middleware('api')
            ->prefix('webhooks')
            ->name('webhooks.')
            ->group(base_path('routes/webhooks.php'));
    },
)

echo route('account.billing');
// http://example.com/account/billing

Subdomain group with a captured parameter

The subdomain placeholder becomes the first argument to the closure, ahead of the URI parameters, and route() needs a value for it when generating the URL. Registering the root-domain /user/{id} first would shadow the tenant route, since the router stops at the first match.

Example · php
<?php

use Illuminate\Support\Facades\Route;

// Register subdomain routes BEFORE root-domain routes with the same path.
Route::domain('{account}.example.com')->group(function () {
    Route::get('/user/{id}', function (string $account, string $id) {
        return $account.':'.$id;
    })->name('tenant.user');
});

Route::get('/user/{id}', [UserController::class, 'show'])->name('user.show');

echo route('tenant.user', ['account' => 'acme', 'id' => 4]);
// http://acme.example.com/user/4

Discussion

  • Be the first to comment on this lesson.