tiangolo/fastapi · error · AssertionError
fallback must be 'auto', 'index.html', '404.html', or None
Error message
fallback must be 'auto', 'index.html', '404.html', or None
What it means
Raised by `_FrontendRoute.__init__` (fastapi/routing.py:2057) as an AssertionError when the `fallback` argument is not one of the allowed values {'auto', 'index.html', '404.html', None}. FastAPI restricts fallback to a fixed literal set so that fallback semantics are well-defined; any other string (e.g. 'main.html', 'true', 0) is rejected at route setup time.
Solutions
- Use one of the allowed literals: `fallback='auto'` (default), `'index.html'`, `'404.html'`, or `None`.
- If you need a custom file, place it as index.html/404.html in the directory and select the matching literal.
- Validate config-driven values against the allowed set before calling `frontend`.
Example fix
// before
router.frontend('/', directory='dist', fallback='main.html')
// after
router.frontend('/', directory='dist', fallback='index.html') Defensive patterns
Strategy: validation
Validate before calling
from typing import Literal
ALLOWED = {'auto', 'index.html', '404.html', None}
def coerce_fallback(value: str | None) -> Literal['auto','index.html','404.html'] | None:
if value not in ALLOWED:
raise ValueError(f'fallback must be one of {ALLOWED}')
return value # type: ignore[return-value]
router.frontend('/', directory='dist', fallback=coerce_fallback(cfg.get('fallback'))) Type guard
def is_allowed_fallback(value: object) -> bool:
return value in {'auto', 'index.html', '404.html', None} Prevention
- Constrain config options to the allowed literal set before passing to frontend().
- Use mypy/pyright Literal types to catch invalid fallback values statically.
- Avoid passing arbitrary filenames as fallback.
When it happens
Trigger: Calling `router.frontend('/', directory='dist', fallback='main.html')`. Passing a boolean or integer instead of the literal (`fallback=True`). Passing a typo like `fallback='Auto'` (capitalized). Passing an arbitrary filename expecting it to be used as fallback.
Common situations: Assuming any filename can be a fallback. Copying example code that used a non-standard value. Case-sensitivity mistakes. Passing a value read from config without validating it against the allowed set.
Related errors
- A frontend path cannot be empty
- A frontend path must start with '/'
- Frontend directory ' ' does not exist. Resolved absolute…
- Frontend fallback file
- No route exists for name
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/e3a526c6cc3a4ed1.
Report an issue: GitHub.
Appendix: source
Thrown at fastapi/routing.py:2057
for media_type, quality in _iter_accept_media_types(
request.headers.get("accept", "")
):
if media_type in {"text/html", "application/xhtml+xml"} and quality != 0:
return True
return False
class _FrontendRoute(BaseRoute):
def __init__(
self,
path: str,
*,
directory: str | os.PathLike[str],
fallback: Literal["auto", "index.html", "404.html"] | None = "auto",
check_dir: bool,
) -> None:
if fallback not in {"auto", "index.html", "404.html", None}:
raise AssertionError(
"fallback must be 'auto', 'index.html', '404.html', or None"
)
self.path = _normalize_frontend_path(path)
self.methods = {"GET", "HEAD"}
self.app = _FrontendStaticFiles(
directory=directory, fallback=fallback, check_dir=check_dir
)
def matches(self, scope: Scope) -> tuple[Match, Scope]:
return self.matches_with_path(scope, self.path)
def matches_with_path(self, scope: Scope, path: str) -> tuple[Match, Scope]:
if scope["type"] != "http":
return Match.NONE, {}
frontend_path = self._get_frontend_path(path, get_route_path(scope))
if frontend_path is None:
return Match.NONE, {}
child_scope = {View on GitHub (pinned to 3e8d1526d8)