pallets/flask · error · TypeError
Allowed methods must be a list of strings, for example…
Error message
Allowed methods must be a list of strings, for example: @app.route(..., methods=["POST"])
What it means
In add_url_rule, Flask expects the methods argument to be an iterable of strings. If methods is a single str (e.g. "GET"), Python would otherwise silently iterate character-by-character, so Flask explicitly raises TypeError with a corrective hint. This prevents silently registering methods like 'G','E','T'.
Solutions
- Wrap the value in a list: methods=["POST"] instead of methods="POST".
- For a single method shortcut, use @app.get(rule) / @app.post(rule) which do not take methods.
- If generating methods dynamically, coerce to a list: methods=[m] if isinstance(m, str) else m.
Example fix
// before
@app.route('/login', methods="POST")
def login(): ...
// after
@app.route('/login', methods=["POST"])
def login(): ... Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_methods(methods):
if isinstance(methods, str):
return [methods]
return list(methods)
Type guard
def is_methods_arg_valid(methods) -> bool:
return not isinstance(methods, str)
Prevention
- Always pass methods as a list, even for a single method.
- Prefer @app.get/@app.post shortcuts for single-method routes.
- Lint route registrations to flag methods=<str>.
When it happens
Trigger: Passing methods="POST" or methods="GET" (a bare string) to app.add_url_rule or @app.route(..., methods=...); passing a methods string to app.url_rule_class indirectly.
Common situations: Developer forgets the list brackets; copying a single-method example and dropping the brackets; dataclass/config-driven route registration that yields a string for single-method routes.
Related errors
- Use the 'route' decorator to use the 'methods' argument.
- A valid Flask application was not obtained from
- Could not import .
- Could not locate a Flask application. Use the 'flask --app'…
- is an instance, not a class. Handlers can only be…
AI-assisted analysis of pallets/flask@3596b1ab61 (2026-08-11).
Data as JSON: /api/errors/50e2f4bc8a628739.
Report an issue: GitHub.
Appendix: source
Thrown at src/flask/sansio/app.py:624
self,
rule: str,
endpoint: str | None = None,
view_func: ft.RouteCallable | None = None,
provide_automatic_options: bool | None = None,
**options: t.Any,
) -> None:
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore
options["endpoint"] = endpoint
methods = options.pop("methods", None)
# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, str):
raise TypeError(
"Allowed methods must be a list of strings, for"
' example: @app.route(..., methods=["POST"])'
)
methods = {item.upper() for item in methods}
# Methods that should always be added
required_methods: set[str] = set(getattr(view_func, "required_methods", ()))
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)
if provide_automatic_options is None:
provide_automatic_options = (
"OPTIONS" not in methods
and self.config["PROVIDE_AUTOMATIC_OPTIONS"]
)View on GitHub (pinned to 3596b1ab61)