In Laravel, what does Route::fallback() do, where should you define it, and what are its limits?
answer
- runs when nothing else matches
- checked last wherever it is declared
- inherits its file's middleware group
- GET and HEAD: {fallbackPlaceholder} with .*
- status is yours to set
basics
~20 sRoute::fallback() registers a GET/HEAD route that runs only when no other route matches; defined in routes/web.php it gets the web middleware group, it is checked last wherever it is declared, and its response status is whatever the action returns.
solid answer
~40 s`Route::fallback($action)` registers a GET route (HEAD is added, as for every GET route) with the URI `{fallbackPlaceholder}` constrained to `.*` and marks it as a fallback. The router skips fallback routes until every other route has failed to match, so its position in the file does not matter. Defined in `routes/web.php` it runs inside the `web` middleware group, so a friendly not-found page can use the session and the logged-in user; defined in `routes/api.php` it gets the `api` group and prefix, which suits a JSON not-found body. Two limits catch people: it answers only GET and HEAD, so an unknown POST is not handled by it, and it does not set 404 for you: `return view('errors.recipe-missing')` answers 200 unless you pass a 404 status.
code
php · 8 lines<?php
// routes/api.php - served under /api by the builder
use Illuminate\Support\Facades\Route;
Route::fallback(function () {
return response()->json(['message' => 'Endpoint not found.'], 404);
});go deeper
Know that Route::fallback runs when no other route matches and that it usually lives in routes/web.php.
Explain that the router checks fallbacks last, that the route answers only GET and HEAD, and that it inherits its file's middleware group.
Return an explicit 404 to avoid soft-404 pages, give APIs their own JSON fallback, and know when the exception handler's error views are the better tool.
Set one consistent not-found contract for pages and APIs, including status codes and body shape, and decide which layer owns it.
## What the fallback route is Without a fallback, a request that matches no route makes Laravel throw a `NotFoundHttpException`, and the exception handler renders the 404 page. **`Route::fallback()`** lets you handle that case with an ordinary route action instead. The router's source shows what it registers: - A route for the **GET** verb; the `Route` constructor adds HEAD, as it does for every GET route. - The URI `{fallbackPlaceholder}`, constrained with `where('fallbackPlaceholder', '.*')`, so it matches any path, slashes included. - The route is flagged as a **fallback** (`isFallback`). In `php artisan route:list` it appears as `GET|HEAD {fallbackPlaceholder}`. ## Checked last, wherever it is declared When the router matches a request, it walks the routes for the request's verb and **skips fallback routes**, remembering the first one that matched. Only if no ordinary route matches does it return the fallback. When routes are cached, fallback routes are also compiled after all others. So: 1. Declaring the fallback at the top of `routes/web.php` does not shadow the routes below it. 2. It still pays to declare it at the bottom, simply so readers find it where they expect it. ## Which middleware it gets A fallback is a normal route inside whatever group its file is registered with: | Defined in | Middleware group | Path it covers | Typical response | |---|---|---|---| | `routes/web.php` | `web` | everything unmatched | a branded HTML page using the session and `auth()->user()` | | `routes/api.php` | `api`, with the `/api` prefix | unmatched paths under `/api/` | a JSON body with a 404 status | The docs point out this difference from the default 404 handling: because the fallback lives in `routes/web.php`, all the web middleware runs for it, and you may add more. On a recipe site that also offers a JSON API, both can coexist. The builder registers `routes/api.php` before `routes/web.php`, so for an unknown `/api/...` path the API fallback is found first. ## The limits - **GET and HEAD only.** A POST, PUT or DELETE to an unknown URL is not handled by the fallback. Because a GET route (the fallback) matches the path, the router answers such requests with **405 Method Not Allowed** rather than running the fallback. - **You own the status code.** `Route::fallback(fn () => view('errors.recipe-missing'))` sends **200**, because a view response defaults to 200. Search engines then index "soft 404" pages. Return `response()->view('errors.recipe-missing', [], 404)`, or call `abort(404)` so the exception handler renders your 404 error view. - **It is not an error handler.** A 404 thrown inside a matched route (for example a missing model) is still rendered by the exception handler, not by the fallback. ## A recipe-site example ```php use Illuminate\Support\Facades\Route; // routes/web.php - bottom of the file for readability Route::fallback(function () { return response()->view('errors.recipe-missing', [ 'suggestions' => cache('popular_recipes', []), ], 404); }); ``` ```php // routes/api.php Route::fallback(function () { return response()->json(['message' => 'Endpoint not found.'], 404); }); ``` ## Fallback versus a hand-written catch-all Before `Route::fallback()` existed, apps wrote `Route::any('{any}', ...)->where('any', '.*')`. The two are not equivalent: | | `Route::fallback()` | `Route::any('{any}')->where('any', '.*')` | |---|---|---| | Verbs | GET and HEAD | all seven router verbs | | Matching order | always after ordinary routes | an ordinary route, matched in registration order | | Risk | unknown writes get 405 | declared too early, it swallows real routes | The hand-written catch-all is only safe at the very end of the last file registered, and even then it silently answers POST and DELETE with a page meant for lost visitors. The fallback flag exists precisely so the catch-all no longer depends on file order. ## When to use it and when not to - **Use it** when unmatched URLs need app context (session, the signed-in user, suggestions) or when an API needs its own JSON not-found shape. - **Skip it** when the stock 404 page, customised through the exception handler's error views, is enough; that path also covers 404s thrown from inside matched routes.
- Why does a POST to an unknown URL in a Laravel app with a fallback route get a 405 instead of the fallback page?`Route::fallback()` registers GET, plus the HEAD Laravel adds to every GET route. For a POST, no POST route matches, so the router checks other verbs, finds that the GET fallback matches the path, and throws `MethodNotAllowedHttpException`. Handling unknown writes needs a separate route, such as `Route::any` on a catch-all pattern, or customised 405 rendering in the exception handler.
- What is the difference between a fallback route and a customised 404 error page?The fallback only runs when no route matched, and it runs as a route with its file's middleware. A 404 error view is rendered by the exception handler for any `NotFoundHttpException`, including `abort(404)` or a missing model inside a matched route. Many apps need only the error view; the fallback adds app context for unmatched URLs.
A fallback route is the lost-and-found desk in a train station: you are sent there only after every other counter has said no, and it makes no difference where in the hall the desk happens to stand.
saying these in an interview costs you the question
- The fallback must be the last line in web.php or it shadows later routes
- Route::fallback catches unknown POST and DELETE requests too
- A fallback returning a view sends a 404 status automatically
- Fallback routes skip the web middleware group
- A fallback also handles 404s thrown inside matched routes