aio-libs/aiohttp · error · ValueError

charset must not be in content_type argument

Error message

charset must not be in content_type argument

What it means

The content_type argument must be a bare media type (e.g. 'application/json'); aiohttp builds the full Content-Type header including charset itself (line 581, 593). Embedding 'charset=' in content_type double-sets it and is rejected at line 562-563.

Source

Thrown at aiohttp/web_response.py:563

        status: int = 200,
        reason: str | None = None,
        text: str | None = None,
        headers: LooseHeaders | None = None,
        content_type: str | None = None,
        charset: str | None = None,
        zlib_executor_size: int = MAX_SYNC_CHUNK_SIZE,
        zlib_executor: Executor | None = None,
    ) -> None:
        if body is not None and text is not None:
            raise ValueError("body and text are not allowed together")

        if headers is None:
            real_headers: CIMultiDict[str] = CIMultiDict()
        else:
            real_headers = CIMultiDict(headers)

        if content_type is not None and "charset" in content_type:
            raise ValueError("charset must not be in content_type argument")

        if text is not None:
            if hdrs.CONTENT_TYPE in real_headers:
                if content_type or charset:
                    raise ValueError(
                        "passing both Content-Type header and "
                        "content_type or charset params "
                        "is forbidden"
                    )
            else:
                # fast path for filling headers
                if not isinstance(text, str):
                    raise TypeError("text argument must be str (%r)" % type(text))
                if content_type is None:
                    content_type = "text/plain"
                if charset is None:
                    charset = "utf-8"
                real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Pass content_type='text/html' and charset='utf-8' separately.
  2. Drop the charset segment entirely and let aiohttp default it.
  3. If you need full control, pass headers={'Content-Type': '...'} and omit content_type/charset.

Example fix

# before
resp = Response(text=html, content_type='text/html; charset=utf-8')  # raises ValueError

# after
resp = Response(text=html, content_type='text/html', charset='utf-8')
Defensive patterns

Strategy: validation

Validate before calling

def clean_content_type(ct: str | None) -> str | None:
    if ct and 'charset' in ct.lower():
        # split off charset; let aiohttp re-add it
        return ct.split(';')[0].strip()
    return ct

Type guard

def content_type_is_bare(ct: str | None) -> bool:
    return ct is None or (';' not in ct and 'charset' not in ct.lower())

Prevention

When it happens

Trigger: Calling Response(content_type='text/html; charset=utf-8', ...) or json_response(content_type='application/json; charset=utf-8'). The substring check is 'charset' in content_type.

Common situations: Copy-pasting a full Content-Type header value into the content_type param; building content_type dynamically and accidentally concatenating '; charset=...'.

Related errors


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