Write once. Store anywhere.
One small, honest API for S3, R2, Vercel Blob, GCS, Azure and 43 more storage backends. Web-standard I/O, one error type, and the native client whenever you need it.
- Adapters
- 48
- Plugins
- 16
- Methods
- 11
- Error type
- 1
import { Files } from "files-sdk";import { s3 } from "files-sdk/s3"; const files = new Files({ adapter: s3({ bucket: "uploads" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });import { Files } from "files-sdk";import { r2 } from "files-sdk/r2"; const files = new Files({ adapter: r2({ bucket: "uploads" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });import { Files } from "files-sdk";import { vercelBlob } from "files-sdk/vercel-blob"; const files = new Files({ adapter: vercelBlob({ access: "private" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });import { Files } from "files-sdk";import { gcs } from "files-sdk/gcs"; const files = new Files({ adapter: gcs({ bucket: "uploads" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });import { Files } from "files-sdk";import { azure } from "files-sdk/azure"; const files = new Files({ adapter: azure({ container: "uploads" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });import { Files } from "files-sdk";import { supabase } from "files-sdk/supabase"; const files = new Files({ adapter: supabase({ bucket: "uploads" }),}); await files.upload("avatar.png", file);const url = await files.url("avatar.png", { expiresIn: 600 });Swap the adapter. Keep every call site.
48 backends. One shape.
Each adapter is its own subpath export, and each provider's SDK is an optional peer. The ones you never import are never bundled.
Recommended adapters
These providers help maintain Files SDK by sponsoring its development.
Other adapters
- Akamai Cloud Object Storageakamai
- Alibaba Cloud OSSalibaba
- Appwriteappwrite
- Archilarchil
- Azure Blob Storageazure
- Backblaze B2backblaze-b2
- Boxbox
- Bun S3bun-s3
- Bunny Storagebunny-storage
- Cloudinarycloudinary
- Convexconvex
- DigitalOcean Spacesdigitalocean-spaces
- Dropboxdropbox
- Exoscale Object Storageexoscale
- Filebasefilebase
- Filesystemfs
- Firebase Storagefirebase-storage
- FTPftp
- Google Cloud Storagegcs
- Google Drivegoogle-drive
- Hetzner Object Storagehetzner
- IBM Cloud Object Storageibm-cos
- iDrive e2idrive-e2
- In-Memorymemory
- MinIOminio
- Neonneon
- Netlify Blobsnetlify-blobs
- OneDriveonedrive
- Oracle Cloud Object Storageoracle-cloud
- OVHcloud Object Storageovhcloud
- PocketBasepocketbase
- RustFSrustfs
- S3s3
- S3 (fetch)s3-fetch
- Scaleway Object Storagescaleway
- SFTPsftp
- SharePointsharepoint
- Storjstorj
- Supabase Storagesupabase
- Tencent Cloud Object Storagetencent
- Tigristigris
- UploadThinguploadthing
- Vercel Blobvercel-blob
- Vultr Object Storagevultr
- Wasabiwasabi
- WebDAVwebdav
- Yandex Object Storageyandex
The same verbs on the server, in the browser, in the terminal, and in your agents.
files-sdk
Core SDK
Eleven methods that behave the same on all 48 adapters, with bulk calls, multipart, byte ranges, retries, timeouts and conditional writes.
- upload
- download
- head
- exists
- delete
- copy
- move
- list
- search
- url
- signedUploadUrl
files-sdk/encryption
Plugins
16 ordered layers for encryption, versioning, soft delete, caching, tiering, failover and more.
Explore Pluginsfiles-sdk/react
UI
useFiles for React, Vue and Svelte over a deny-by-default gateway, plus 12 shadcn components.
Explore UIfiles --provider r2 list
CLI and MCP
One files binary with JSON output, stdin and stdout streaming, and a built-in MCP server.
Explore CLI and MCPfiles-sdk/ai-sdk
AI tools
Hand a bucket to the AI SDK, OpenAI or Claude as ready-made tools, with writes gated behind approval.
Explore AI toolsLayer on what your storage is missing.
Plugins wrap every operation in an ordered pipeline to transform it, veto it, or watch it go by. Writes pass through them in order, and reads unwind in reverse on their own.
How plugins composeimport { Files } from "files-sdk";import { r2 } from "files-sdk/r2";import { compression } from "files-sdk/compression";import { encryption } from "files-sdk/encryption";import { validation } from "files-sdk/validation"; const files = new Files({ adapter: r2({ bucket: "uploads" }), plugins: [ validation({ allowedTypes: ["image/*"] }), compression(), encryption(key), ],});validation() only guards writes, so reads skip it.- encryption()
- Envelope AES-256-GCM encryption at rest.
- compression()
- gzip and deflate bodies through Compression Streams.
- validation()
- Fail-closed size, MIME and key guards on every write.
- versioning()
- Snapshots on overwrite and delete, with restore.
- contentType()
- Magic-byte sniffing that sets and guards Content-Type.
- dedup()
- Content-addressed storage that shares identical blobs.
- usage()
- Meters operations and bytes up and down, optionally grouped.
- tracing()
- An OpenTelemetry span for every operation.
- softDelete()
- A recycle bin with trashed, restore and purge.
- audit()
- A durable who, what and when log of every mutation.
- cache()
- LRU or KV caching for head, url and small downloads.
- tiering()
- Routes hot and cold keys to different backends.
- failover()
- Retries provider errors against replica backends.
- signedUrlPolicy()
- Safe defaults for url and signedUploadUrl.
- zip()
- Bundles stored objects into ZIP archives and back.
- events()
- Normalized bucket notifications, routed by type and key glob.
An API that tells you the truth.
Files SDK only promises what every backend can keep, and it says so up front when one can't.
- A common subset
- Not the lowest common denominator: the core is what every adapter does cleanly, and the rest is one files.raw away.
- Fail loud
- If an adapter can't honor an option, the call throws before any request is made. Nothing degrades silently.
- Web-standard I/O
- Bodies are Blob, File, ReadableStream, bytes or strings. No provider types leak into your code.
- One error type
- Every failure is a FilesError with a small set of codes, and the original error is kept on cause.
import { Files, FilesError } from "files-sdk";import { r2 } from "files-sdk/r2"; const files = new Files({ adapter: r2({ bucket: "uploads" }) }); // Ask what the adapter can do before you call it.if (files.capabilities.rangeRead) { await files.download("clip.mp4", { range: { start: 0, end: 1023 } });} try { await files.head("missing.txt");} catch (error) { if (error instanceof FilesError && error.code === "NotFound") { // The same code on every adapter. The original is on error.cause. }} files.raw; // the native client, typed for this adapterSupported by our sponsors.
Files SDK is a community-driven project, powered by the generosity of these companies. Become a sponsor.
Ship the storage layer once.
Open source, MIT licensed, and built on web standards.
Pick an adapter today and change your mind whenever you like.