Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 RouteServiceProvider Replacement: Custom Route Files in bootstrap/app.php

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 RouteServiceProvider Replacement: Custom Route Files in bootstrap/app.php

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
Laravel 13 RouteServiceProvider Replacement: Custom Route Files in bootstrap/app.php

Laravel 13 applications do not ship with a RouteServiceProvider. Route files are registered in bootstrap/app.php through withRouting(): use its then closure to add extra route files such as routes/admin.php, or the using closure to take full control. Rate limiters and custom route model bindings, which used to live in the provider, now go in the boot() method of AppServiceProvider.

The provider was removed from the application skeleton in Laravel 11 as part of the slimmer structure, and Laravel 13 keeps that layout. If you are upgrading an older project, your existing provider still works; this guide is for new projects or for teams moving to the new style.

Prerequisites

  • Laravel 13.x on PHP 8.3 or newer.
  • Basic familiarity with route groups, middleware and named routes.

Where are routes registered in Laravel 13?

Open bootstrap/app.php. A fresh project contains something like this:

PHP
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

web loads routes/web.php with the web middleware group, commands loads console routes, and health registers a simple health-check endpoint at /up for load balancers and uptime monitors.

Step 1: Add API routes

API routes are not installed by default. Add them with one command:

Code
php artisan install:api

It creates routes/api.php, installs Laravel Sanctum, and adds api: __DIR__.'/../routes/api.php' to withRouting(). Those routes get the api middleware group and the /api prefix. To change the prefix, add apiPrefix: 'api/v1' next to the api argument.

Step 2: Register a custom route file

Large apps often split routes by area. Create routes/admin.php and register it inside the then closure, which runs after the default files are loaded:

CSS
use Illuminate\Support\Facades\Route;

->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    then: function () {
        Route::middleware(['web', 'auth'])
            ->prefix('admin')
            ->name('admin.')
            ->group(base_path('routes/admin.php'));

        Route::middleware('api')
            ->prefix('webhooks')
            ->name('webhooks.')
            ->group(base_path('routes/webhooks.php'));
    },
)

Inside routes/admin.php you write plain routes. The prefix, name prefix and middleware are applied automatically:

PHP
<?php

use App\Http\Controllers\Admin\UserController;
use Illuminate\Support\Facades\Route;

// URL: /admin/users, name: admin.users.index
Route::get('/users', [UserController::class, 'index'])->name('users.index');

Webhook routes use the api group on purpose: they are stateless and should not need a CSRF token.

Step 3: Take full control with the using closure

If you prefer to register every route file yourself, pass using instead of web and api. Laravel then registers no HTTP routes on its own:

CSS
->withRouting(
    commands: __DIR__.'/../routes/console.php',
    using: function () {
        Route::middleware('api')
            ->prefix('api')
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->group(base_path('routes/web.php'));
    },
)

I only reach for this when route registration depends on runtime conditions, for example a multi-tenant app that loads different files per domain.

Where do rate limiters go now?

Define them in App\Providers\AppServiceProvider::boot():

JavaScript
<?php

namespace App\Providers;

use App\Models\Post;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        // Custom route model binding: resolve {post} by slug.
        Route::bind('post', fn (string $value) => Post::where('slug', $value)->firstOrFail());

        // Global constraint: every {id} parameter must be numeric.
        Route::pattern('id', '[0-9]+');
    }
}

Attach a limiter with ->middleware('throttle:api') on a route or group. For most slug lookups you do not even need Route::bind: Route::get('/posts/{post:slug}', ...) tells implicit binding which column to use.

How do I organize routes in a large Laravel 13 app?

A layout that scales well is one file per audience: web.php for public and customer pages, admin.php for the back office, api.php for the public API and webhooks.php for third-party callbacks. Each file gets its own middleware, prefix and name prefix in bootstrap/app.php, so a developer opening admin.php knows every route there is already authenticated. Inside a file, group related routes with Route::controller() or Route::resource() rather than repeating the controller class on every line. For versioned APIs, a routes/api/v1.php and routes/api/v2.php pair registered with different prefixes keeps old clients working while new endpoints evolve.

Common mistakes

  • Creating a RouteServiceProvider out of habit. It is not needed. If you do add one, register it in bootstrap/providers.php, or it never runs.
  • Missing the web group on custom files. Without it, sessions, CSRF protection and error bags do not work for browser routes.
  • Using then and using together. using replaces default registration, so mixing the two leads to confusion. Pick one style.
  • Stale route cache. After changing route files in production, run php artisan route:cache again as part of deployment.
  • Closures in routes with route caching. Modern Laravel can cache closure routes, but controller routes are easier to test and reuse.

How to verify it works

List the routes and filter by path or name:

Code
php artisan route:list --path=admin
php artisan route:list --name=webhooks

Check the middleware column to confirm the groups you expect. Add a quick feature test such as $this->get('/admin/users')->assertRedirect('/login') for a guest to prove the auth middleware is active, and request /up to see the health endpoint return 200.

FAQ

Can I still use a RouteServiceProvider after upgrading?

Yes. Upgraded apps can keep their provider. The new structure is recommended for new code, not forced on old projects.

Where do I set the HOME redirect constant?

There is no HOME constant any more. Redirect to a named route directly, and configure where authenticated users are sent from guest pages with $middleware->redirectUsersTo().

How do I add a subdomain route group?

Use Route::domain('{account}.example.com')->group(...) in a route file, or inside the then closure.

References

Article Index & Topic Tags
LaravelRoutingConfigurationRate-Limiting

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.