pallets/flask · error · TypeError

Allowed methods must be a list of strings, for example: @app

Error message

Allowed methods must be a list of strings, for example: @app.route(..., methods=["POST"])

What it means

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.

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

Solutions

  1. Wrap the method name in a list: `methods=["POST"]` (or a tuple/set).
  2. If the value comes from config as 'GET,POST', split it first: `methods=[m.strip() for m in value.split(',')]`.
  3. For class-based views, set `methods = ["GET", "POST"]` as a list on the class, not a string.

Example fix

# before
@app.route("/submit", methods="POST")
def submit():
    ...

# after
@app.route("/submit", methods=["POST"])
def submit():
    ...
Defensive patterns

Strategy: type-guard

Validate before calling

methods = ['POST']  # a list/iterable of str, never a bare string
assert not isinstance(methods, str) and all(isinstance(m, str) for m in methods)

Type guard

def is_valid_methods(methods):
    return methods is None or (not isinstance(methods, str) and hasattr(methods, '__iter__') and all(isinstance(m, str) for m in methods))

Prevention

When it happens

Trigger: `@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.

Common situations: 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.

Related errors


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