{"id":"50e2f4bc8a628739","repo":"pallets/flask","slug":"allowed-methods-must-be-a-list-of-strings-for-exa","errorCode":null,"errorMessage":"Allowed methods must be a list of strings, for example: @app.route(..., methods=[\"POST\"])","messagePattern":"Allowed methods must be a list of strings, for example: @app\\.route\\(\\.\\.\\., methods=\\[\"POST\"\\]\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/flask/sansio/app.py","lineNumber":624,"sourceCode":"        self,\n        rule: str,\n        endpoint: str | None = None,\n        view_func: ft.RouteCallable | None = None,\n        provide_automatic_options: bool | None = None,\n        **options: t.Any,\n    ) -> None:\n        if endpoint is None:\n            endpoint = _endpoint_from_view_func(view_func)  # type: ignore\n        options[\"endpoint\"] = endpoint\n        methods = options.pop(\"methods\", None)\n\n        # if the methods are not given and the view_func object knows its\n        # methods we can use that instead.  If neither exists, we go with\n        # a tuple of only ``GET`` as default.\n        if methods is None:\n            methods = getattr(view_func, \"methods\", None) or (\"GET\",)\n        if isinstance(methods, str):\n            raise TypeError(\n                \"Allowed methods must be a list of strings, for\"\n                ' example: @app.route(..., methods=[\"POST\"])'\n            )\n        methods = {item.upper() for item in methods}\n\n        # Methods that should always be added\n        required_methods: set[str] = set(getattr(view_func, \"required_methods\", ()))\n\n        if provide_automatic_options is None:\n            provide_automatic_options = getattr(\n                view_func, \"provide_automatic_options\", None\n            )\n\n            if provide_automatic_options is None:\n                provide_automatic_options = (\n                    \"OPTIONS\" not in methods\n                    and self.config[\"PROVIDE_AUTOMATIC_OPTIONS\"]\n                )","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/pallets/flask/blob/6a2f545bfd8ed31e19066a299296917e034aca58/src/flask/sansio/app.py#L606-L642","documentation":"A TypeError from `Flask.add_url_rule` (src/flask/sansio/app.py:623-627). The `methods` option must be an iterable of method-name strings; because a plain string is itself an iterable of characters, `methods=\"POST\"` would silently become the methods {'P','O','S','T'} when Flask does `{item.upper() for item in methods}`. Flask special-cases `isinstance(methods, str)` to turn that foot-gun into an immediate, explicit error.","triggerScenarios":"`@app.route('/x', methods='POST')` or `app.add_url_rule('/x', view_func=f, methods='GET, POST')` — any place `methods` is passed as a bare string instead of a list/tuple/set of strings. Also triggers via a class-based view whose `methods` attribute was set to a string.","commonSituations":"Beginners writing `methods='POST'` by analogy with other string options; methods loaded from a config file or env var as a comma-separated string and passed through unsplit; typo dropping the brackets during a quick edit.","solutions":["Wrap the method name in a list: `methods=[\"POST\"]` (or a tuple/set).","If the value comes from config as 'GET,POST', split it first: `methods=[m.strip() for m in value.split(',')]`.","For class-based views, set `methods = [\"GET\", \"POST\"]` as a list on the class, not a string."],"exampleFix":"# before\n@app.route(\"/submit\", methods=\"POST\")\ndef submit():\n    ...\n\n# after\n@app.route(\"/submit\", methods=[\"POST\"])\ndef submit():\n    ...","handlingStrategy":"type-guard","validationCode":"methods = ['POST']  # a list/iterable of str, never a bare string\nassert not isinstance(methods, str) and all(isinstance(m, str) for m in methods)","typeGuard":"def is_valid_methods(methods):\n    return methods is None or (not isinstance(methods, str) and hasattr(methods, '__iter__') and all(isinstance(m, str) for m in methods))","tryCatchPattern":null,"preventionTips":["Always pass methods as a list: methods=[\"POST\"], never methods=\"POST\"","Uppercase strings only; avoid tuples of non-strings from config","Wrap route registration helpers to normalize a single string into [string] if you accept both"],"tags":["flask","python","routing","type-error","http-methods"],"analyzedSha":"6a2f545bfd8ed31e19066a299296917e034aca58","analyzedAt":"2026-07-31T19:13:49.921Z","schemaVersion":2}