Skip to main content
When you add HTTP features to a package, keep making middleware available separate from applying it to the consuming application’s requests. If installing the package alone does not change the behavior of every route, it is easier to manage the impact of adoption and future compatibility. This page assumes you are familiar with package development basics and refers to the official Laravel 13.x documentation and the Framework v13.36.0 implementation. For how to write regular middleware, see Middleware.

Separate registration, application, and execution

Router::aliasMiddleware() registers an entry in the Router’s array, with the alias as the key and the class name as the value. It does not instantiate the middleware or apply it to any route. Alias names are part of your public API. Avoid built-in names such as auth and throttle, as well as generic names likely to collide with other packages, and add a package-specific prefix. If you register the same key again, aliasMiddleware() overwrites the previous mapping.

Expose the alias from the provider

The following example creates middleware that adds a fixed identification header to the response. It is not an example of authentication, authorization, or access control. It assumes that Composer’s PSR-4 maps Acme\Courier\ to src/ and that the provider is registered through auto-discovery or manually.
src/Http/Middleware/AddCourierHeader.php
Use the Router in boot(). Because boot() arguments are injected from the service container, you do not need to automatically edit the consumer’s bootstrap/app.php to register middleware.
src/CourierServiceProvider.php
Boot this provider as a regular provider. If you move alias registration into a deferred provider that runs only when another service is resolved, there is no guarantee that the alias will be registered by the time a route needs it.

Consumers apply it per route

In the consuming application’s routes/web.php, assign it only to the routes that need it.
routes/web.php
You can also skip providing an alias and specify the class name directly, as in ->middleware(AddCourierHeader::class). Choose based on whether you need to offer consumers a short name. When the package itself provides routes, also declare the required middleware explicitly in the route file. loadRoutesFrom() does not automatically add web or api. For registration methods and how route caching is handled, see Package route registration and caching.

Let consumers choose application-wide application

If consumers want to apply the middleware to every web route in their application, they add the following configuration to their existing withMiddleware() callback. This example does not replace the existing configuration.
Because this configuration uses the class name directly, it does not depend on the order in which aliases are registered. The scope is the web group, not the global stack. It is not automatically applied to api routes or to routes that have neither group.
A design that applies middleware to all of web at package installation time is not compatible with a design where consumers apply it explicitly. Do not extend processing that affects authentication, sessions, response headers, external communication, and the like to every route through an automatic update.

Router group operations behave differently

If your package provides its own group, use a unique name. The following example goes inside the provider’s boot(Router $router).
Consumers can apply it with ->middleware('acme-courier'). However, this group contains only the classes you specify; it does not inherit the session or CSRF protection from web. There are also APIs for adding to existing groups, but the v13.36.0 Router implementation has the following differences. If you set only your package’s class with middlewareGroup('web', [...]), you replace the existing contents of web. Do not confuse it with the methods for adding. Also, calling pushMiddlewareToGroup() on a nonexistent web group does not restore the standard web group. The duplicate check in the adding methods compares the registered values. An alias and a class name that point to the same class are different strings, so they are separate items at the registration stage. Use consistent notation, and do not assume that the same class written in different forms will be deduplicated at registration time.

Registration order alone does not guarantee execution order

The Router expands the aliases and groups assigned to a route, processes exclusions, and then sorts by priority. Prepending or appending alone does not guarantee a strict ordering relative to authentication or route model binding. In addition, the HTTP Kernel’s syncMiddlewareToRouter() synchronizes the Kernel’s groups to the Router using middlewareGroup(). If the Kernel’s configuration is resynchronized later, the contents of groups you added to the Router directly may be replaced. Rather than competing to modify existing groups based on the boot order of packages, the basic approach is to consolidate configuration in the consuming application’s withMiddleware(). For features that require a particular order, such as after the session starts or after authentication, document that requirement in the installation instructions and verify it together with the consuming application’s priority settings. Avoid having a package replace the application’s entire priority list.

The boundary between caching and verification

Do not place alias registration inside a routesAreCached() condition or in a route file. loadRoutesFrom() does not load the route file when a cache exists, but registration is still needed to resolve aliases that remain in cached routes at runtime. Register them every time in the provider’s boot(). If you change route assignments, rebuild the route cache in the consuming application.
Use -vv to expand and check group contents. However, a successful listing does not guarantee the processing inside the middleware or the use of sessions. You also need HTTP request tests. The following method is added to a Feature test in a Laravel application that defines the /courier-status route above.
Unit tests that check whether a group exists or inspect the alias array are a separate kind of verification from actual HTTP execution. In particular, verify caching not just by inspecting the Router in the same process, but with a fresh boot after the cache is created. If you use long-running workers, reload them according to your normal deployment procedure.

Middleware

Review the basics of creating middleware, applying it to routes, parameters, and priority.

Package route registration and caching

Review how route loading relates to configuration when the cache is created.

Primary sources consulted

Last modified on October 15, 2026