pallets/flask · warning · FormDataRoutingRedirect
A request was sent to
Error message
A request was sent to '{request.url}', but routing issued a redirect to the canonical URL '{exc.new_url}'. Send requests to the canonical URL, or use 307 or 308 for routing redirects. Otherwise, browsers will drop form data.\n\nThis exception is only raised in debug mode. What it means
Raised in debug mode when Map routing issues a RequestRedirect to a canonical URL on a non-safe method (POST/PUT/DELETE/PATCH). Browsers would drop the request body on a 301/302 redirect, so Flask raises FormDataRoutingRedirect to surface the misconfiguration. In non-debug mode the redirect is passed through unchanged.
Solutions
- Make the form action match the canonical route URL exactly (including trailing slash).
- Set redirect_codes to 307/308 on the Map/url_map so body is preserved.
- Align trailing slash usage: configure the route with strict_slashes=False or normalize all URLs.
Example fix
// before
@app.route('/submit/', methods=['POST'])
def submit(): ...
# form action='/submit' -> redirect, body lost, debug raises
// after
@app.route('/submit/', methods=['POST'])
def submit(): ...
# <form action="/submit/" method="post"> (match canonical URL) Defensive patterns
Strategy: validation
Validate before calling
def route_matches_request(rule_path, request_path):
return rule_path.rstrip('/') == request_path.rstrip('/') Type guard
def safe_method_for_redirect(method) -> bool:
return method in {'GET', 'HEAD', 'OPTIONS'} Try / catch
try:
app.full_dispatch_request()
except FormDataRoutingRedirect:
# surface to logs, fix URL canonicalization
raise Prevention
- Keep form action URLs and route paths consistent (trailing slashes).
- Set strict_slashes=False on routes that are ambiguous.
- Run the app in debug mode locally to catch redirect mismatches early.
When it happens
Trigger: A POST/PUT form targets a URL whose route is defined with a trailing slash (or vice versa) and the request lacks it, triggering Werkzeug's redirect-to-canonical path, while app.debug is True and the status code is not 307/308.
Common situations: Form action URL mismatches the route's trailing-slash convention (e.g. action="/submit" but route is "/submit/"); inconsistent trailing slashes between links and routes during development.
Related errors
- You tried to access the file
- Allowed methods must be a list of strings, for example…
- Use the 'route' decorator to use the 'methods' argument.
- View function mapping is overwriting an existing endpoint…
AI-assisted analysis of pallets/flask@3596b1ab61 (2026-08-11).
Data as JSON: /api/errors/51827057c9332902.
Report an issue: GitHub.
Appendix: source
Thrown at src/flask/app.py:589
body.
.. versionchanged:: 2.1
Don't intercept 307 and 308 redirects.
:meta private:
:internal:
"""
if (
not self.debug
or not isinstance(request.routing_exception, RequestRedirect)
or request.routing_exception.code in {307, 308}
or request.method in {"GET", "HEAD", "OPTIONS"}
):
raise request.routing_exception # type: ignore[misc]
from .debughelpers import FormDataRoutingRedirect
raise FormDataRoutingRedirect(request)
def update_template_context(
self, ctx: AppContext, context: dict[str, t.Any]
) -> None:
"""Update the template context with some commonly used variables.
This injects request, session, config and g into the template
context as well as everything template context processors want
to inject. Note that the as of Flask 0.6, the original values
in the context will not be overridden if a context processor
decides to return a value with the same key.
:param context: the context as a dictionary that is updated in place
to add extra variables.
"""
names: t.Iterable[str | None] = (None,)
# A template may be rendered outside a request context.
if ctx.has_request:View on GitHub (pinned to 3596b1ab61)