aio-libs/aiohttp · error · TypeError
Only io.IOBase, multidict and (name, file) pairs allowed, us
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
Raised by FormData.add_fields when an element of the *fields sequence is not an io.IOBase, not a MultiDict/MultiDictProxy, and not a 2-element list/tuple. add_fields dispatches on the runtime type of each record; anything else falls into the else branch and raises TypeError, pointing the user at add_field() for complex parameters.
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 c0ef574e29)
Solutions
- Use add_field() for each field with explicit name/value.
- Pass a list of (name, value) 2-tuples.
- Pass a MultiDict of name/value pairs for simple forms.
Example fix
// before
form = FormData([('field', val, 'text/plain')]) # 3-tuple not unpacked
// after
form = FormData()
form.add_field('field', val, content_type='text/plain') Defensive patterns
Strategy: validation
Validate before calling
def normalize_fields(fields):
if isinstance(fields, dict):
return [(k, v) for k, v in fields.items()]
if isinstance(fields, (list, tuple)):
for rec in fields:
assert isinstance(rec, (list, tuple)) and len(rec) == 2, f'bad record {rec!r}'
return list(fields)
raise TypeError('fields must be dict or list of 2-tuples') Type guard
def is_valid_field_record(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
- Prefer add_field() for anything beyond simple (name, value) pairs.
- Never pass 3-tuples; use add_field(name, value, content_type=...).
- Normalize input to a list of 2-tuples before constructing FormData.
When it happens
Trigger: Passing FormData({'k': v}) is fine (dict converted), but FormData('plain string'), FormData(42), FormData((1,2,3)) (3-tuple), or FormData([{'nested': 'dict'}]) triggers it. Also FormData(SomeObj()).
Common situations: Passing a single string value directly to FormData instead of a list of pairs; passing 3-tuples (name, value, content_type) which add_fields does not unpack; passing a single non-file object.
Related errors
- filename must be an instance of str. Got: %s
- content_type must be an instance of str. Got: %s
- expected str, got {value!r}
- Can not serialize value type: %r headers: %r value: %r
- Unable to decode.
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/1379bd6dd2294418.json.
Report an issue: GitHub.