{"id":"1c2dd7d5de19f6ed","repo":"aio-libs/aiohttp","slug":"filename-must-be-an-instance-of-str-got-s","errorCode":null,"errorMessage":"filename must be an instance of str. Got: %s","messagePattern":"filename must be an instance of str\\. Got: (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"aiohttp/formdata.py","lineNumber":63,"sourceCode":"    @property\n    def is_multipart(self) -> bool:\n        return self._is_multipart\n\n    def add_field(\n        self,\n        name: str,\n        value: Any,\n        *,\n        content_type: str | None = None,\n        filename: str | None = None,\n    ) -> None:\n        if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)):\n            self._is_multipart = True\n\n        _safe_header(name)\n        type_options: MultiDict[str] = MultiDict({\"name\": name})\n        if filename is not None and not isinstance(filename, str):\n            raise TypeError(\"filename must be an instance of str. Got: %s\" % filename)\n        if filename is None and isinstance(value, io.IOBase):\n            filename = guess_filename(value, name)\n        if filename is not None:\n            _safe_header(filename)\n            type_options[\"filename\"] = filename\n            self._is_multipart = True\n\n        headers = {}\n        if content_type is not None:\n            if not isinstance(content_type, str):\n                raise TypeError(\n                    \"content_type must be an instance of str. Got: %s\" % content_type\n                )\n            _safe_header(content_type)\n            headers[hdrs.CONTENT_TYPE] = content_type\n            self._is_multipart = True\n\n        self._fields.append((type_options, headers, value))","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/formdata.py#L45-L81","documentation":"Raised by FormData.add_field when the filename argument is provided but is not a str. The type annotation declares filename as str | None, and aiohttp uses the filename verbatim to build the Content-Disposition header, so a non-string (e.g. pathlib.Path, int) is rejected with a TypeError before any serialization happens. The check runs after _safe_header(name) so the field name is already validated.","triggerScenarios":"Calling form.add_field('file', fp, filename=Path('x.txt')) or FormData(fields=..., filename=123). Also triggered when an (name, file) pair is built programmatically and a Path object is passed where a filename string is expected.","commonSituations":"Developers coming from requests where Path objects are accepted; passing os.path.join results wrapped in Path; integer filename IDs from DB rows passed as filename.","solutions":["Coerce the filename to str before passing: filename=str(path).","Use Path.name / os.path.basename to get a plain string leaf name.","If using a pathlib.Path, pass str(path.name)."],"exampleFix":"// before\nform.add_field('upload', fp, filename=Path('/data/report.pdf'))\n// after\nform.add_field('upload', fp, filename=str(Path('/data/report.pdf').name))","handlingStrategy":"type-guard","validationCode":"def coerce_filename(fn):\n    if fn is None:\n        return None\n    if isinstance(fn, str):\n        return fn\n    if isinstance(fn, os.PathLike):\n        return os.path.basename(os.fspath(fn))\n    return str(fn)","typeGuard":"def is_str_filename(fn) -> bool:\n    return fn is None or isinstance(fn, str)","tryCatchPattern":null,"preventionTips":["Always wrap Path objects with str(path.name) before passing as filename.","Keep filename parameters as plain str or None at API boundaries.","Run mypy/pyright with strict optional checks on FormData call sites."],"tags":["formdata","type-error","validation","multipart"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}