Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
v3.1.1 · Read the changelog

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.

Get started
Adapters
48
Plugins
16
Methods
11
Error type
1
storage.tsadapter: S3
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 });
storage.tsadapter: R2
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 });
storage.tsadapter: Vercel Blob
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 });
storage.tsadapter: GCS
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 });
storage.tsadapter: Azure
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 });
storage.tsadapter: Supabase
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.

Layer 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 compose
storage.ts
import { 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), ],});
Uploadcheckcompressencryptwritevalidation()compression()encryption()r2()Downloadskipdecompressdecryptread
The array order is the pipeline order. Writes run through it left to right, and reads unwind it right to left, so you never hand-manage the symmetry. 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.
honest.ts
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 adapter

Supported 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.

Read the docs