boto/boto3 · error · ValueError

Filename must be a string or a path-like object

Error message

Filename must be a string or a path-like object

What it means

Raised by `S3Transfer.upload_file` after a `PathLike`-to-string conversion attempt: the `filename` argument must ultimately be a `str`. boto3 accepts `os.PathLike` objects (it calls `os.fspath` on them) but rejects anything else (None, int, bytes, an already-open file object). To stream from an open file handle, use `upload_fileobj` instead.

Solutions

  1. Pass a real filesystem path as a string or pathlib.Path: `client.upload_file('/tmp/f', bucket, key)`.
  2. If you have an open file object, switch to `client.upload_fileobj(fileobj, bucket, key)`.
  3. Guard against None: `if filename: client.upload_file(filename, ...)`.
  4. Coerce pathlib Paths explicitly if unsure: `str(pathlib.Path(...))`.

Example fix

# before
with open('/tmp/f', 'rb') as f:
    client.upload_file(f, 'bucket', 'key')  # f is a file object, not a path

# after
client.upload_file('/tmp/f', 'bucket', 'key')
# or, for an open handle:
with open('/tmp/f', 'rb') as f:
    client.upload_fileobj(f, 'bucket', 'key')
Defensive patterns

Strategy: type-guard

Validate before calling

from pathlib import Path
import os

def upload_path(p):
    if isinstance(p, os.PathLike):
        p = os.fspath(p)
    if not isinstance(p, str):
        raise TypeError('filename must be a str or os.PathLike')
    client.upload_file(p, bucket, key)

Type guard

import os
def is_path_or_str(p) -> bool:
    return isinstance(p, (str, os.PathLike))

Try / catch

try:
    client.upload_file(filename, bucket, key)
except ValueError as e:
    if 'Filename must be a string' in str(e):
        client.upload_file(str(filename), bucket, key)  # or use upload_fileobj

Prevention

When it happens

Trigger: Calling `client.upload_file(None, bucket, key)`, `upload_file(b'data', ...)`, `upload_file(open(path), ...)` (passing a file object), or `upload_file(123, ...)`.

Common situations: Passing an open file handle to `upload_file` (meant for `upload_fileobj`); a filename variable that resolved to None due to a missing user input or a failed `tempfile` call; building the path with bytes on a system where `os.fspath` does not yield `str`.

Related errors


AI-assisted analysis of boto/boto3@6e10b029c1 (2026-08-11). Data as JSON: /api/errors/d50d8348d26aef2d. Report an issue: GitHub.

Appendix: source

Thrown at boto3/s3/transfer.py:445

        else:
            self._manager = create_transfer_manager(client, config, osutil)

    def upload_file(
        self, filename, bucket, key, callback=None, extra_args=None
    ):
        """Upload a file to an S3 object.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.upload_file() directly.

        .. seealso::
            :py:meth:`S3.Client.upload_file`
            :py:meth:`S3.Client.upload_fileobj`
        """
        if isinstance(filename, PathLike):
            filename = fspath(filename)
        if not isinstance(filename, str):
            raise ValueError('Filename must be a string or a path-like object')

        subscribers = self._get_subscribers(callback)
        future = self._manager.upload(
            filename, bucket, key, extra_args, subscribers
        )
        try:
            future.result()
        # If a client error was raised, add the backwards compatibility layer
        # that raises a S3UploadFailedError. These specific errors were only
        # ever thrown for upload_parts but now can be thrown for any related
        # client error.
        except ClientError as e:
            raise S3UploadFailedError(
                f"Failed to upload {filename} to {bucket}/{key}: {e}"
            )

    def download_file(
        self, bucket, key, filename, extra_args=None, callback=None

View on GitHub (pinned to 6e10b029c1)