Jonathan Bird Web Development

How to Fix "Route [login] Not Defined" Error in Laravel 13 (2026 Guide)

If you've ever hit a "Route [login] not defined" error in your Laravel application, you're in very good company. This is one of the most common errors that catches Laravel developers off guard, and it usually shows up in one of two ways: an app with no auth scaffolding installed where a route accidentally gets the auth middleware applied to it, or a custom auth setup with a login route named something other than login.

In this guide, I'll walk you through what causes the "Route [login] not defined" error, how to diagnose it, and the various ways to fix it in Laravel 13. Whether you're building an API, using a starter kit, or rolling your own auth, I've got you covered in this article.

What is the "Route [login] Not Defined" Error?

This error is thrown by Laravel's URL generator when something tries to look up a route named login and can't find one. The exception class is Symfony\Component\Routing\Exception\RouteNotFoundException and the message looks like this:

1Symfony\Component\Routing\Exception\RouteNotFoundException: Route [login] not defined.

The reason it shows up so often is that Laravel's exception handler calls route('login') as the default fallback whenever an AuthenticationException is thrown. Here's the actual line from Illuminate\Foundation\Exceptions\Handler:

1return $this->shouldReturnJson($request, $exception)
2 ? response()->json(['message' => $exception->getMessage()], 401)
3 : redirect()->guest($exception->redirectTo($request) ?? route('login'));

If the request expects JSON (an API call, an XHR request with the right headers), Laravel returns a 401 with a JSON message. For anything else, it tries to redirect the user to a route named login. If that route doesn't exist, the URL generator blows up and you see the "Route [login] not defined" error instead of the auth redirect you expected.

So the underlying problem is almost always: the user wasn't authenticated, the auth middleware fired, and there's no route named login for Laravel to redirect them to.

Common Causes of the Route [login] Not Defined Error

Before jumping into fixes, it helps to understand where this error tends to come from.

No Auth Scaffolding Installed

This is the biggest one. You spin up a fresh Laravel application, add the auth middleware to a route, and the moment a guest hits that route the redirect fires. A fresh Laravel app does not ship with a login route by default. You need to install one of the Laravel 13 starter kits (or roll your own auth routes) before the auth middleware will have somewhere to redirect to.

Login Route Has a Different Name

If you've rolled your own auth, you might have a login form route but called it something else. For example:

1// This won't satisfy route('login')
2Route::get('/sign-in', [AuthController::class, 'showLoginForm'])->name('signin');
3Route::get('/auth/login', [AuthController::class, 'showLoginForm'])->name('auth.login');

The auth middleware doesn't care what the URL is, it cares about the route name. If your named route is signin or auth.login, then route('login') will fail.

Login Route Inside a Name-Prefixed Group

This is a sneaky one. You define what looks like a login route, but it's inside a group with a name prefix:

1Route::name('auth.')->group(function () {
2 Route::get('/login', [AuthController::class, 'show'])->name('login');
3 // The actual route name is "auth.login", not "login"
4});

Route name prefixes are appended, so the route name becomes auth.login. route('login') will still fail.

API-Only Application

If you're building an API and a route ends up with the web auth middleware (or the wrong guard), Laravel will try to redirect on auth failure instead of returning a 401. Without a login route, you'll see this error in your logs or API responses instead of a clean 401.

Removed or Renamed Login Route

You started with Breeze or Fortify, decided to customise things, and somewhere along the way you renamed or deleted the login route. Now everything that used to redirect to login is broken. This often happens during refactors when someone changes route names without searching for references.

Stale Route Cache

If you've run php artisan route:cache in production and then deployed changes that affect the login route, the cached file might be out of sync with your route definitions. The cached version is what Laravel uses to resolve routes, so a stale cache can hide a route that's technically defined in your code.

Calling route('login') Directly in Views or Code

Sometimes the error doesn't come from auth at all. Some package, view, or piece of code is calling route('login') directly and the route just doesn't exist. The stack trace will tell you exactly where the call originated.

How to Fix the Route [login] Not Defined Error

Let's work through the solutions from most common to more advanced scenarios.

Fix 1: Add a Named Login Route

The simplest fix is to actually have a route named login. If you've got a login form somewhere, just make sure its name is login:

1// routes/web.php
2 
3use App\Http\Controllers\Auth\LoginController;
4 
5Route::get('/login', [LoginController::class, 'show'])->name('login');
6Route::post('/login', [LoginController::class, 'store']);
7Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');

The URL can be whatever you want. What matters is that the route name is exactly login. Even if you don't have a real login form yet, you can register a placeholder so the auth middleware has somewhere to redirect to:

1Route::get('/login', function () {
2 return 'Login page coming soon';
3})->name('login');

Fix 2: Install a Starter Kit

If you haven't yet got auth set up at all, the fastest path is a starter kit. Laravel 13's official starter kits (React, Svelte, Vue, and Livewire) scaffold the login route, controller, views, and tests for you, and use Laravel Fortify behind the scenes.

For a brand new application, the laravel new installer will prompt you to pick a starter kit:

1laravel new my-app

Pick React, Svelte, Vue, or Livewire when prompted and the installer wires up the rest. After running php artisan migrate and starting the dev server, you'll have a working /login route named login and the error will disappear.

The Laravel 13 starter kits also offer a WorkOS AuthKit variant for each flavour if you want social authentication (Google, Microsoft, GitHub, Apple) without writing any of the OAuth plumbing yourself.

If you're on an existing application that can't easily adopt one of the starter kits, Laravel Breeze (from the Laravel 12 era) still works and publishes its routes directly into your app:

1composer require laravel/breeze --dev
2php artisan breeze:install
3php artisan migrate
4npm install && npm run build

Breeze isn't part of the official Laravel 13 starter kit lineup any more, but it's a sensible option for adding auth to an existing project without restructuring it.

Fix 3: Customise the Authenticate Redirect (Laravel 11, 12, and 13)

If you have a login route but it's named something other than login, you can tell the auth middleware where to redirect guests instead. In Laravel 11 and above (which includes Laravel 12 and 13), this is done in bootstrap/app.php:

1// bootstrap/app.php
2 
3use Illuminate\Foundation\Application;
4use Illuminate\Foundation\Configuration\Middleware;
5use Illuminate\Http\Request;
6 
7return Application::configure(basePath: dirname(__DIR__))
8 ->withRouting(
9 web: __DIR__.'/../routes/web.php',
10 commands: __DIR__.'/../routes/console.php',
11 )
12 ->withMiddleware(function (Middleware $middleware): void {
13 // Using a path string
14 $middleware->redirectGuestsTo('/sign-in');
15 
16 // Or using a closure (useful if your route name isn't "login")
17 $middleware->redirectGuestsTo(fn (Request $request) => route('signin'));
18 })
19 ->create();

The closure form is the one I reach for most often, because it lets you keep using named routes for everything.

Fix 4: Use Authenticate::redirectUsing for Legacy Apps

If you're on Laravel 10 or earlier (or your Laravel 11+ app still has an app/Http/Middleware/Authenticate.php file from an upgrade), you can either edit the middleware directly or call redirectUsing from a service provider.

Editing the middleware directly:

1// app/Http/Middleware/Authenticate.php
2 
3namespace App\Http\Middleware;
4 
5use Illuminate\Auth\Middleware\Authenticate as Middleware;
6use Illuminate\Http\Request;
7 
8class Authenticate extends Middleware
9{
10 protected function redirectTo(Request $request): ?string
11 {
12 return route('signin');
13 }
14}

Or, from a service provider:

1// app/Providers/AppServiceProvider.php
2 
3use Illuminate\Auth\Middleware\Authenticate;
4use Illuminate\Http\Request;
5 
6public function boot(): void
7{
8 Authenticate::redirectUsing(fn (Request $request) => route('signin'));
9}

Both approaches give the same result. The service provider version is nice because it keeps the customisation in one place and doesn't require modifying the framework's middleware.

Fix 5: Force JSON Responses for API Routes

If the error is showing up on API routes, the underlying issue is that the request isn't being treated as a JSON request. When $request->expectsJson() returns true, Laravel returns a 401 JSON response and never touches route('login'). When it returns false, Laravel tries to redirect.

The simplest fix is to make sure your API consumers send the right Accept header:

1Accept: application/json

If you can't control the client (or want a belt-and-braces approach), add a small middleware that forces the Accept header on every API request:

1// app/Http/Middleware/ForceJsonResponse.php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class ForceJsonResponse
10{
11 public function handle(Request $request, Closure $next): Response
12 {
13 $request->headers->set('Accept', 'application/json');
14 
15 return $next($request);
16 }
17}

Then apply it to your API group in bootstrap/app.php:

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->appendToGroup('api', [
3 \App\Http\Middleware\ForceJsonResponse::class,
4 ]);
5})

You can verify what expectsJson is doing locally:

1use Illuminate\Http\Request;
2 
3$request = Request::create('/api/users', 'GET');
4$request->headers->set('Accept', 'application/json');
5 
6$request->expectsJson(); // true

The cleanest API design is to use Laravel Sanctum's token guard (auth:sanctum) which behaves correctly on API routes without any extra config.

Fix 6: Check Route Name Prefix Conflicts

If you think you have a login route but Route::has('login') returns false, run through your route groups carefully. The most common culprit is a name prefix on a parent group:

1// WRONG: The route name is "auth.login", not "login"
2Route::name('auth.')->group(function () {
3 Route::get('/login', [LoginController::class, 'show'])->name('login');
4});
5 
6// CORRECT (option 1): Move the login route outside the prefixed group
7Route::get('/login', [LoginController::class, 'show'])->name('login');
8 
9Route::name('auth.')->group(function () {
10 Route::get('/forgot-password', [PasswordController::class, 'request'])->name('forgot-password');
11});
12 
13// CORRECT (option 2): Keep the URL inside the group, change the name
14Route::prefix('auth')->group(function () {
15 Route::get('/login', [LoginController::class, 'show'])->name('login');
16});

The difference between prefix (which affects the URL) and name (which affects the route name) is easy to miss when you're scanning route files quickly.

Fix 7: Clear Cached Routes

If your route definitions look correct but Laravel still says the route doesn't exist, the route cache might be stale:

1php artisan route:clear

Or to clear everything in one go:

1php artisan optimize:clear

This is especially important if:

  • You're deploying changes that touched route files
  • You renamed or moved a controller
  • You merged a branch that changed route definitions

In production, you'd typically run route:clear followed by route:cache again as part of your deployment script.

Debugging the Error

If none of the fixes above resolve it, here's how to track down what's actually happening.

Check if the Route Exists

Use Route::has as a defensive check anywhere you're not sure:

1use Illuminate\Support\Facades\Route;
2 
3if (Route::has('login')) {
4 return route('login');
5}
6 
7return '/'; // or some other fallback

This is also a handy snippet to drop into a test route while you're debugging:

1Route::get('/debug-login-route', function () {
2 return [
3 'has_login_route' => \Route::has('login'),
4 'login_url' => \Route::has('login') ? route('login') : null,
5 ];
6});

List Your Routes

The route:list command will tell you exactly what Laravel sees:

1php artisan route:list --name=login

If the output is empty, you don't have a route named login. If you see a route with a name like auth.login or signin, you've found the mismatch.

You can also list every route to scan for typos:

1php artisan route:list | grep -i login

Read the Stack Trace

The stack trace tells you exactly what called route('login'). Look for the frame just before UrlGenerator::route(). Common callers:

  • Illuminate\Foundation\Exceptions\Handler::unauthenticated (the auth middleware path)
  • A Blade view (look for route('login') in your templates)
  • A package (some third-party packages assume a login route exists)
  • Your own code (a controller, service, or component calling route('login') directly)

If the trace points to your own code, the fix is usually to wrap the call in Route::has or use a more specific named route.

Special Cases

Sanctum SPA Authentication

If you're building an SPA with Sanctum, the SPA frontend is supposed to call your API and get a 401 back, not a redirect. Make sure your API routes use auth:sanctum:

1// routes/api.php
2 
3use App\Http\Controllers\Api\UserController;
4 
5Route::middleware('auth:sanctum')->group(function () {
6 Route::get('/user', [UserController::class, 'show']);
7});

And make sure your frontend sends Accept: application/json with every request. Laravel's default bootstrap.js sets X-Requested-With: XMLHttpRequest on Axios, which is enough for expectsJson() to return true. But to be safe, you can also explicitly set the Accept header:

1axios.defaults.headers.common['Accept'] = 'application/json';

If you're also dealing with CSRF token issues alongside auth errors, my 419 Page Expired guide covers Sanctum's CSRF cookie flow in detail.

Inertia.js Applications

In an Inertia application, an unauthenticated request still goes through Laravel's normal exception handler, which means it still tries to call route('login'). If your login route isn't named login, you'll see the same error in an Inertia app as in a regular Blade app.

The fix is the same as Fix 3: use redirectGuestsTo to point at your actual login route.

1$middleware->redirectGuestsTo(fn (Request $request) => route('your-login-route-name'));

Tests

If this error shows up in your tests, you're almost certainly testing a route with the auth middleware without an authenticated user. The fix is to log in a test user before hitting the route:

1public function test_authenticated_users_can_view_dashboard(): void
2{
3 $user = \App\Models\User::factory()->create();
4 
5 $response = $this->actingAs($user)->get('/dashboard');
6 
7 $response->assertStatus(200);
8}

If you specifically want to test the redirect-to-login behaviour, make sure a login route is registered in your test environment, either via a starter kit or a placeholder route registered in a test service provider.

Filament, Nova, and Other Admin Panels

Admin panel packages (Filament, Nova, etc.) register their own login routes under their own names. If your only login route is the admin one and your application's own auth middleware fires, Laravel will still look for a route literally named login and fail. The simplest fix is to register a login route that redirects to the admin login URL:

1Route::get('/login', fn () => redirect()->route('filament.admin.auth.login'))->name('login');

This gives Laravel something to call route('login') on, while still sending users to the actual login form.

Summary

The "Route [login] not defined" error in Laravel is almost always a missing or mis-named login route. Here's a quick checklist:

  1. Make sure a route named login exists in your route files
  2. Install a starter kit if you don't have auth scaffolding yet (one of the Laravel 13 React, Svelte, Vue, or Livewire kits)
  3. Use redirectGuestsTo in bootstrap/app.php to point at a differently-named route
  4. Check name-prefixed route groups for accidentally renamed login routes
  5. Send Accept: application/json on API requests so you get a 401 instead of a redirect
  6. Clear cached routes with php artisan route:clear after any route changes
  7. Run php artisan route:list --name=login to verify what Laravel actually sees

The error message itself is actually pretty helpful once you know what to look for. It's telling you Laravel tried to redirect somewhere and couldn't, and once you know which route Laravel expected, the fix is almost always a one-line change.


Having trouble with authentication, route configuration, or other Laravel issues? I specialise in Laravel development and debugging complex application issues. Get in touch to discuss your project.

Topics

Syntax highlighting by Torchlight

More articles

Antlers vs Blade in Statamic: Which Should You Use in 2026?

Statamic lets you pick between two templating engines: Antlers, the built-in CMS-first language, and Blade, the same engine you already know from Laravel. This guide walks through the real differences, what each one is good at, and how to mix them when it makes sense.

Read article

How to Fix CORS Errors in Laravel 13 (2026 Guide)

CORS errors in Laravel are almost always a config issue, not a code issue. This guide covers the common causes and how to fix them in Laravel 13, including allowed origins, credentials support, preflight requests, Sanctum SPA authentication, and wildcard subdomains.

Read article

Talk to me about your website project