aio-libs/aiohttp · error · TypeError

filename must be an instance of str. Got: %s

Error message

filename must be an instance of str. Got: %s

What it means

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.

Source

Thrown at aiohttp/formdata.py:63

    @property
    def is_multipart(self) -> bool:
        return self._is_multipart

    def add_field(
        self,
        name: str,
        value: Any,
        *,
        content_type: str | None = None,
        filename: str | None = None,
    ) -> None:
        if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)):
            self._is_multipart = True

        _safe_header(name)
        type_options: MultiDict[str] = MultiDict({"name": name})
        if filename is not None and not isinstance(filename, str):
            raise TypeError("filename must be an instance of str. Got: %s" % filename)
        if filename is None and isinstance(value, io.IOBase):
            filename = guess_filename(value, name)
        if filename is not None:
            _safe_header(filename)
            type_options["filename"] = filename
            self._is_multipart = True

        headers = {}
        if content_type is not None:
            if not isinstance(content_type, str):
                raise TypeError(
                    "content_type must be an instance of str. Got: %s" % content_type
                )
            _safe_header(content_type)
            headers[hdrs.CONTENT_TYPE] = content_type
            self._is_multipart = True

        self._fields.append((type_options, headers, value))

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Coerce the filename to str before passing: filename=str(path).
  2. Use Path.name / os.path.basename to get a plain string leaf name.
  3. If using a pathlib.Path, pass str(path.name).

Example fix

// before
form.add_field('upload', fp, filename=Path('/data/report.pdf'))
// after
form.add_field('upload', fp, filename=str(Path('/data/report.pdf').name))
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_filename(fn):
    if fn is None:
        return None
    if isinstance(fn, str):
        return fn
    if isinstance(fn, os.PathLike):
        return os.path.basename(os.fspath(fn))
    return str(fn)

Type guard

def is_str_filename(fn) -> bool:
    return fn is None or isinstance(fn, str)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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