Skip to content
Files SDK
Esc
navigateopen⌘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

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

Could not generate a type table for RustfsAdapterOptions: Cannot read properties of undefined (reading 'ESNext')

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?