{"record":{"id":"51827057c9332902","repo":"pallets/flask","slug":"a-request-was-sent-to-request-url-but-routing","errorCode":null,"errorMessage":"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.","messagePattern":"A request was sent to '(.+?)', but routing issued a redirect to the canonical 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\\.","errorType":"exception","errorClass":"FormDataRoutingRedirect","httpStatus":null,"severity":"warning","filePath":"src/flask/app.py","lineNumber":589,"sourceCode":"        body.\n\n        .. versionchanged:: 2.1\n            Don't intercept 307 and 308 redirects.\n\n        :meta private:\n        :internal:\n        \"\"\"\n        if (\n            not self.debug\n            or not isinstance(request.routing_exception, RequestRedirect)\n            or request.routing_exception.code in {307, 308}\n            or request.method in {\"GET\", \"HEAD\", \"OPTIONS\"}\n        ):\n            raise request.routing_exception  # type: ignore[misc]\n\n        from .debughelpers import FormDataRoutingRedirect\n\n        raise FormDataRoutingRedirect(request)\n\n    def update_template_context(\n        self, ctx: AppContext, context: dict[str, t.Any]\n    ) -> None:\n        \"\"\"Update the template context with some commonly used variables.\n        This injects request, session, config and g into the template\n        context as well as everything template context processors want\n        to inject.  Note that the as of Flask 0.6, the original values\n        in the context will not be overridden if a context processor\n        decides to return a value with the same key.\n\n        :param context: the context as a dictionary that is updated in place\n                        to add extra variables.\n        \"\"\"\n        names: t.Iterable[str | None] = (None,)\n\n        # A template may be rendered outside a request context.\n        if ctx.has_request:","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/pallets/flask/blob/3596b1ab61cea85edb8970e83ff61daa073facf8/src/flask/app.py#L571-L607","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@app.route('/submit/', methods=['POST'])\ndef submit(): ...\n# form action='/submit' -> redirect, body lost, debug raises\n// after\n@app.route('/submit/', methods=['POST'])\ndef submit(): ...\n# <form action=\"/submit/\" method=\"post\">  (match canonical URL)","handlingStrategy":"validation","validationCode":"def route_matches_request(rule_path, request_path):\n    return rule_path.rstrip('/') == request_path.rstrip('/')","typeGuard":"def safe_method_for_redirect(method) -> bool:\n    return method in {'GET', 'HEAD', 'OPTIONS'}","tryCatchPattern":"try:\n    app.full_dispatch_request()\nexcept FormDataRoutingRedirect:\n    # surface to logs, fix URL canonicalization\n    raise","preventionTips":["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."],"tags":["routing","debug","forms","redirect"],"backgroundTag":null,"analyzedSha":"3596b1ab61cea85edb8970e83ff61daa073facf8","analyzedAt":"2026-08-11T20:11:52.622Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}