aio-libs/aiohttp · error · TypeError

filename must be an instance of str. Got

Error message

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

What it means

FormData.add_field raises TypeError when the filename keyword argument is not None and not a str instance. The filename is used in the Content-Disposition header of the multipart part, so it must be a plain string. This is a type validation guard, not a runtime I/O error.

Solutions

  1. Convert the filename to str: form.add_field('file', data, filename=str(my_path))
  2. If using pathlib.Path, call .name then str(): filename=str(path.name)
  3. Ensure the filename comes from a string source, not bytes or Path

Example fix

# before
from pathlib import Path
form.add_field('upload', f, filename=Path('/tmp/photo.jpg'))

# after
form.add_field('upload', f, filename=str(Path('/tmp/photo.jpg').name))
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_filename(fn):
    if fn is None:
        return None
    if isinstance(fn, bytes):
        return fn.decode('utf-8')
    if not isinstance(fn, str):
        return str(fn)
    return fn

form.add_field('file', data, filename=coerce_filename(my_filename))

Type guard

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

Try / catch

try:
    form.add_field('file', data, filename=fn)
except TypeError as e:
    if 'filename must be an instance of str' in str(e):
        form.add_field('file', data, filename=str(fn))
    raise

Prevention

When it happens

Trigger: Calling form.add_field('file', data, filename=Path('photo.jpg')) or passing an int, bytes, or other non-str value as the filename argument. Common when using pathlib.Path objects or os.path.join results without converting to str.

Common situations: Passing a pathlib.Path object directly as filename; using os.path.basename which may return bytes on some systems; passing an integer or float from a computed filename.

Related errors


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

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