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.php2 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 --dev2php artisan breeze:install3php artisan migrate4npm 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 string14 $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): ?string11 {12 return route('signin');13 }14}
Or, from a service provider:
1// app/Providers/AppServiceProvider.php2 3use Illuminate\Auth\Middleware\Authenticate;4use Illuminate\Http\Request;5 6public function boot(): void7{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 ForceJsonResponse10{11 public function handle(Request $request, Closure $next): Response12 {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 name14Route::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
loginroute 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.php2 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(): void2{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:
- Make sure a route named
loginexists in your route files - Install a starter kit if you don't have auth scaffolding yet (one of the Laravel 13 React, Svelte, Vue, or Livewire kits)
- Use
redirectGuestsToinbootstrap/app.phpto point at a differently-named route - Check name-prefixed route groups for accidentally renamed login routes
- Send
Accept: application/jsonon API requests so you get a 401 instead of a redirect - Clear cached routes with
php artisan route:clearafter any route changes - Run
php artisan route:list --name=loginto 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 articleHow 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