Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

RustFS

RustFS, the Apache-2.0 Rust object store that runs as a MinIO drop-in. Path-style addressing on by default; region defaulted; errors relabelled; optional @aws-sdk-free fetch client for Workers.

Installation

The @aws-sdk/* packages are only needed for the "aws-sdk" client (the default outside Cloudflare Workers). The lightweight fetch client needs none of them - files-sdk alone is enough:

npm install files-sdk
pnpm add files-sdk
yarn add files-sdk
bun add files-sdk
nub add files-sdk
aube add files-sdk

For the "aws-sdk" client, @aws-sdk/client-s3, @aws-sdk/s3-presigned-post, and @aws-sdk/s3-request-presigner are optional peer dependencies - install alongside the SDK so the adapter’s imports resolve at runtime.

npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
pnpm add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
yarn add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
bun add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
nub add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
aube add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner

With the "aws-sdk" client, upload also needs the optional @aws-sdk/lib-storage package when it uses onProgress, multipart, or a ReadableStream body of unknown length. It’s only loaded then.

Usage

import { Files } from "files-sdk";
import { rustfs } from "files-sdk/rustfs";

const files = new Files({
  adapter: rustfs({
    bucket: "uploads",
    endpoint: "http://localhost:9000",
    // accessKeyId / secretAccessKey auto-loaded from
    // RUSTFS_ACCESS_KEY_ID / RUSTFS_SECRET_ACCESS_KEY, falling back to the
    // RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY names the server itself reads
  }),
});

endpoint is the S3 API port (9000 by default); the RustFS console on 9001 is not an S3 endpoint. The env-var fallback accepts the same RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY names the server reads, so one docker-compose .env configures both sides. Rotate the well-known rustfsadmin defaults before exposing the server beyond localhost.

The "aws-sdk" client is loaded on first use, so files.raw is undefined until any method has run - call one first if you need the underlying S3Client.

Server defaults the adapter mirrors

  • Path-style addressing is on. RustFS only accepts virtual-hosted requests (<bucket>.<host>) once RUSTFS_SERVER_DOMAINS and a wildcard DNS record are configured; pass forcePathStyle: false after that.
  • Signing region defaults to us-east-1, matching the server’s RUSTFS_REGION default. If you changed that variable, pass the same value as region - RustFS rejects signatures for another region.
  • Migrating from MinIO? The two servers share these defaults, so swapping minio() for rustfs() is a rename plus the env-var prefix.

Lightweight fetch client

Pass client: "fetch" to swap the @aws-sdk/* stack for a SigV4-signed fetch engine built on aws4fetch (~2.5 KB gzipped, Web Crypto only). No @aws-sdk/* packages are installed or bundled - ideal for Cloudflare Workers and other edge runtimes where the AWS SDK’s ~500 KB defeats the point of web-standard tooling.

const files = new Files({
  adapter: rustfs({
    bucket: "uploads",
    endpoint: "https://rustfs.internal:9000",
    client: "fetch",
  }),
});

Prefer this over pointing the generic s3Fetch() at a RustFS server: it is the same engine, but RustFS’s defaults ride along - path-style addressing (without RUSTFS_SERVER_DOMAINS, virtual-hosted requests surface as a misleading NoSuchBucket), the us-east-1 signing region, and RustFS error labels.

The fetch client covers upload, download (including ranges), head, exists, delete, list (including delimiter folding), server-side copy, presigned url(), and signedUploadUrl(). Trade-offs against the "aws-sdk" client:

  • ReadableStream bodies are buffered in memory before a single PUT (a lone PUT needs a Content-Length, and single-request uploads cap at 5 GB).
  • multipart and resumable (control) uploads throw instead of engaging the S3 multipart API.
  • Bulk deletes fan out as per-key delete() calls instead of batched DeleteObjects requests. On Workers each call is a subrequest, so chunk large bulk deletes to stay under the per-invocation subrequest cap.
  • signedUploadUrl() returns a presigned PUT; maxSize throws because enforcing it needs a presigned POST policy this engine doesn’t implement.
  • Keys containing . or .. path segments are rejected - URL normalization would silently sign a request for a different key.
  • Byte-level onProgress reporting falls back to the SDK’s generic reporting.
  • files.raw is the aws4fetch AwsClient, not an S3Client.

Default inside Cloudflare Workers

Inside Cloudflare Workers the fetch client is the default. The "aws-sdk" client’s XML parsing needs a DOMParser, which workerd doesn’t provide, so it fails at runtime on the first list or error-body parse - long after construction. Detection uses navigator.userAgent === "Cloudflare-Workers", or the workerd-only WebSocketPair global on compatibility dates where navigator is disabled. Two things keep the "aws-sdk" default even in a Worker: an explicit client: "aws-sdk", and a DOMParser on the global (the usual polyfill workaround), since the SDK works once that is present.

Options

PropType
bucketstring

RustFS bucket name. The adapter scopes all operations to it.

Typestring
endpointstring

RustFS server URL, e.g. `http://localhost:9000` (the S3 API port; the console lives on 9001). Include the scheme — `http://` for local dev, `https://` in production.

Typestring
accessKeyId?string

Static credentials. Falls back to `RUSTFS_ACCESS_KEY_ID`, then to the `RUSTFS_ACCESS_KEY` variable the RustFS server itself reads (so a docker-compose `.env` serves both sides); required if neither is set.

Typestring
secretAccessKey?string

Static credentials. Falls back to `RUSTFS_SECRET_ACCESS_KEY`, then to the `RUSTFS_SECRET_KEY` variable the RustFS server itself reads; required if neither is set.

Typestring
region?string

SigV4 region used for signing. Defaults to `us-east-1`, which is also the server's default (`RUSTFS_REGION`). Set it to match the server if you changed that variable — RustFS rejects signatures for a different region.

Typestring
forcePathStyle?boolean

Use path-style addressing (`/<bucket>/<key>`) rather than virtual-hosted style. Defaults to `true` — RustFS serves path style out of the box and only accepts virtual-hosted requests once `RUSTFS_SERVER_DOMAINS` and a wildcard DNS record are configured.

Typeboolean
publicBaseUrl?string

Origin used to build URLs from `url()`. When set, `url(key)` returns `${publicBaseUrl}/${key}` — appropriate for a public bucket policy or a reverse proxy in front of RustFS. When unset, `url()` falls back to a presigned GetObject (default expiry: 1 hour).

Typestring
defaultUrlExpiresIn?number

Default expiry, in seconds, for the presigned URLs returned by `url()` when `publicBaseUrl` is not set. Defaults to 3600 (1 hour).

Typenumber
client?"aws-sdk" | "fetch"

Which HTTP engine backs the adapter. Defaults to `"aws-sdk"`, except on Cloudflare Workers (detected via `navigator.userAgent === "Cloudflare-Workers"`, or the workerd-only `WebSocketPair` global when `navigator` is disabled), where it defaults to `"fetch"` — the aws-sdk engine's XML parsing needs `DOMParser`, which workerd doesn't provide. A Worker that polyfills `DOMParser` keeps the `"aws-sdk"` default. Note the `"fetch"` engine's narrower surface below before relying on the default in a Worker. - `"aws-sdk"`: `@aws-sdk/client-s3` — the full surface, including multipart/resumable uploads, byte-level upload progress, and batched `deleteMany`. Requires the `@aws-sdk/*` optional peer dependencies. Loaded lazily on first use, so `raw` is `undefined` until a method has run. - `"fetch"`: SigV4-signed `fetch` via `aws4fetch` (~2.5 KB) — no `@aws-sdk/*` install needed, ideal for Workers and other edge runtimes. Covers upload, download (+ ranges), head, exists, delete, list (+ delimiter), server-side copy, presigned `url()`, and `signedUploadUrl()`. Trade-offs: `ReadableStream` bodies are buffered before the single PUT, `multipart`/`control` uploads throw, bulk deletes fan out per-key instead of batching, `signedUploadUrl` rejects `maxSize`, keys with `.`/`..` segments are rejected, and `raw` is an aws4fetch `AwsClient`. Both engines keep RustFS's defaults — path-style addressing, the `us-east-1` signing region, `RustFS error` labels — which is what reaching for the generic `s3Fetch()` instead would drop.

Type"aws-sdk" | "fetch"
fetch?(request: Request) => Promise<Response>

Override the `fetch` implementation used by the `"fetch"` client — for tests, or runtimes that hand out a bound/instrumented fetch. Defaults to `globalThis.fetch`. Ignored by the `"aws-sdk"` client.

Type(request: Request) => Promise<Response>

Compatibility

client: "aws-sdk" (default)

Method Status Notes
upload ✅
download ✅
delete ✅
list ⚠️ The S3 list API returns no per-object Content-Type, so type is inferred from the key’s extension (application/octet-stream when unknown). Use head() for the stored value.
search ✅
head ✅
exists ✅
copy ✅
url ✅
signedUploadUrl ⚠️ maxSize switches to a presigned POST policy. RustFS’s own S3 compatibility notes list POST-object checksum handling as incomplete, so verify maxSize uploads against your server version before relying on them.

client: "fetch"

Method Status Notes
upload ⚠️ Single PUT - ReadableStream bodies are buffered in memory first, and multipart / resumable control uploads throw. Multipart needs the "aws-sdk" client, which runs on Workers only with a DOMParser polyfill.
download ✅
delete ✅ Bulk deletes fan out per key (no batched DeleteObjects).
list ⚠️ The S3 list API returns no per-object Content-Type, so type is inferred from the key’s extension (application/octet-stream when unknown). Use head() for the stored value.
search ✅
head ✅
exists ✅
copy ✅
url ✅
signedUploadUrl ⚠️ PUT URL only - maxSize throws (no presigned POST policy).

Was this page helpful?