files-sdk: one TypeScript storage API across S3, GCS, Azure and Vercel Blob
A unified storage SDK for object and blob backends. One small, honest API. Web-standards I/O.
At a glance
- What is it?
- files-sdk wraps object and blob backends behind a single upload/download/head/exists surface, with native clients kept at files.raw. It is a reasonable fit for Node services that must not care which bucket they talk to, and a poor fit for anyone who needs provider-specific features exposed as first-class methods.
- Who is it for?
- Adopt files-sdk if your application code should not know whether it is writing to R2, GCS or Azure, and if you are willing to install each provider's native SDK as an optional peer dependency. Do not adopt it if you need a provider feature that the unified surface does not model, or if you expect the adapters to be tested against real buckets on every pull request, because the live suites are skipped by default and run only through a manual workflow.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem files-sdk targets: provider-shaped code in application layers
Storage code tends to leak. A service starts on S3, someone adds a signed URL helper from @aws-sdk/s3-request-presigner, a second environment uses Vercel Blob, and now the upload path has two branches and two sets of types. files-sdk exists to collapse that. The README describes it as "A unified storage SDK for object and blob backends" with "One small, honest API," and the operation list backs that up: upload, download, head, exists, delete, copy, move, list/listAll, url and signedUploadUrl, plus a key-scoped file(key) handle.
The audience is narrower than the tagline suggests. This is a TypeScript and Node library, published to npm as files-sdk. It suits backend services, serverless functions and tooling where the same object-store logic has to run against more than one backend, or where the backend is chosen at deploy time rather than compile time. It does not suit browser-only code, and it does not suit anyone who wants the SDK to abstract away provider semantics entirely. The README is explicit that the escape hatch exists precisely because the unified surface will not cover everything.
How the adapter model works and where the native client lives
The design is a thin facade over each provider's own client. You construct a Files instance with an adapter, and every method on that instance dispatches to the adapter. The quick start in the README shows the shape: import Files from files-sdk, import s3 from files-sdk/s3, pass s3({ bucket: "uploads" }) as the adapter, then call upload, download and exists on the resulting object.
Two details matter more than the method list. First, each adapter is a separate entry point, so bundlers only pull in what you import; the README calls the package tree-shakeable for this reason. Second, every adapter exposes its provider client at files.raw. That is the honest part of the design. Rather than pretending the unified API covers every provider feature, the library hands you the underlying client when you need something it does not model. The cost is that code reaching through files.raw is no longer portable, which is the trade-off the README accepts rather than hides.
File handles are the third piece. files.file(key) returns an object scoped to one key, with upload, exists, head, url and delete on it. The README states these handles are "a thin layer over the same adapter methods, so adapters do not need to implement anything extra." That is a useful constraint: it means handles cannot drift from the base API, because they add no behaviour of their own.
I/O types are web standards: Blob, File, ReadableStream, Uint8Array, ArrayBuffer or string. No provider-specific body types appear in the signatures. One semantic worth noting: exists returns false only when the provider reports NotFound. Authentication, permission and transport failures still throw, so a false result is a statement about the object, not about the request.
Installing files-sdk and uploading a first object
The package installs from npm. Provider SDKs are optional peer dependencies, so you install files-sdk plus only the native clients for the backends you actually use. The README gives the S3 line as:
npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presignerThe same pattern applies to the other adapters, and the README lists the exact package sets for Google Cloud Storage, Azure Blob Storage and Vercel Blob. If you import an adapter without its peer installed, Node throws ERR_MODULE_NOT_FOUND naming the missing package. That failure is loud and immediate, which is the behaviour you want.
A first upload looks like this, taken from the README quick start:
import { Files } from "files-sdk";
import { s3 } from "files-sdk/s3";
const files = new Files({
adapter: s3({ bucket: "uploads" }),
});
await files.upload("avatars/abc.png", file, { contentType: "image/png" });
const got = await files.download("avatars/abc.png");
const exists = await files.exists("avatars/abc.png");After this runs against a real bucket, download returns the object and exists returns true. Swapping the adapter import to files-sdk/r2, files-sdk/gcs or files-sdk/azure is the only change the README says is needed; the rest of the code stays the same. Credentials are not passed in the constructor here. The README states that provider credentials continue to come from each adapter's environment-variable conventions, so the S3 adapter expects the usual AWS environment variables to be set.
For tools that pick a provider at runtime, there is a lazy loader entry point:
import { loadFiles } from "files-sdk/loader";
const { files, provider } = await loadFiles({
bucket: "uploads",
retries: 3,
timeout: 10_000,
});Set FILES_SDK_PROVIDER to a name such as r2, or pass provider directly. The README notes that only the selected adapter is imported, which keeps startup cost tied to one provider rather than all of them.
The escape hatch is also the boundary of the abstraction
The most consequential limitation is structural, not a bug. A unified API can only be as rich as the intersection of the providers it covers. Anything outside that intersection lives at files.raw, and code written against files.raw is provider-specific by definition. If your application depends heavily on provider features that the unified surface does not model, files-sdk adds a layer without removing the branching it was meant to remove.
The second limitation concerns testing confidence. Most tests mock the provider. The README states that a few *.live.test.ts suites exercise a real backend, that they are skipped by default, and that they only run with LIVE_TESTS=1. Suites needing credentials also skip when those environment variables are absent. In CI, live tests never run on pull_request from forks, and they run only when a maintainer triggers the Live tests workflow manually, typically against main after a merge. That is a defensible choice for keeping the default test run fast, offline and credential-free, but it means a pull request can merge without any adapter having touched a real bucket. If you are evaluating this library, the mock-heavy test story is the thing to weigh, not the method list.
There is also a runtime-selection trade-off. The loader imports only the chosen adapter, which is good for cold starts, but it means provider choice moves from a type-checked import to a string in FILES_SDK_PROVIDER or a provider option. A typo is a runtime failure rather than a compile error.
files-sdk compared with the provider SDKs it wraps
The obvious alternative is to use each provider's native SDK directly: @aws-sdk/client-s3 for S3 and S3-compatible stores, @google-cloud/storage, @azure/storage-blob, @vercel/blob. Those packages expose the full feature set of their backend, including lifecycle rules, multipart configuration, storage classes and provider-specific metadata semantics. They also tie your code to one backend. If your project will only ever talk to S3, the native client gives you more surface with no abstraction tax, and files-sdk's value proposition largely disappears.
The difference in approach is therefore about where the branching lives. With native SDKs, the branch is in your application code, and it is explicit and fully typed. With files-sdk, the branch is inside the adapter, and your application code sees one interface. The library does not hide the native clients; it relocates them behind files.raw. That makes the choice less about capability and more about how much provider-specific code you are willing to keep in the layers above storage.
A second comparison point is the AI tooling subpaths. The README describes wrappers that expose a configured Files instance as tools for the Vercel AI SDK (files-sdk/ai-sdk), OpenAI's Responses API and Agents SDK (files-sdk/openai), and Anthropic's Claude Agent SDK (files-sdk/claude). Those share the same file operations and approval-gating defaults. If your goal is to let a model read and optionally mutate a bucket, that is a different starting point than wiring a native client into a tool definition by hand, and it is the part of the package that has no direct equivalent in the provider SDKs.
Maintenance, releases and what the MIT licence leaves you to decide
The repository is not archived, and the last push was on 2026-09-14. Recent releases are close together: files-sdk@2.3.1 on 2026-09-05, files-sdk@2.4.0 on 2026-09-07 and files-sdk@2.4.1 on 2026-09-09. The monorepo is a Bun workspace with Turborepo, Changesets for versioning and publishing, and Ultracite for lint and format, with husky hooks installed through the prepare script. That toolchain tells you what an upgrade costs you: version bumps flow through Changesets, and the release script builds with turbo before publishing.
For consumers, the practical upgrade question is peer dependencies. Because each provider SDK is an optional peer, a major bump in @aws-sdk/client-s3 or @azure/storage-blob is your upgrade to schedule, not the library's. The README does not document a rollback procedure, and it does not state a support window for older major versions, so pinning files-sdk and testing adapter upgrades in your own environment is the only route the documentation supports.
The licence is MIT, which is permissive and imposes no source-disclosure requirement on your application. The root package.json in the monorepo declares ISC for the private workspace package, but the published package is MIT per the README and the LICENSE file. That distinction matters if you are running a licence scan across the repository rather than against the published artifact: the monorepo root is not the package you install. This is a description of what the files say, not legal advice; check the published package metadata for the terms that bind you.
Editorial conclusion
Adopt files-sdk if your application code should not know whether it is writing to R2, GCS or Azure, and if you are willing to install each provider's native SDK as an optional peer dependency. Do not adopt it if you need a provider feature that the unified surface does not model, or if you expect the adapters to be tested against real buckets on every pull request, because the live suites are skipped by default and run only through a manual workflow. Before committing, verify two things in your own environment: that the adapter you need exists on files-sdk.dev, and that importing it without its peer installed produces the ERR_MODULE_NOT_FOUND error the README describes rather than a silent fallback.
Frequently asked questions
How do I install files-sdk and which packages do I need for S3?
Install files-sdk from npm together with the native clients for the backends you use. For S3 the README gives npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner. Each provider SDK is an optional peer dependency, so you only install the ones your adapters import.
Can I use files-sdk with a provider that is not S3, such as Google Cloud Storage or Azure Blob Storage?
Yes. The README lists adapters for Google Cloud Storage, Azure Blob Storage, Vercel Blob and S3-compatible stores, each installed with its own native packages. Swapping the adapter import is the only change the README says is needed; the rest of your code stays the same.
What happens if I import a files-sdk adapter without installing its peer dependency?
Node throws ERR_MODULE_NOT_FOUND naming the missing package. The README documents this behaviour for adapters imported without their peer installed.
Does files-sdk run its tests against a real storage bucket?
Most tests mock the provider. A few *.live.test.ts suites exercise a real backend, but they are skipped by default and only run with LIVE_TESTS=1, and suites needing credentials skip when those environment variables are absent. In CI they run only when a maintainer triggers the Live tests workflow manually.
Community notes