aio-libs/aiohttp · error · ValueError
Unsupported body type %r
Error message
Unsupported body type %r
What it means
Raised by the Response.body setter when the value is not None, not bytes/bytearray, and not registered in aiohttp.payload.PAYLOAD_REGISTRY. The registry maps Python types (str, dict via JsonPayload, file objects via StreamReader, etc.) to Payload adapters; anything not registered is rejected as unsupported rather than silently coerced.
Solutions
- Serialize to bytes first: `resp.body = json.dumps(obj).encode()` or `orjson.dumps(obj)`.
- Use `text=` for str payloads, `body=` for bytes, `json_response()` for dict/list objects.
- Register a custom Payload adapter via `aiohttp.payload.PAYLOAD_REGISTRY.register(MyType, MyPayload)` if you need first-class support.
- Wrap file-like objects in `aiohttp.streamer` or use `web.FileResponse` for filesystem paths.
Example fix
// before
resp.body = my_dataclass_instance # LookupError -> ValueError
// after
resp.body = json.dumps(asdict(my_dataclass_instance)).encode('utf-8') Defensive patterns
Strategy: type-guard
Validate before calling
def to_body_bytes(value):
if isinstance(value, (bytes, bytearray)):
return bytes(value)
if isinstance(value, str):
return value.encode('utf-8')
# fall back to JSON for dict/list/dataclass-like objects
return json.dumps(value).encode('utf-8')
resp.body = to_body_bytes(value) Type guard
import bytes as _
def is_supported_body(value) -> bool:
return value is None or isinstance(value, (bytes, bytearray, str, dict, list, tuple)) Try / catch
try:
resp.body = value
except ValueError:
resp.body = json.dumps(value).encode('utf-8') Prevention
- Serialize ORM/dataclass/Pydantic objects to bytes before assigning body.
- Use text= for str, body= for bytes, json_response() for objects.
- Register custom Payload adapters via PAYLOAD_REGISTRY for first-class types.
- Use web.FileResponse for filesystem paths.
When it happens
Trigger: Assigning `resp.body = 42`, a custom class instance, a `pathlib.Path`, a `memoryview` of an unsupported backing type, or any object whose type has no registered Payload adapter. Also triggered when a Payload subclass is registered but the value is a sibling type.
Common situations: Returning ORM model instances, dataclasses, or Pydantic objects directly as body without serializing. Passing a generator/async generator that isn't wrapped in a StreamingResponse-like payload. Forgetting that str needs to go through text= (str IS registered but the error fires for truly unknown types).
Related errors
- body and text are not allowed together
- Content length is set automatically
- Cannot call write() after write_eof()
- Cannot call write() before prepare()
- charset must not be in content_type argument
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/177be9342c2a4411.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_response.py:620
self._zlib_executor_size = zlib_executor_size
self._zlib_executor = zlib_executor
@property
def body(self) -> bytes | bytearray | Payload | None:
return self._body
@body.setter
def body(self, body: Any) -> None:
if body is None:
self._body = None
elif isinstance(body, (bytes, bytearray)):
self._body = body
else:
try:
self._body = body = payload.PAYLOAD_REGISTRY.get(body)
except payload.LookupError:
raise ValueError("Unsupported body type %r" % type(body))
headers = self._headers
# set content-type
if hdrs.CONTENT_TYPE not in headers:
headers[hdrs.CONTENT_TYPE] = body.content_type
# copy payload headers
if body.headers:
for key, value in body.headers.items():
if key not in headers:
headers[key] = value
self._compressed_body = None
@property
def text(self) -> str | None:
if self._body is None:View on GitHub (pinned to d041d4d0fd)