aio-libs/aiohttp · error · ValueError

passing both Content-Type header and content_type or charset

Error message

passing both Content-Type header and content_type or charset params is forbidden

What it means

When you pass text=, the constructor populates Content-Type for you. But if you ALSO pass a Content-Type header AND a content_type/charset kwarg, the two sources conflict; this is detected at lines 566-572 and raises ValueError to avoid an ambiguous header.

Source

Thrown at aiohttp/web_response.py:568

        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
                body = text.encode(charset)
                text = None
        elif hdrs.CONTENT_TYPE in real_headers:
            if content_type is not None or charset is not None:
                raise ValueError(

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Pick ONE source: either set Content-Type in headers, or use content_type+charset kwargs — not both.
  2. Strip Content-Type from the headers dict before passing it when you intend to use content_type.
  3. Let text= infer Content-Type and pass neither header nor kwargs.

Example fix

# before
resp = Response(text=body, headers={'Content-Type':'text/plain'}, content_type='text/html')

# after
resp = Response(text=body, content_type='text/html')
Defensive patterns

Strategy: validation

Validate before calling

def build_response(text=None, headers=None, content_type=None, charset=None, **kw):
    headers = dict(headers or {})
    has_ct_header = any(k.lower() == 'content-type' for k in headers)
    if has_ct_header and (content_type or charset):
        # resolve the conflict: drop kwargs in favor of the header
        content_type = charset = None
    return Response(text=text, headers=headers, content_type=content_type, charset=charset, **kw)

Prevention

When it happens

Trigger: Calling Response(text='hi', headers={'Content-Type': 'text/plain'}, content_type='text/html') or any combination where headers contain Content-Type and content_type/charset kwargs are non-None while text is set.

Common situations: Building headers from a framework/helper that already injects Content-Type, then also passing content_type; merging a default-headers dict with explicit kwargs.

Related errors


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