Route::resource
Registers the seven conventional CRUD routes for a controller in one declaration.
resource(string $name, string $controller, array $options = []): Illuminate\Routing\PendingResourceRegistrationParameters
| Parameter | Type | Description |
|---|---|---|
$$name | string | The resource name, used as the URI segment and the route-name prefix. Dot notation such as 'photos.comments' nests one resource inside another. |
$$controller | string | The controller class name, normally PhotoController::class. Generate a matching controller with php artisan make:controller PhotoController --resource. |
$$optionsoptional | array= [] | Registration options such as only, except, names, parameters and middleware, passed as an array instead of chained fluently. |
Return value
Illuminate\Routing\PendingResourceRegistrationA PendingResourceRegistration whose fluent methods — only(), except(), names(), parameters(), scoped(), shallow(), middleware(), middlewareFor(), withoutMiddlewareFor(), missing(), withTrashed() — trim or reshape the generated set before it is registered.
Errors
Nothing at registration. At request time an unresolved implicit binding returns 404 unless missing() supplies alternative behaviour.
One call to Route::resource registers seven routes against one controller:
| Verb | URI | Action | Route Name |
|---|---|---|---|
| GET | /photos | index | photos.index |
| GET | /photos/create | create | photos.create |
| POST | /photos | store | photos.store |
| GET | /photos/{photo} | show | photos.show |
| GET | /photos/{photo}/edit | edit | photos.edit |
| PUT/PATCH | /photos/{photo} | update | photos.update |
| DELETE | /photos/{photo} | destroy | photos.destroy |
When to reach for it
Whenever a controller genuinely maps onto a model's CRUD lifecycle. Trim the set with only() or except() rather than registering routes you never implement — every registered route is a reachable URL.
What trips people up
- Ordering. Supplemental routes must be declared before the
resourcecall, or/photos/{photo}matches/photos/popularfirst. - Parameter names are singularized.
Route::resource('users', ...)produces{user}; override withparameters(['users' => 'admin_user']). - Nesting deepens URIs.
photos.commentsyields/photos/{photo}/comments/{comment};shallow()drops the parent id from routes that do not need it. - If you need methods outside the seven, that is usually a sign the controller should be split.
Example
<?php
use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route;
// Supplemental routes go FIRST, before the resource declaration.
Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class)
->middleware(['auth', 'verified'])
->withoutMiddlewareFor('index', ['auth', 'verified']);
echo route('photos.index');
echo PHP_EOL;
echo route('photos.show', ['photo' => 3]);
echo PHP_EOL;
echo route('photos.edit', ['photo' => 3]);
// http://example.com/photos
// http://example.com/photos/3
// http://example.com/photos/3/editWhen to use it
- Stand up an admin CRUD screen for a model with one line plus a generated controller.
- Expose only index and show for a read-only public listing.
- Nest comments under photos and use shallow() so /comments/{comment} does not repeat the photo id.
- Apply auth to every generated route and drop it from index alone with withoutMiddlewareFor.
Frequently asked questions
How do I register only some of the seven resource routes?
Chain ->only(['index', 'show']) or ->except(['destroy']) onto the Route::resource call. Both accept the action names index, create, store, show, edit, update and destroy.
Why is my extra route on a resource controller returning the wrong action?
It was declared after Route::resource, so the generated /photos/{photo} show route matched the URI first. Move supplemental routes above the resource line.
What route parameter name does Route::resource generate?
The singularized resource name — 'photos' gives {photo}, 'users' gives {user}. Override it with ->parameters(['users' => 'admin_user']).
More examples
Trimming the set with only() and except()
only() and except() are two spellings of the same trim — the albums line leaves index, show and edit registered. names() changes the route name without touching the URI, which is why route('posts.build') still resolves to /posts/create.
<?php
use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route;
// Read-only: registers photos.index and photos.show only.
Route::resource('photos', PhotoController::class)->only([
'index', 'show',
]);
// The inverse spelling: everything except the write actions.
Route::resource('albums', AlbumController::class)->except([
'create', 'store', 'update', 'destroy',
]);
// Renaming a generated route name:
Route::resource('posts', PostController::class)->names([
'create' => 'posts.build',
]);
echo route('photos.show', ['photo' => 3]);
echo PHP_EOL;
echo route('posts.build');
// http://example.com/photos/3
// http://example.com/posts/createNested and shallow resources
shallow() splits the set in two: routes that genuinely need the parent keep /photos/{photo}, while routes identified by the child id alone drop it — and their route names shorten from photos.comments.show to comments.show. Forgetting that rename is the usual cause of a RouteNotFoundException after adding shallow().
<?php
use App\Http\Controllers\CommentController;
use Illuminate\Support\Facades\Route;
Route::resource('photos.comments', CommentController::class)->shallow();
// Registers:
// GET /photos/{photo}/comments photos.comments.index
// GET /photos/{photo}/comments/create photos.comments.create
// POST /photos/{photo}/comments photos.comments.store
// GET /comments/{comment} comments.show
// GET /comments/{comment}/edit comments.edit
// PUT/PATCH /comments/{comment} comments.update
// DELETE /comments/{comment} comments.destroy
echo route('photos.comments.index', ['photo' => 3]);
echo PHP_EOL;
echo route('comments.show', ['comment' => 9]);
// http://example.com/photos/3/comments
// http://example.com/comments/9
Discussion