aio-libs/aiohttp · error · TypeError

Only io.IOBase, multidict and (name, file) pairs allowed…

Error message

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

What it means

FormData.add_fields (plural) raises TypeError when it encounters a record that is not an io.IOBase instance, a MultiDict/MultiDictProxy, or a 2-element list/tuple of (name, value). The variadic add_fields method iterates records and dispatches them — anything that does not match the three accepted shapes is rejected.

Solutions

  1. Use add_field (singular) for fields with content_type or filename: form.add_field('name', value, content_type='text/plain')
  2. Convert a dict to (name, value) tuples: form.add_fields(*my_dict.items())
  3. For IOBase objects, ensure they are not wrapped in another container

Example fix

# before (3-tuple is rejected)
form.add_fields(('file', data, 'application/pdf'))

# after
form.add_field('file', data, content_type='application/pdf')
Defensive patterns

Strategy: validation

Validate before calling

from multidict import MultiDict, MultiDictProxy
import io

def validate_form_fields(fields):
    for rec in fields:
        if isinstance(rec, io.IOBase):
            continue
        if isinstance(rec, (MultiDict, MultiDictProxy)):
            continue
        if isinstance(rec, (list, tuple)) and len(rec) == 2:
            continue
        raise TypeError(f'Unsupported field type: {type(rec).__name__}. '
                        f'Use add_field() for complex parameters.')

Type guard

def is_valid_form_field(rec) -> bool:
    import io
    from multidict import MultiDict, MultiDictProxy
    return (
        isinstance(rec, io.IOBase) or
        isinstance(rec, (MultiDict, MultiDictProxy)) or
        (isinstance(rec, (list, tuple)) and len(rec) == 2)
    )

Prevention

When it happens

Trigger: Passing a plain dict to add_fields; passing a 3-tuple (name, value, content_type) instead of using add_field; passing a single scalar value or a string; passing a list longer than 2 elements.

Common situations: Building FormData from a dict.items() that includes non-file values mixed incorrectly; trying to use the (name, value, content_type) shorthand that some other libraries support; passing a list of lists where inner lists have more than 2 elements.

Related errors


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

Appendix: source

Thrown at aiohttp/formdata.py:101

    def add_fields(self, *fields: Any) -> None:
        to_add: deque[Any] = deque(fields)

        while to_add:
            rec = to_add.popleft()

            if isinstance(rec, io.IOBase):
                k = guess_filename(rec, "unknown")
                self.add_field(k, rec)  # type: ignore[arg-type]

            elif isinstance(rec, (MultiDictProxy, MultiDict)):
                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:

View on GitHub (pinned to d041d4d0fd)