Route::prefix
Prepends a URI segment to every route defined inside the group.
prefix(string $prefix): Illuminate\Routing\RouteRegistrarParameters
| Parameter | Type | Description |
|---|---|---|
$$prefix | string | The URI segment to prepend. Leading and trailing slashes are normalised, and the prefix may itself contain route parameters such as 'accounts/{account_id}'. |
Return value
Illuminate\Routing\RouteRegistrarA RouteRegistrar, so the call chains into group() or into further attribute methods such as middleware() and name(). Chained onto a single route instead — Illuminate\Routing\Route::prefix() — it accepts string|null and returns that route.
Errors
Nothing. A prefix containing a parameter that no handler accepts simply arrives as an extra positional argument.
Route::prefix factors a shared URI segment out of a set of routes. Everything inside the group gains it, and nested groups append: prefix('admin') wrapping prefix('reports') yields /admin/reports/.... Slashes are inserted for you.
When to reach for it
An admin area, an API version, a tenant or locale segment, any block of routes sharing a common root. A prefix may contain parameters — prefix('accounts/{account_id}') — and that parameter is then passed to every handler in the group, ahead of the route's own parameters.
What trips people up
- URIs only. A prefix does not touch route names.
Route::prefix('admin')alone leaves the route namedusers, notadmin.users; pair it withRoute::name('admin.')when you want both. - routes/api.php already has one. The
/apiprefix is applied bywithRouting(apiPrefix: ...), so addingprefix('api')inside that file produces/api/api/.... - A prefixed parameter shifts argument positions. Every closure and controller method in the group must accept it first, or the wrong value lands in the wrong argument.
- Prefixes cannot be removed by a nested group; they only accumulate.
Example
<?php
use App\Http\Controllers\Admin\UserController;
use Illuminate\Support\Facades\Route;
// Prefix alone changes only the URI...
Route::prefix('admin')->group(function () {
Route::get('/users', [UserController::class, 'index'])->name('users');
});
// ...so pair it with a name prefix to keep names in step.
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/settings', [SettingsController::class, 'edit'])->name('settings');
});
echo route('users');
echo PHP_EOL;
echo route('admin.settings');
// http://example.com/admin/users
// http://example.com/admin/settingsWhen to use it
- Put every administrative screen under /admin without repeating the segment.
- Version an API by wrapping controllers in prefix('v1') with a matching name prefix.
- Capture a tenant id in the path with prefix('accounts/{account_id}').
- Nest a reports section inside an already-prefixed admin group.
Frequently asked questions
Does Route::prefix change route names too?
No, it only prepends to URIs. Use Route::name('admin.') alongside it if the route names should carry the same segment.
Can a route prefix contain a parameter?
Yes — Route::prefix('accounts/{account_id}'). The captured value is passed to every handler in the group as its first argument, before the route's own parameters.
Why are my API URLs coming out as /api/api/...?
routes/api.php is already loaded with an apiPrefix of 'api' from bootstrap/app.php, so adding ->prefix('api') inside that file applies it twice. Prefix with the version segment only.
More examples
A parameter inside the prefix
The generated URL shows both parameters filled in from the same array. The argument order is the detail that bites: {account_id} comes from the prefix and is therefore the first argument to every handler in the group, so a controller method written as show(Invoice $invoice) would silently receive the account id.
<?php
use Illuminate\Support\Facades\Route;
Route::prefix('accounts/{account_id}')
->whereNumber('account_id')
->group(function () {
// The prefix parameter arrives FIRST, before the route's own.
Route::get('/invoices/{invoice}', function (string $accountId, string $invoice) {
return $accountId.'/'.$invoice;
})->name('invoices.show');
});
echo route('invoices.show', ['account_id' => 42, 'invoice' => 7]);
// http://example.com/accounts/42/invoices/7API versioning, and the api.php double-prefix trap
Two versions of the same resource coexist because each group carries a distinct URI prefix and a distinct name prefix — without the name prefixes the second apiResource would overwrite photos.index from the first. The /api segment in the output comes from bootstrap/app.php, not from these groups.
<?php
use Illuminate\Support\Facades\Route;
// routes/api.php — /api is ALREADY applied by the framework.
// Do NOT write ->prefix('api') here; you would get /api/api/...
Route::prefix('v1')->name('api.v1.')->middleware('auth:sanctum')->group(function () {
Route::apiResource('photos', PhotoController::class);
});
Route::prefix('v2')->name('api.v2.')->middleware('auth:sanctum')->group(function () {
Route::apiResource('photos', V2\PhotoController::class);
});
echo route('api.v1.photos.index');
echo PHP_EOL;
echo route('api.v2.photos.show', ['photo' => 3]);
// http://example.com/api/v1/photos
// http://example.com/api/v2/photos/3
Discussion