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

  1. Wrap the value in a list: methods=["POST"] instead of methods="POST".
  2. For a single method shortcut, use @app.get(rule) / @app.post(rule) which do not take methods.
  3. 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

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


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)