Route::resource

Registers the seven conventional CRUD routes for a controller in one declaration.

Signature
resource(string $name, string $controller, array $options = []): Illuminate\Routing\PendingResourceRegistration

Parameters

ParameterTypeDescription
$$namestringThe resource name, used as the URI segment and the route-name prefix. Dot notation such as 'photos.comments' nests one resource inside another.
$$controllerstringThe controller class name, normally PhotoController::class. Generate a matching controller with php artisan make:controller PhotoController --resource.
$$optionsoptionalarray= []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:

VerbURIActionRoute Name
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.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 resource call, or /photos/{photo} matches /photos/popular first.
  • Parameter names are singularized. Route::resource('users', ...) produces {user}; override with parameters(['users' => 'admin_user']).
  • Nesting deepens URIs. photos.comments yields /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

Example · php
<?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/edit

When 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.

Example · php
<?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/create

Nested 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().

Example · php
<?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

  • Be the first to comment on this lesson.