FAQ
Common questions about supported providers, bundling and peer dependencies, switching backends, browser uploads, error handling, and the CLI.
Which providers are supported?
40+, behind one API: AWS S3 and the whole S3-compatible long tail (R2, MinIO, RustFS, Backblaze, Wasabi, DigitalOcean Spaces, Scaleway, OVH, Hetzner, Tigris, Storj, Filebase, Akamai, IDrive, Vultr, IBM COS, Oracle, Exoscale, Alibaba, Tencent, Yandex, Archil, Neon), Vercel Blob, Netlify Blobs, Google Cloud Storage, Azure, Supabase, the consumer providers (Dropbox, Box, Google Drive, OneDrive, SharePoint), upload and media services (UploadThing, Cloudinary, Bunny Storage), the BaaS stack (Appwrite, PocketBase, Firebase Storage, Convex), FTP, SFTP, and WebDAV servers, Bun’s native S3 client, and local fs and in-memory adapters for tests. The provider catalog lists every one, and each has its own page under Adapters.
Do I have to install every provider’s SDK?
No. Adapters are subpath exports (files-sdk/s3, files-sdk/r2, …) and each provider SDK is an optional peer dependency, loaded only when you import that adapter’s subpath. Install only the one(s) you wire up - the SDK you don’t import is never bundled. See Installation.
Why am I getting “Cannot find module ‘@aws-sdk/client-s3’”?
Provider SDKs are peer dependencies - install only the ones for the adapters you use. The per-adapter docs list the exact packages.
How do I switch providers?
Swap the adapter you pass to new Files({ adapter }). Every call site below the constructor stays identical - the code that uploads to S3 is the code that uploads to Vercel Blob. See Usage.
What happens when a provider doesn’t support a feature?
Every method normalizes to the same shape, but providers differ in what they offer natively. Where a provider lacks a primitive, the method throws a FilesError rather than silently misbehaving (for example, signedUploadUrl on Bunny Storage). Each adapter’s docs page has a Compatibility section listing its per-method support, and files.raw is the typed escape hatch for anything outside the unified surface.
Can I use it in the browser?
The core Files class and the StoredFile type are isomorphic, but most adapters wrap server-only SDKs. For browser uploads, mint a contract with signedUploadUrl() on the server and have the client fetch directly to the provider - the signedUploadUrl reference walks through the full pattern, and the UI gateway and useFiles bindings package it up for React, Vue, and Svelte.
How are errors handled?
Every method throws a single FilesError with a normalized code - NotFound, Unauthorized, Conflict, ReadOnly, or Provider - and the original provider error attached as cause. See Errors.
Does delete throw on a missing key?
Depends on the provider. S3, R2, Vercel Blob, FTP, Bunny Storage, and the local fs adapter treat a missing key as a successful no-op (other failures, such as a permission error, still throw); strict providers throw a FilesError with code: "NotFound". If you need consistent behavior, gate on await files.exists(key) first. See delete.
Why is contentType application/octet-stream?
When upload can’t infer the type from the input (an ArrayBuffer, typed array, ReadableStream, or a Blob with no type), it defaults to application/octet-stream; strings default to text/plain; charset=utf-8. The key’s extension isn’t used. Pass contentType explicitly to override it, or add the contentType() plugin to detect it from the file’s bytes.
Why does the body come back empty from head / list?
Body accessors on head and list results are lazy - they fetch on call. If you serialize a StoredFile without invoking an accessor, you get the metadata only. That’s the intent; call .arrayBuffer() / .text() / .blob() / .stream() to materialize the bytes. See Troubleshooting for the lazy-body gotcha.
Is there a CLI?
Yes - a files binary with JSON-by-default output, stdin/stdout streaming, and a built-in MCP server, with the same semantics as the SDK. See the CLI docs.