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
- Pick ONE source: either set Content-Type in headers, or use content_type+charset kwargs — not both.
- Strip Content-Type from the headers dict before passing it when you intend to use content_type.
- 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
- Choose exactly one Content-Type source: headers dict OR content_type/charset kwargs.
- Strip Content-Type from shared/default headers before adding content_type.
- Let text= infer Content-Type when possible.
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
- Setting charset for application/octet-stream doesn't make se
- charset must not be in content_type argument
- Got more than {limit} bytes when reading: {line!r}.
- Unsupported type for last_modified: {type(value).__name__}
- Unsupported etag type: {type(value)}. etag must be str, ETag
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/e8215467631e4dde.json.
Report an issue: GitHub.