pallets/flask · error · FormDataRoutingRedirect

A request was sent to '{request.url}', but routing issued a

Error message

A request was sent to '{request.url}', but routing issued a redirect to the canonical URL '{exc.new_url}'. The URL was defined with a trailing slash. Flask will redirect to the URL with a trailing slash if it was accessed without one. Send requests to the canonical URL, or use 307 or 308 for routing redirects. Otherwise, browsers will drop form data.

This exception is only raised in debug mode.

What it means

This is `FormDataRoutingRedirect`, raised by `Flask.raise_routing_exception` (src/flask/app.py:562) in debug mode when Werkzeug's router issues a non-307/308 `RequestRedirect` (e.g. 301 to the trailing-slash canonical URL) for a request whose method can carry a body (POST/PUT/etc.). Following such a redirect would make browsers switch to GET and drop the form data, so Flask surfaces a loud error instead of a silent data loss. Modern Werkzeug uses 308 redirects, which preserve method and body, so this mainly appears with older Werkzeug or custom rules.

Source

Thrown at src/flask/app.py:588

        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 6a2f545bfd)

Solutions

  1. Send the request to the canonical URL exactly as defined (include the trailing slash).
  2. Upgrade Werkzeug so routing redirects use 308, which preserves the method and body.
  3. Define the route without a trailing slash, or set `strict_slashes=False` on the rule, if you want both forms to work: `@app.route('/submit', strict_slashes=False)`.

Example fix

# before (route is /submit/, client posts to /submit)
fetch("/submit", {method: "POST", body: data})
# after
fetch("/submit/", {method: "POST", body: data})
Defensive patterns

Strategy: validation

Validate before calling

# send requests to the canonical URL (trailing slash matches the route definition)
resp = client.post('/items/', data=payload)  # route defined as '/items/'

Try / catch

try:
    resp = client.post('/items', data=payload)
except Exception as e:  # flask.app raises only in debug mode
    if 'routing issued a redirect' in str(e):
        # fix the caller to use the canonical URL
        raise

Prevention

When it happens

Trigger: `app.debug` is True, `request.routing_exception` is a `RequestRedirect` with a code other than 307/308, and the method is not GET/HEAD/OPTIONS — e.g. POSTing to `/items` when the route is defined as `/items/` (strict_slashes redirect) under an older Werkzeug, or custom routing rules/redirect_defaults producing 301/302.

Common situations: Defining routes with trailing slashes (`@app.route('/submit/')`) and POSTing to the slashless URL from JS or tests; pinned old Werkzeug versions (<2.1 behavior); reverse proxies rewriting paths so the app sees the non-canonical URL.

Related errors


AI-assisted analysis of pallets/flask@6a2f545bfd (2026-07-31). Data as JSON: /data/errors/d3277e0b0fd5869d.json. Report an issue: GitHub ↗.