Introduction

@visulima/storage

Unified file storage for Node.js, edge runtimes, and modern web frameworks. One library, two surfaces:

  • Files — one-liner API for ad-hoc storage operations. Web-standard body types (Blob, Uint8Array, ReadableStream), a compact set of provider-agnostic methods, bulk-array overloads, lifecycle hooks (onAction / onError / onRetry), ranged downloads, upload progress, async-iterable listings, and a cross-provider transfer() for migrations. Use this when you just need to put files somewhere.
  • BaseStorage adapters (S3Storage, DiskStorage, BunnyStorage, …) — the full upload-server framework. TUS / multipart / REST handlers, lifecycle hooks, validators, image / video / audio transformers, OpenAPI export. Use this when you're hosting an upload service.

Both surfaces wrap the same adapters — swap providers without touching call sites.

Install

npm install @visulima/storage
# or yarn add / pnpm add

Quick start with Files

import { Files } from "@visulima/storage";
import { S3Storage } from "@visulima/storage/provider/aws";

const files = new Files({
    adapter: new S3Storage({ bucket: "uploads", region: "us-east-1" }),
    defaults: { timeout: 30_000, retries: { maxRetries: 5 } },
    hooks: {
        onAction: ({ type, key, durationMs }) => log.info(type, key, durationMs),
        onRetry: ({ type, key, attempt }) => log.warn(type, key, "retry", attempt),
    },
});

await files.upload("avatars/abc.png", buffer, { contentType: "image/png" });

const url = await files.url("avatars/abc.png", { expiresIn: 900 });
const { body } = await files.download("avatars/abc.png", { range: { start: 0, end: 1023 } });

// Bulk: upload / download / delete / move accept an array and return a structured result.
const { uploaded, errors } = await files.upload([
    { key: "a.txt", body: "A" },
    { key: "b.txt", body: "B" },
]);

// Walk every object as an async iterable — pages internally.
for await (const file of files.listAll({ prefix: "avatars/" })) {
    log.info(file.key, file.size);
}

For ephemeral environments and tests, an in-memory adapter ships out of the box:

import MemoryStorage from "@visulima/storage/provider/memory";

const files = new Files({ adapter: new MemoryStorage({ initial: { "k.txt": "x" } }) });

Swap the adapter to change provider — everything else stays the same:

import { Files, DiskStorage } from "@visulima/storage";
import { BunnyStorage } from "@visulima/storage/provider/bunny";

new Files({ adapter: new DiskStorage({ directory: "./uploads" }) });
new Files({ adapter: new BunnyStorage({ zone, accessKey, region: "de" }) });

The Files facade accepts any web-standard body (string, Buffer, Uint8Array, ArrayBuffer, Blob, ReadableStream, or a Node Readable) and returns provider-agnostic FileObject results.

→ See the Files facade reference for the full method surface.

Building an upload server?

If you're handling client uploads from a browser — large files, resumable transfers, form posts — use the adapter directly with one of the HTTP handlers. The Files facade is a one-shot put; upload servers need streaming, locking, and partial-write protocols.

Node.js / Express

import { DiskStorage } from "@visulima/storage";
import { Multipart, Rest } from "@visulima/storage/handler/http/node";

const storage = new DiskStorage({ directory: "./uploads" });

const multipart = new Multipart({ storage });
app.use("/upload", multipart.handle, (req, res) => {
    res.json(req.body); // handler writes the stored file metadata to req.body
});

const rest = new Rest({ storage });
app.use("/files", rest.handle);

Hono / Cloudflare Workers / Bun / Deno

import { Hono } from "hono";
import { DiskStorage } from "@visulima/storage";
import { createStorageHandler } from "@visulima/storage/handler/http/hono";

const app = new Hono();
const storage = new DiskStorage({ directory: "./uploads" });

createStorageHandler(app, { path: "/files", storage, type: "multipart" });
createStorageHandler(app, { path: "/files-tus", storage, type: "tus" });

Three handlers cover the common upload patterns:

  • Multipartmultipart/form-data from HTML forms.
  • REST — direct binary POST / PUT. Optional client-side chunking for large files.
  • TUS — resumable uploads (tus.io) for unreliable networks and very large files.

→ See TUS handler, Chunked uploads, Authenticated uploads.

Choosing your surface

You want to…Use
Save a Blob / Buffer to S3 and get a signed URLFiles
Bulk-upload / bulk-delete a list of keysFiles (array overload on every method)
Migrate every object from one provider to anothertransfer(source, dest) on Files
Walk every object in a bucket as an async iterablefiles.listAll({ prefix })
Observe operation counts, durations, retries from the facadeFiles with hooks: { onAction, onRetry }
Drop in a file picker / "import from cloud" flowFiles with a consumer provider adapter
Expose ad-hoc storage to an AI agent or LLM toolFiles + @visulima/storage/ai/* subpaths
Build a TUS / multipart / chunked upload endpointadapter + handler
Transform images / video / audio on the flyadapter + transformer
Hook adapter-level onCreate / onComplete / onDeleteadapter
Export OpenAPI for an upload endpointadapter + OpenAPI export

Providers

Object storage (service credentials, presigned URLs, S3-style):

  • AWS S3 (S3Storage, AWSLightStorage)
  • Azure Blob (AzureStorage)
  • Google Cloud Storage (GcsStorage)
  • Vercel Blob (VercelBlobStorage)
  • Netlify Blobs (NetlifyBlobStorage)
  • Local filesystem (DiskStorage, DiskStorageWithChecksum)
  • In-memory (MemoryStorage) — for tests and ephemeral environments

S3-compatible (branded client configs under provider/aws/s3/clients): Cloudflare R2, DigitalOcean Spaces, MinIO, Hetzner, Storj, Backblaze B2, Tigris, Wasabi, Akamai.

Consumer providers (user OAuth or service-credential, all peer-dep gated):

  • Dropbox, Google Drive, Microsoft OneDrive, Box
  • Supabase Storage, UploadThing, Bunny Storage

→ See Storage services for the full capability matrix per provider.

What you get beyond Files

The Files facade is intentionally small. Features below live on the adapter and the framework around it:

  • Upload protocols — multipart-form, raw REST, chunked REST, TUS resumable.
  • HTTP handlers — Node, Fetch, Hono, Next.js, SolidStart, Nuxt, Bun, Deno, Cloudflare, Edge.
  • Image / video / audio transformers — Sharp + Mediabunny, URL-query or programmatic, with caching.
  • Retry + backoff — exponential retry baked into every adapter, plus custom shouldRetry.
  • Observability — built-in metrics and OpenTelemetry spans.
  • Lifecycle hooksonCreate, onUpdate, onComplete, onDelete, onError.
  • Batch operationsdeleteBatch, copyBatch, moveBatch with per-item error capture.
  • Validators, locking, expiration, checksums — content / size / MIME limits, lock tokens, automatic purge, MD5/SHA1/SHA256.
  • OpenAPI export — generate a schema for your upload endpoint.
  • @visulima/storage-client — React / Vue / Solid / Svelte hooks with progress, drag-drop, paste, and abort.
  • AI-tool wrappers — Vercel AI SDK, OpenAI Responses / Agents, Claude Agent SDK, TanStack AI.

Next steps

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues