{"id":"894b01cdfc73043c","repo":"aio-libs/aiohttp","slug":"charset-must-not-be-in-content-type-argument","errorCode":null,"errorMessage":"charset must not be in content_type argument","messagePattern":"charset must not be in content_type argument","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_response.py","lineNumber":563,"sourceCode":"        status: int = 200,\n        reason: str | None = None,\n        text: str | None = None,\n        headers: LooseHeaders | None = None,\n        content_type: str | None = None,\n        charset: str | None = None,\n        zlib_executor_size: int = MAX_SYNC_CHUNK_SIZE,\n        zlib_executor: Executor | None = None,\n    ) -> None:\n        if body is not None and text is not None:\n            raise ValueError(\"body and text are not allowed together\")\n\n        if headers is None:\n            real_headers: CIMultiDict[str] = CIMultiDict()\n        else:\n            real_headers = CIMultiDict(headers)\n\n        if content_type is not None and \"charset\" in content_type:\n            raise ValueError(\"charset must not be in content_type argument\")\n\n        if text is not None:\n            if hdrs.CONTENT_TYPE in real_headers:\n                if content_type or charset:\n                    raise ValueError(\n                        \"passing both Content-Type header and \"\n                        \"content_type or charset params \"\n                        \"is forbidden\"\n                    )\n            else:\n                # fast path for filling headers\n                if not isinstance(text, str):\n                    raise TypeError(\"text argument must be str (%r)\" % type(text))\n                if content_type is None:\n                    content_type = \"text/plain\"\n                if charset is None:\n                    charset = \"utf-8\"\n                real_headers[hdrs.CONTENT_TYPE] = content_type + \"; charset=\" + charset","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_response.py#L545-L581","documentation":"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.","triggerScenarios":"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.","commonSituations":"Copy-pasting a full Content-Type header value into the content_type param; building content_type dynamically and accidentally concatenating '; charset=...'.","solutions":["Pass content_type='text/html' and charset='utf-8' separately.","Drop the charset segment entirely and let aiohttp default it.","If you need full control, pass headers={'Content-Type': '...'} and omit content_type/charset."],"exampleFix":"# before\nresp = Response(text=html, content_type='text/html; charset=utf-8')  # raises ValueError\n\n# after\nresp = Response(text=html, content_type='text/html', charset='utf-8')","handlingStrategy":"validation","validationCode":"def clean_content_type(ct: str | None) -> str | None:\n    if ct and 'charset' in ct.lower():\n        # split off charset; let aiohttp re-add it\n        return ct.split(';')[0].strip()\n    return ct","typeGuard":"def content_type_is_bare(ct: str | None) -> bool:\n    return ct is None or (';' not in ct and 'charset' not in ct.lower())","tryCatchPattern":null,"preventionTips":["Pass content_type and charset as separate kwargs.","Never paste a full 'type; charset=...' string into content_type.","Use headers={'Content-Type': full_value} only when you need full manual control."],"tags":["http","headers","content-type","charset","response"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}