aio-libs/aiohttp · error · ValueError

To decode nested multipart you need to use custom reader

Error message

To decode nested multipart you need to use custom reader

What it means

Raised as ValueError by BaseRequest.post() (aiohttp/web_request.py:802) when the multipart parser encounters a nested multipart body part (a multipart/* part inside multipart/form-data) rather than a BodyPartReader. aiohttp's high-level post() helper does not attempt to recursively decode nested multipart; you must drive the MultipartReader yourself if your API legitimately needs nested multipart support.

Source

Thrown at aiohttp/web_request.py:802

                        raw_data = bytearray()
                        while chunk := await field.read_chunk():
                            size += len(chunk)
                            if 0 < max_size < size:
                                raise HTTPRequestEntityTooLarge(max_size)
                            raw_data.extend(chunk)

                        value = bytearray()
                        # form-data doesn't support compression, so don't need to check size again.
                        async for d in field.decode_iter(raw_data):  # type: ignore[arg-type]
                            value.extend(d)

                        if field_ct is None or field_ct.startswith("text/"):
                            charset = field.get_charset(default="utf-8")
                            out.add(field.name, value.decode(charset))
                        else:
                            out.add(field.name, value)  # type: ignore[arg-type]
                else:
                    raise ValueError(
                        "To decode nested multipart you need to use custom reader",
                    )
        else:
            data = await self.read()
            if data:
                charset = self.charset or "utf-8"
                bytes_query = data.rstrip()
                try:
                    query = bytes_query.decode(charset)
                except (LookupError, UnicodeDecodeError):
                    raise HTTPUnsupportedMediaType()
                out.extend(
                    parse_qsl(qs=query, keep_blank_values=True, encoding=charset)
                )

        self._post = MultiDictProxy(out)
        return self._post

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Use request.multipart() directly and recurse into nested MultipartReader instances yourself.
  2. Change the client to submit each file as a separate top-level multipart/form-data part (filename=...).
  3. Catch ValueError and return HTTPBadRequest explaining nested multipart is unsupported for that endpoint.

Example fix

// before
async def handler(request):
    form = await request.post()  # ValueError on nested multipart

# after
multipart = await request.multipart()
async for part in multipart:
    if isinstance(part, MultipartReader):
        async for sub in part:
            ...  # handle nested part manually
Defensive patterns

Strategy: try-catch

Validate before calling

ct = request.content_type
if ct and ct.startswith("multipart/") and ct != "multipart/form-data":
    raise web.HTTPBadRequest(text="nested multipart not supported")

Type guard

def is_flat_multipart(request) -> bool:
    return request.content_type == "multipart/form-data"

Try / catch

try:
    form = await request.post()
except ValueError as err:
    if "nested multipart" in str(err):
        raise web.HTTPBadRequest(text="use top-level form-data parts")
    raise

Prevention

When it happens

Trigger: A request with Content-Type: multipart/form-data where one of the inner parts has its own Content-Type: multipart/mixed (or any multipart/* subtype). post()'s `if isinstance(field, BodyPartReader)` branch is the else, and the inner part is a MultipartReader -> ValueError.

Common situations: RFC 7578-style 'multiple files in one field' submitted via multipart/mixed; some older desktop uploaders and WebDAV-style clients; legacy APIs that wrapped several assets in a single form field.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/ccfbc5ab34f1ec0d.json. Report an issue: GitHub.