Skip to content

[12.x] Route Name Aliases#59078

Closed
GianfriAur wants to merge 8 commits intolaravel:12.xfrom
GianfriAur:12.x
Closed

[12.x] Route Name Aliases#59078
GianfriAur wants to merge 8 commits intolaravel:12.xfrom
GianfriAur:12.x

Conversation

@GianfriAur
Copy link

@GianfriAur GianfriAur commented Mar 4, 2026

Summary

This PR introduces a first-class concept of route name aliases — the ability to assign one or more alternative names to an already-named route, without duplicating the route registration itself.

Route::get('/', HomeController::class)->name('home');

Route::alias('home', ['app', 'saas.team-selector']);

route('app') and route('saas.team-selector') will now resolve to the exact same URL as route('home'), with no duplicate entries in the route collection.


Motivation

The need for this feature emerges naturally in modular Laravel applications, where multiple independent packages contribute routes and expect to navigate between each other using well-known route names.

A concrete example: consider an application composed of two packages:

  • A core package that defines the application entry point as GET / with the name home.
  • A SaaS/team package that needs to navigate to a route named saas.team-selector.
  • The host application that registers its own entry point under the name app.

All three names refer to the same route. Today, the only workarounds are:

  1. Registering duplicate routes — same URI and action, different names. This pollutes the route collection and route:list output.
  2. Using config values to share route names across packages — which introduces coupling, requires publish steps, and arguably adds more overhead than a simple alias map.

Approach

This implementation takes a deliberate design decision: aliases live on the name, not on the route.

Rather than creating new Route instances, aliases are stored as a simple map of alias → original name directly inside the Router. The UrlGenerator resolves aliases transparently at call time, so route('alias') behaves identically to route('original').

This keeps the route collection clean and avoids any runtime overhead from duplicate route matching.

Key design choices

  • Router::addAlias(string $aliasName, string $originalName) — stores the alias map inside the Router itself, so it is serialized as part of bootstrap/cache/routes-v7.php with no additional cache file needed.
  • UrlGenerator::route() — resolves aliases transparently before delegating to the original implementation.
  • route:list --with-aliases (or -a) — a new flag has been added to the route:list command. By default aliases are hidden to keep the output clean; passing -a reveals them inline next to the original route name:
    GET|HEAD  /   home [app, saas.team-selector]  › HomeController
    
  • Route::alias() — provides a clean, expressive API at the route definition level.
  • Alias name collision protection — rather than adding explicit checks or throwing exceptions at registration time, the protection is handled at the root level inside UrlGenerator::route(). The resolution order is strict: real route names always take priority over aliases. If a route named app exists in the route collection, it will be resolved directly and the alias map is never consulted. The alias lookup only kicks in when no real route matches the given name. This means a real route can never be silently shadowed by an alias, with no extra validation logic needed anywhere else.

Open questions

  • Should route:list -a show aliases as a separate column, or inline as in the current implementation?
  • Should ->alias('other-name') be supported as a chainable alternative to Route::alias()?
  • Should there be a way to explicitly allow overriding an existing name (opt-in), or should the collision check always be strict?
  • Would it also be useful to add a dedicated command to list all registered aliases, something like route:alias?

@GianfriAur GianfriAur changed the title Route Name Aliases [12.x] Route Name Aliases Mar 4, 2026
@browner12
Copy link
Contributor

Maybe I'm misunderstanding your use case, but IMO it is the responsibility of the package to adapt to your applications needs, not the other way around. If the package needs to know where your home route lives, they should make you explicitly tell them where it is with a config value, not hardcode a "saas.team-selector" route name into their code that you must adapt to.

@GianfriAur
Copy link
Author

Fair point, the home page is a simple example — let me try to paint a clearer picture.

Think of an application made of 10 packages, each adding its own routes. These packages need to reference each other's routes for navigation, redirects, and so on. Today, the only clean way to handle this is through shared config: every package exposes a config file, the consumer publishes it, wires up the route names, and every package reads from it at boot.

It works — but it's a lot of moving parts just to say "this name points here".

Aliases simplify exactly that. One line, no config files, no cross-package wiring. And since aliases are cached alongside the route collection, there's no extra cost at runtime either.

The goal is just to make things simpler — not to replace existing patterns, just to remove unnecessary friction when the use case is straightforward.

@browner12
Copy link
Contributor

Are these 10 packages all 3rd party, or do you control all/some of them? If they're all 3rd party, it seems odd/uncommon for them to need to know about each other and the associated routes, as this would create a bit of a rat's nest of coupling.

I also don't completely follow how this is cleaner/simpler than the config solution, but I'll also admit this is never a problem I've run into.

@GianfriAur
Copy link
Author

GianfriAur commented Mar 5, 2026

To clarify: these aren't third-party packages — I own and maintain all of them,colleagues too. So the problem isn't compatibility, it's the configuration overhead that quietly accumulates as a project grows. The bigger the project, the easier it is to lose track of which config controls what, and with many packages it quickly turns into an endless web of settings spread across many different files.

The way I think about aliases is as semantic connections — a way for a package to declare the purpose of a route rather than just its name. Take Filament as an example: if its packages could declare that a given page serves as, say, the "dashboard entry point", other packages could connect to that concept without needing to know the exact route name or wiring up config manually.

A practical everyday example: Laravel itself expects a route named login to exist. If your auth package registers it under auth.login, today you have no clean way to bridge that gap. With aliases, it would just be:

Route::alias('auth.login', ['login']);

No extra config, no workarounds, no RouteNotFoundException.

It's also worth pointing out that this change doesn't alter how Laravel works today in any way. It purely adds optional functionality — if you never call Route::alias(), nothing changes. It's just a convenience for those who find it useful.

That said, I fully understand your point and I know this reflects my own vision of how things should work.

If the feature itself feels too opinionated for the framework core, maybe a lighter alternative would be to make the UrlGenerator easier to extend or override. That way, anyone who needs this kind of custom resolution logic could implement it cleanly, without having to fight the framework to get there. Would that direction feel more appropriate?

@donnysim
Copy link
Contributor

donnysim commented Mar 5, 2026

I'm having a hard time understanding this and I've worked with modules etc.. So what is the problem exactly of just referencing the original route name in those said packages if it's all your controlled code? Why is the route not named "saas.team-selector" to begin with if it's used across multiple different packages? surely it has only one meaning what the page represents or does.

If you're using those packages across multiple different projects then it definitely should be a configuration file, as finding all routes you need to alias is way harder than looking at a config file.

@GianfriAur
Copy link
Author

Hi @donnysim,

The key point is that these packages are not designed for a single application — they are reusable building blocks that can be composed together to build different applications. Each application may define its own route structure, its own naming conventions, and its own entry points. A package like saas-team doesn't know upfront what the host application will call its home route. It shouldn't have to.

This is exactly why a centralized routes/aliases.php file would make sense. Just like routes/web.php or routes/api.php give you a single place to define your routes, routes/aliases.php would give you a single, obvious place to define all the semantic connections between route names in your application:

// routes/aliases.php

Route::alias('auth.login',             ['login']);
Route::alias('auth.logout',            ['logout']);
Route::alias('auth.register',          ['register']);
Route::alias('auth.password.request',  ['password.request']);
// ...

Everything in one place, easy to find, easy to understand.

As for the packages that would benefit immediately from this today — without any custom setup — here is a concrete list:

  • Fortify — registers login, logout, register, password.request, password.reset, password.email, password.confirm, verification.notice, verification.verify, verification.send
  • Breeze — same set of auth route names, different implementation
  • Jetstream — same, plus team-specific routes
  • Sanctum — relies on login for SPA authentication redirects
  • Laravel's own RedirectIfAuthenticated middleware — hardcodes a redirect to home
  • Any package that uses redirect()->route('login') — which is virtually every auth-aware package

All of these today require you to either match their expected route names exactly, or work around them. Aliases would let each package declare what it needs and let the application wire things up in one place, cleanly.

@GianfriAur
Copy link
Author

Beyond the modular package scenario, there are several other situations where route name aliases would be immediately useful:

1. Renaming routes without breaking existing code
When a route needs to be renamed as a project evolves, today you have to hunt down every route() and redirect()->route() reference across the entire codebase. With aliases, you can introduce the new name and keep the old one as an alias during a transition period:

// old name kept alive while you migrate references gradually
Route::alias('dashboard.index', ['home']);

2. Localization / multi-tenant applications
In applications that serve different audiences under different branding, the same route may need to be reachable under different names depending on context. Aliases make it trivial to map multiple logical names to a single implementation without duplicating routes.

3. API versioning bridges
When moving from api.v1.users.index to api.v2.users.index, an alias lets you keep the old name pointing to the new route during a deprecation window, with no duplicate controllers or redirects.

4. Cleaner package interoperability
A package can document a "contract" — "I expect a route named auth.login to exist" — and let the host application fulfill that contract however it wants, via an alias. This is a much lighter coupling mechanism than shared config or service binding.

5. Backwards compatibility in open source packages
When a package author needs to rename a route in a major version, they can ship the alias in a compatibility layer so existing user code doesn't break immediately.


I'm aware these are fairly specific scenarios and this may not be something everyone needs. But that's exactly the point — it's entirely optional. If you never call Route::alias(), nothing changes, nothing breaks, and there is zero impact on your application. It's simply a tool that's there when you need it, and completely invisible when you don't.

Beyond the practical side though, I think introducing a semantic layer to route naming could be genuinely valuable for the Laravel ecosystem as a whole. Being able to say "this route serves as the application entry point" or "this is where authentication happens" — independently of what it's actually called — makes code more expressive, packages more interoperable, and applications easier to reason about. That kind of shared vocabulary, even if optional, tends to have a positive ripple effect across the ecosystem over time.

@donnysim
Copy link
Contributor

donnysim commented Mar 5, 2026

  1. Renaming routes without breaking existing code

If you have a single name, it ain't that hard to change, whereas if you have multiple aliases it will become even harder.

  1. Localization / multi-tenant applications

Translating route names is the worst idea ever. You cache them once, you don't translate them every time nor reference random route string that come from who knows where.

  1. API versioning bridges

Duplicating a route and pointing to the same controller ain't that hard and keeps all v2 routes in one place instead of scattered across multiple versions. Also, if you're providing the same v1 for v2 you're risking oof breaking the v2 or v1 by changing the same code.

  1. Cleaner package interoperability

If a package allows you to choose, that means it should also include a config option and if it has a config why bother with alias where configs usually contain way more than just routes which most of the time - you'll want them too.

  1. Backwards compatibility in open source packages

Configs already solve this because it's not tied to authors route name but yours.

Local code should never need aliases, it just creates a separation issue where a single route could be reference by different name and it's just harder to find. Packages should not enforce implicit logic, they should be explicit about their expectations and needs. What you're getting with these aliases is implicit logic for packages where you have no idea why they're there whereas a config file is explicit. All your named packages have one or another way to change the default expectations via configuration as for aliases feel like cutting corners to enforce package needs onto the app instead of inverse.

@GianfriAur
Copy link
Author

These are all fair and well-reasoned points, and I appreciate the detailed feedback.

You're right that configs are explicit, and explicitness is generally a virtue. I won't argue against that. But I'd push back slightly on the framing that aliases are implicit in a negative sense — a routes/aliases.php file is just as explicit and discoverable as any config file. It's a single place that says: "in this application, these names are equivalent". That's not hidden logic, it's declared intent.

On the individual points:

  • Renaming routes — fair, if it's one name it's easy. The harder case is when many packages reference the same route by different names and you need to reconcile them. That's where aliases help, not replace a rename.
  • Localization — you're right, I should not have framed it that way. That point was poorly argued and I'd remove it.
  • API versioning — agreed that duplicating a route is clean and explicit. This was the weakest use case I listed.
  • Package interoperability and backwards compatibility — this is where I still think there's a genuine gap. Yes, packages like Fortify and Jetstream have config options. But RedirectIfAuthenticated hardcodes home. Laravel's own Auth::routes() hardcodes login. These aren't third-party choices — they're framework defaults that every Laravel developer has had to work around at some point.

The core of my argument isn't that aliases replace configs — and I want to be clear on this. Both approaches are perfectly valid and they don't get in each other's way at all. A project can use configs for complex package configuration and aliases for the specific, lightweight case of saying "this name means this route". One doesn't invalidate the other.

What I find interesting about aliases, beyond the practical side, is the possibility of introducing a semantic layer to route naming. Being able to express that a route serves a purpose — independently of what it's actually called — could help create a kind of shared vocabulary across packages and applications. I think that kind of harmony, even if entirely optional, could be a quiet but positive contribution to the ecosystem over time.

That said, I have the utmost respect for your perspective and for the care you're putting into this review. If the consensus is that configs are always the right tool here, I genuinely accept that. I just think this is a conversation worth having.

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants