Skip to main content
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 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 requires 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. 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.
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.

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 or manually.
config/courier.php
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.
src/CourierServiceProvider.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.
routes/web.php
src/Http/Controllers/StatusController.php
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.
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.

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

Routing

Review the basics of route groups, named routes, and listing routes.

Package config merging and caching

Review update procedures that account for published configuration and the configuration cache.

Deferred Service Providers

Learn why providers that register routes should not be deferred.

Package Version Compatibility Management

Connect public API changes to your release policy and continuous verification.

Primary sources

Last modified on October 5, 2026