aio-libs/aiohttp · error · ValueError

passing both Content-Type header and content_type or…

Error message

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

What it means

Raised by the Response constructor in the `text is not None` branch when the caller supplies both a Content-Type header (in `headers=`) AND a `content_type=` or `charset=` kwarg. The two channels for setting Content-Type conflict; aiohttp refuses to silently pick one and produce an inconsistent header.

Solutions

  1. Choose ONE channel: either set Content-Type via `headers=` OR via `content_type=`/`charset=` kwargs, never both.
  2. If middleware sets defaults, have it omit Content-Type when the handler will pass content_type.
  3. Strip Content-Type from the headers dict before constructing the Response if you intend to use the kwargs.
  4. Build the headers dict without Content-Type and let the kwargs drive it.

Example fix

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

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

Strategy: validation

Validate before calling

def build_response(text=None, body=None, headers=None, content_type=None, charset=None):
    h = CIMultiDict(headers or {})
    if 'Content-Type' in h:
        assert content_type is None and charset is None, \
            'do not pass content_type/charset with a Content-Type header'
    return Response(text=text, body=body, headers=h, content_type=content_type, charset=charset)

Type guard

from multidict import CIMultiDict

def headers_have_content_type(headers) -> bool:
    return any(k.lower() == 'content-type' for k in (headers or {}).keys())

Prevention

When it happens

Trigger: Calling `Response(text='hi', headers={'Content-Type': 'text/html'}, content_type='text/plain')`, or passing charset alongside a headers dict that already carries Content-Type. Common when wrapping responses with default headers and then overriding per-call.

Common situations: Middleware that injects a default Content-Type header into all responses; then a handler also passes content_type. Helpers that merge a base headers dict with caller-supplied content_type. Copy-pasting a headers dict that contains Content-Type and then adding the kwarg 'just in case'.

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/e8215467631e4dde. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)