Skip to main content
Laravel Modular automatically loads route files from your modules, making it easy to keep your routes organized alongside your module code.

Route Files

All PHP files in a module’s routes/ directory are automatically loaded. Routes are loaded alphabetically by filename.

Default Route File

When you create a module, a default route file is generated:
app-modules/blog/routes/blog-routes.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/posts', function() {
    return 'Blog posts';
});
Route files are loaded automatically - you don’t need to register them manually.

Organizing Routes

You can organize routes into multiple files within the routes/ directory:
app-modules/blog/
└── routes/
    ├── api.php
    ├── web.php
    └── admin.php

Web Routes

app-modules/blog/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Modules\Blog\Controllers\PostController;

Route::middleware('web')->group(function () {
    Route::get('/posts', [PostController::class, 'index'])
        ->name('blog.posts.index');
    
    Route::get('/posts/{post}', [PostController::class, 'show'])
        ->name('blog.posts.show');
});

API Routes

app-modules/blog/routes/api.php
<?php

use Illuminate\Support\Facades\Route;
use Modules\Blog\Controllers\Api\PostController;

Route::middleware('api')->prefix('api')->group(function () {
    Route::apiResource('posts', PostController::class);
});

Admin Routes

app-modules/blog/routes/admin.php
<?php

use Illuminate\Support\Facades\Route;
use Modules\Blog\Controllers\Admin\PostController;

Route::middleware(['web', 'auth', 'admin'])->prefix('admin')->group(function () {
    Route::resource('posts', PostController::class);
});

Route Naming Conventions

Prefix route names with your module name to avoid conflicts:
Route::get('/posts', [PostController::class, 'index'])
    ->name('blog.posts.index');
This prevents naming conflicts when multiple modules have similar routes:
// Blog module
Route::name('blog.')->group(function () {
    Route::get('/posts', ...)->name('posts.index'); // blog.posts.index
    Route::get('/posts/{post}', ...)->name('posts.show'); // blog.posts.show
});

Route Groups

Group routes by module features using route groups:
app-modules/blog/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Modules\Blog\Controllers\PostController;
use Modules\Blog\Controllers\CommentController;

// Public routes
Route::prefix('blog')->name('blog.')->group(function () {
    Route::get('/', [PostController::class, 'index'])->name('index');
    Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
});

// Authenticated routes
Route::middleware('auth')->prefix('blog')->name('blog.')->group(function () {
    Route::post('/posts/{post}/comments', [CommentController::class, 'store'])
        ->name('comments.store');
});

Route Model Binding

Use route model binding with your module models:
use Illuminate\Support\Facades\Route;
use Modules\Blog\Models\Post;
use Modules\Blog\Controllers\PostController;

Route::get('/posts/{post}', [PostController::class, 'show']);
In your controller:
app-modules/blog/src/Controllers/PostController.php
<?php

namespace Modules\Blog\Controllers;

use App\Http\Controllers\Controller;
use Modules\Blog\Models\Post;

class PostController extends Controller
{
    public function show(Post $post)
    {
        return view('blog::posts.show', compact('post'));
    }
}

Custom Route Key

Customize the route key in your model:
app-modules/blog/src/Models/Post.php
<?php

namespace Modules\Blog\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function getRouteKeyName()
    {
        return 'slug';
    }
}
Now routes will bind using the slug:
// Matches /posts/my-first-post instead of /posts/1
Route::get('/posts/{post}', [PostController::class, 'show']);

Resource Routes

Use resource routing for CRUD operations:
use Modules\Blog\Controllers\PostController;

Route::resource('posts', PostController::class);
This creates all standard RESTful routes:
GET       /posts              index
GET       /posts/create       create
POST      /posts              store
GET       /posts/{post}       show
GET       /posts/{post}/edit  edit
PUT       /posts/{post}       update
DELETE    /posts/{post}       destroy

API Resources

For APIs, use apiResource to exclude create and edit routes:
Route::apiResource('posts', PostController::class);

Middleware

Apply middleware to module routes:

Route-Level Middleware

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

Module Middleware

Create middleware specific to your module:
php artisan make:middleware CheckBlogAccess --module=blog
Register it in your module’s service provider:
app-modules/blog/src/Providers/BlogServiceProvider.php
<?php

namespace Modules\Blog\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\Router;
use Modules\Blog\Middleware\CheckBlogAccess;

class BlogServiceProvider extends ServiceProvider
{
    public function boot(Router $router)
    {
        $router->aliasMiddleware('blog.access', CheckBlogAccess::class);
    }
}
Use it in routes:
Route::middleware('blog.access')->group(function () {
    // Protected routes
});

Route Caching

Module routes work with Laravel’s route caching, but make sure to clear and recache after adding new routes.
Cache routes for production:
php artisan route:cache
Clear route cache during development:
php artisan route:clear

Loading Order

Route files are loaded alphabetically by filename across all modules:
1. app-modules/blog/routes/api.php
2. app-modules/blog/routes/web.php
3. app-modules/shop/routes/api.php
4. app-modules/shop/routes/web.php
Prefix filenames with numbers to control loading order if needed:
  • 01-api.php
  • 02-web.php
  • 03-admin.php

Example: Complete Routing Setup

Here’s a complete example of a well-organized module routing setup:
app-modules/blog/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Modules\Blog\Controllers\PostController;
use Modules\Blog\Controllers\CommentController;
use Modules\Blog\Controllers\CategoryController;

// Public blog routes
Route::prefix('blog')->name('blog.')->group(function () {
    
    // Posts
    Route::get('/', [PostController::class, 'index'])->name('index');
    Route::get('/posts/{post:slug}', [PostController::class, 'show'])->name('posts.show');
    
    // Categories
    Route::get('/categories/{category:slug}', [CategoryController::class, 'show'])
        ->name('categories.show');
});

// Authenticated user routes
Route::middleware('auth')->prefix('blog')->name('blog.')->group(function () {
    
    // Comments
    Route::post('/posts/{post}/comments', [CommentController::class, 'store'])
        ->name('comments.store');
    
    Route::delete('/comments/{comment}', [CommentController::class, 'destroy'])
        ->name('comments.destroy');
});

// Admin routes
Route::middleware(['auth', 'admin'])
    ->prefix('admin/blog')
    ->name('admin.blog.')
    ->group(function () {
        Route::resource('posts', PostController::class);
        Route::resource('categories', CategoryController::class);
    });

Testing Routes

Test your module routes:
app-modules/blog/tests/PostRoutesTest.php
<?php

namespace Modules\Blog\Tests;

use Tests\TestCase;
use Modules\Blog\Models\Post;

class PostRoutesTest extends TestCase
{
    public function test_can_view_posts_index()
    {
        $response = $this->get(route('blog.index'));
        $response->assertStatus(200);
    }
    
    public function test_can_view_single_post()
    {
        $post = Post::factory()->create();
        
        $response = $this->get(route('blog.posts.show', $post));
        $response->assertStatus(200);
    }
}

Next Steps

Module Views

Learn how to create and use views in modules

Module Components

Create controllers and other components

Build docs developers (and LLMs) love