> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Package route registration and caching

> Based on the Laravel 13 implementation, this guide explains the role of loadRoutesFrom, keeping middleware and names separate, and how to apply configuration changes to the route cache.

When a package provides HTTP endpoints, it is not enough for the routes to work in a development environment. They must also behave under the same contract after the consuming application builds its route cache. If your design lets users change the URL prefix or enable and disable routes through configuration, you also need to tell users when those changes take effect.

This page builds on [Laravel Package Development](/en/advanced/package-development) and treats the registration process and the cache lifecycle as separate concerns. The official documentation references the Laravel 13 default branch, `13.x`, and framework references point to the latest release, `v13.34.0`.

## loadRoutesFrom only loads a file

`ServiceProvider::loadRoutesFrom()` does not load the route file when the application implements `CachesRoutes` and `routesAreCached()` returns true. Otherwise, it `require`s the specified file.

The method itself does not add URI or route name prefixes, a controller namespace, or middleware. It also does not publish files or add routes to an existing cache.

```mermaid theme={null}
flowchart TD
    A["Provider boot"] --> B["Call loadRoutesFrom"]
    B --> C{"Route cache exists?"}
    C -->|No| D["require the package route file"]
    C -->|Yes| E["Skip loading the package file"]
    E --> F["Laravel's RouteServiceProvider<br>loads the application cache"]
```

The diagram assumes a standard Laravel application. There is no package-specific cache. Instead, the package routes are included in the application-wide route cache.

<Warning>
  Naming the file inside your package `routes/web.php` does not by itself apply the `web` middleware. The file is loaded through a different path than the application's standard route files, so declare the middleware you need explicitly in the package.
</Warning>

## Keep configuration and registration separate

The following example creates a public endpoint that reports whether the package is available. It assumes that Composer's PSR-4 configuration maps `Acme\Courier\` to `src/` and that the provider is registered through [auto-discovery](/en/advanced/package-discovery) or manually.

```php config/courier.php theme={null}
<?php

return [
    'routes' => [
        'enabled' => true,
        'prefix' => 'acme-courier',
    ],
];
```

Merge configuration in `register()` and load routes in `boot()`. Do not make a provider that registers HTTP routes a `DeferrableProvider`, because there is no longer any guarantee that the provider boots by the time the routes are needed.

```php src/CourierServiceProvider.php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../config/courier.php', 'courier');
    }

    public function boot(): void
    {
        if (config('courier.routes.enabled')) {
            $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        }
    }
}
```

This condition controls only route registration. In a provider that also registers other services or views, keep those registrations outside this condition. For how to expose configuration to users and caveats about merging nested configuration, see [Package config merging and caching](/en/advanced/package-config-merging).

```php routes/web.php theme={null}
<?php

use Acme\Courier\Http\Controllers\StatusController;
use Illuminate\Support\Facades\Route;

Route::middleware('web')
    ->prefix(config('courier.routes.prefix'))
    ->name('acme-courier.')
    ->group(function () {
        Route::get('/status', StatusController::class)->name('status');
    });
```

```php src/Http/Controllers/StatusController.php theme={null}
<?php

namespace Acme\Courier\Http\Controllers;

use Illuminate\Http\JsonResponse;

class StatusController
{
    public function __invoke(): JsonResponse
    {
        return response()->json(['available' => true]);
    }
}
```

The default URI is `/acme-courier/status`, and the route name is `acme-courier.status`. If you generate URLs with `route('acme-courier.status')`, callers can keep using the same route name even when the URI prefix changes. The group's `name()` concatenates strings as is, so include the trailing `.` as well.

<Info>
  `web` is not a substitute for authentication or authorization. This example is a public endpoint that contains no sensitive information. For endpoints that return user data, add authentication middleware and authorization logic that match your requirements.
</Info>

## Prevent URI and route name collisions separately

URI prefixes and route name prefixes are separate mechanisms. Adding one does not prevent collisions in the other.

| Target | Design in this example | Maintenance notes |
| - | - | - |
| URI | Defaults to `acme-courier` and can be changed in configuration | Choose a value that does not conflict with existing URLs in the consuming application |
| Route name | Fixed as `acme-courier.` | Use a package-specific name and maintain it as a contract for URL generation |
| Controller | Uses a class reference | Do not depend on the application's controller namespace |
| Middleware | Declares `web` explicitly | Verify it against the target application's middleware configuration |

When `AbstractRouteCollection` builds a route collection for caching, it throws a `LogicException` if another route already has the same name. Being able to generate URLs during a normal boot does not guarantee that the routes can be cached. Two routes with different URIs still cause a problem if they share a name.

Do not rely on overriding the consuming application's routes through registration order as a way to extend your package. If needed, provide a setting that disables the routes and a service that users can call from their own routes.

## Configuration at cache time is baked into route definitions

`RouteCacheCommand` first runs `route:clear`, then boots a fresh application and collects its routes. It prepares those routes in a serializable form and writes the compiled result to the cache file.

The package route file is also loaded at this point, so the prefix and whether the routes are registered are determined by the **configuration at the time the cache is built**. On subsequent boots, `loadRoutesFrom()` does not load the file, and the cached routes are used.

| Change | If a stale route cache remains | Required action |
| - | - | - |
| Add a route to the package | The added route does not appear | Rebuild the route cache |
| Change `routes.prefix` | The original URI remains | Rebuild with the new configuration |
| Change `routes.enabled` to `false` | Routes in the cache are not removed | Rebuild with the disabled configuration |
| Remove the package | Definitions that reference removed classes may remain | Rebuild with the post-removal setup |

<Warning>
  `routes.enabled` controls registration. It does not deny access on a per-request basis. Disabling the setting while a stale cache remains does not stop the endpoint.
</Warning>

Do not register routes based on conditions that change per request, such as the user or tenant. Those conditions are evaluated in the CLI environment when the cache is built. Register routes with a stable configuration, and decide whether access is allowed through middleware or authorization in the controller.

### Finalize configuration first during deployment

After updating code and configuration, rebuild in the following order if your setup uses the configuration cache. Include these steps in the consuming application's deployment process.

```bash theme={null}
php artisan config:cache
php artisan route:cache
php artisan route:list --name=acme-courier -vv
```

If you run `route:cache` while a stale configuration cache remains, the routes are also built with the old configuration. Rerunning only `config:cache` does not update the route cache. With `-vv`, you can also inspect the contents of middleware groups.

To verify behavior without caches during development, clear both as needed.

```bash theme={null}
php artisan config:clear
php artisan route:clear
```

Route files are not executed on boots where a cache exists. Registering event listeners or container bindings there changes behavior depending on the cache, so keep route files free of side effects other than route definitions. In environments that use long-running processes, include reloading after cache updates in your normal deployment procedure.

## Combinations to verify before release

In addition to the package tests, verify the following combinations in a Laravel 13 consuming application. Cover not only in-memory route registration but also the path where Artisan boots a fresh application.

* Without a cache, `/acme-courier/status` responds, and the route name and middleware are as expected.
* `route:cache` succeeds, and a fresh boot responds with the same URI and route name.
* After changing the prefix and rebuilding the cache, the new URI responds and the package route at the old URI is gone.
* After disabling the routes and rebuilding the cache, the target route does not appear in `route:list --name=acme-courier`.
* URIs and route names do not collide with the consuming application or other packages.

If you also test cases where a stale cache remains, you can reproduce user reports such as "I changed the configuration file, but the URL did not change." Document cache rebuilding in your upgrade instructions, and treat changes to route names and middleware as part of your compatibility review.

## Related pages

<Columns cols={2}>
  <Card title="Routing" icon="route" href="/en/routing">
    Review the basics of route groups, named routes, and listing routes.
  </Card>

  <Card title="Package config merging and caching" icon="sliders" href="/en/advanced/package-config-merging">
    Review update procedures that account for published configuration and the configuration cache.
  </Card>

  <Card title="Deferred Service Providers" icon="clock" href="/en/advanced/deferred-provider">
    Learn why providers that register routes should not be deferred.
  </Card>

  <Card title="Package Version Compatibility Management" icon="code-branch" href="/en/advanced/package-versioning">
    Connect public API changes to your release policy and continuous verification.
  </Card>
</Columns>

## Primary sources

* [Laravel official documentation: Package routes](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/packages.md#routes)
* [Laravel official documentation: Routing](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/routing.md)
* [ServiceProvider: Implementation of loadRoutesFrom](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [RouteServiceProvider: Loading the cache](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php)
* [RouteCacheCommand: Collecting and saving routes in a fresh application](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/RouteCacheCommand.php)
* [AbstractRouteCollection: Detecting duplicate route names](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Routing/AbstractRouteCollection.php)


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Advanced Topics](/en/advanced/index.md)
- [Develop Laravel packages with Testbench Workbench](/en/advanced/package-workbench.md)
- [Deferred Service Providers](/en/advanced/deferred-provider.md)
- [Package config merging and caching](/en/advanced/package-config-merging.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.