aio-libs/aiohttp · error · TypeError

expected str, got

Error message

expected str, got {value!r}

What it means

FormData._gen_form_urlencoded raises TypeError when generating application/x-www-form-urlencoded body data and a field value is not a str. The urlencoded format only supports string values; binary or file content triggers this error. This fires when is_multipart is False (no files were added) but a non-str value slipped in.

Solutions

  1. Convert non-str values to str before adding: form.add_field('count', str(42))
  2. If you have file/binary data, ensure at least one field sets is_multipart (use add_field with a file or content_type to switch to multipart encoding)
  3. Pre-process your data dict to stringify all values

Example fix

# before
form.add_field('quantity', 42)
form.add_field('price', 19.99)

# after
form.add_field('quantity', str(42))
form.add_field('price', str(19.99))
Defensive patterns

Strategy: validation

Validate before calling

def stringify_form_values(fields):
    """Convert all values to str for urlencoded form data."""
    return [(name, str(value) if not isinstance(value, str) else value)
            for name, value in fields]

Prevention

When it happens

Trigger: Adding an int, float, bool, bytes, or object value to FormData that will be serialized as urlencoded (no multipart fields present). The error surfaces at serialization time, not at add_field time, because the multipart flag is only set by file/content_type fields.

Common situations: Form data with mixed numeric values from a calculation: form.add_field('count', 42); passing a bool value: form.add_field('active', True); passing bytes that were not intended as file content.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/f78b10ef8a0e6cf7. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/formdata.py:112

                to_add.extend(rec.items())

            elif isinstance(rec, (list, tuple)) and len(rec) == 2:
                k, fp = rec
                self.add_field(k, fp)

            else:
                raise TypeError(
                    "Only io.IOBase, multidict and (name, file) "
                    "pairs allowed, use .add_field() for passing "
                    f"more complex parameters, got {rec!r}"
                )

    def _gen_form_urlencoded(self) -> payload.BytesPayload:
        # form data (x-www-form-urlencoded)
        data = []
        for type_options, _, value in self._fields:
            if not isinstance(value, str):
                raise TypeError(f"expected str, got {value!r}")
            data.append((type_options["name"], value))

        charset = self._charset if self._charset is not None else "utf-8"

        if charset == "utf-8":
            content_type = "application/x-www-form-urlencoded"
        else:
            content_type = "application/x-www-form-urlencoded; charset=%s" % charset

        return payload.BytesPayload(
            urlencode(data, doseq=True, encoding=charset).encode(),
            content_type=content_type,
        )

    def _gen_form_data(self) -> multipart.MultipartWriter:
        """Encode a list of fields using the multipart/form-data MIME format"""
        for dispparams, headers, value in self._fields:
            try:

View on GitHub (pinned to d041d4d0fd)