spatie/laravel-route-discovery
Automatically discover Laravel routes by scanning controllers and views instead of manually defining them. Configure discovery in your routes files and use PHP attributes to customize names, middleware, and more for each discovered route.
Installation:
composer require spatie/laravel-route-discovery
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\RouteDiscovery\RouteDiscoveryServiceProvider"
Basic Setup:
Add this to your routes/web.php (or routes/api.php):
use Spatie\RouteDiscovery\Facades\Discover;
Discover::controllers()->in(app_path('Http/Controllers'));
First Use Case:
Annotate a controller method with [Route]:
use Spatie\RouteDiscovery\Attributes\Route;
class UserController extends Controller
{
#[Route('/users', name: 'users.index')]
public function index()
{
return view('users.index');
}
}
Now, visiting /users will trigger the index method.
#[Route], #[Middleware], #[View], and #[Resource].config/route-discovery.php (if published) for customization.Controller-Based Routing:
Replace Route::get(), Route::post(), etc., with attribute-based routing in controllers:
#[Route('/dashboard', name: 'dashboard')]
public function showDashboard()
{
return view('dashboard');
}
Middleware Assignment: Apply middleware to specific routes or controllers:
#[Middleware(['auth', 'verified'])]
class ProfileController extends Controller
{
#[Route('/profile', name: 'profile')]
public function show()
{
// ...
}
}
View-Based Routing:
Use #[View] to auto-generate routes for Blade views:
Discover::views()->in(resource_path('views'));
Then annotate Blade files:
@route('/about', name: 'about')
Resource Routes:
Define RESTful resource routes with #[Resource]:
#[Resource]
class PostController extends Controller
{
// All CRUD methods auto-discovered
}
Dynamic Segments: Use parameters in route paths:
#[Route('/posts/{post}', name: 'posts.show')]
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
Incremental Adoption:
Start by migrating one controller at a time. Use Discover::controllers()->in() to include specific directories.
API + Web Separation:
Use separate Discover calls for web and api routes:
// routes/web.php
Discover::controllers()->in(app_path('Http/Controllers/Web'));
// routes/api.php
Discover::controllers()->in(app_path('Http/Controllers/API'));
Testing:
Test routes by asserting their existence in routes/web.php or via Route::has():
$this->assertTrue(Route::has('users.index'));
Cache Warmup:
Run php artisan route:clear and php artisan route:cache after changes to reflect updates.
Custom Attributes: Extend the package by creating custom attributes:
#[Attribute]
class RateLimited implements RouteAttribute
{
public function __construct(public string $limit) {}
public function handle(RouteDefinition $route): void
{
$route->middleware('throttle:' . $this->limit);
}
}
Route Model Binding:
Leverage Laravel's built-in binding (e.g., Post $post) in annotated methods.
API Versioning: Use middleware or subdirectories to version API routes:
Discover::controllers()
->in(app_path('Http/Controllers/Api/V1'))
->prefix('api/v1');
Fallback Routes:
Combine attribute-based routes with traditional Route::fallback() for catch-all logic.
Route Caching:
php artisan route:clear and route:cache.php artisan route:list to verify routes after changes.Namespace Conflicts:
Discover::controllers()->in().Middleware Order:
#[Middleware] runs after global middleware but before route-specific middleware in web.php.->before() or ->after() on Discover to control order:
Discover::controllers()->in(...)->before('auth');
View Discovery Quirks:
Discover::views()->in() only works with Blade files. Static HTML files are ignored.#[Route] for non-Blade routes.Attribute Overrides:
Route::get() definitions. Avoid mixing both in the same app.Route Not Found:
#[Route] vs @Route).php artisan route:list to debug.Middleware Not Applied:
'auth' vs 'auth:sanctum').#[Middleware] or Discover config.Cache Issues:
php artisan config:clear) if routes persist after changes.Excluded Directories:
->exclude() to ignore specific directories:
Discover::controllers()->in(app_path('Http/Controllers'))->exclude('Tests');
Route Naming:
index → users.index).name in #[Route] for clarity.Attribute Precedence:
#[Middleware] on the controller) apply to all methods.Custom RouteAttribute:
Implement Spatie\RouteDiscovery\Attributes\RouteAttribute to create reusable logic:
#[Attribute]
class ApiDoc implements RouteAttribute
{
public function handle(RouteDefinition $route): void
{
$route->addTags(['api']);
}
}
Event Listeners:
Listen to RouteDiscovered events to log or modify routes dynamically:
use Spatie\RouteDiscovery\Events\RouteDiscovered;
RouteDiscovered::listen(function (RouteDiscovered $event) {
logger()->info('Discovered route:', ['uri' => $event->route->uri]);
});
Service Provider:
Override the registerRoutes method in your service provider to customize discovery:
public function registerRoutes()
{
Discover::controllers()
->in(app_path('Http/Controllers'))
->prefix('app')
->middleware('web');
}
How can I help you explore Laravel packages today?