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

MinIO

MinIO and other self-hosted S3-compatible servers. 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 { minio } from "files-sdk/minio";

const files = new Files({
  adapter: minio({
    bucket: "uploads",
    endpoint: "http://localhost:9000",
    // accessKeyId / secretAccessKey auto-loaded from
    // MINIO_ACCESS_KEY_ID / MINIO_SECRET_ACCESS_KEY
  }),
});

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.

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: minio({
    bucket: "uploads",
    endpoint: "https://minio.internal:9000",
    client: "fetch",
  }),
});

Prefer this over pointing the generic s3Fetch() at a MinIO server: it is the same engine, but MinIO’s defaults ride along - path-style addressing (MinIO has no per-bucket DNS, so virtual-hosted requests surface as a misleading NoSuchBucket), the us-east-1 signing region, and MinIO 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

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

Typestring
endpointstring

MinIO server URL, e.g. `http://localhost:9000`. Include the scheme — `http://` for local dev, `https://` in production.

Typestring
accessKeyId?string

Static credentials. Falls back to `MINIO_ACCESS_KEY_ID`; required if that env var isn't set.

Typestring
secretAccessKey?string

Static credentials. Falls back to `MINIO_SECRET_ACCESS_KEY`; required if that env var isn't set.

Typestring
region?string

SigV4 region used for signing. Defaults to `us-east-1`. SigV4 requires some region in the signature, but MinIO ignores it for routing — leave the default unless you've configured per-region buckets.

Typestring
forcePathStyle?boolean

Use path-style addressing (`/<bucket>/<key>`) rather than virtual-hosted style. Defaults to `true` for MinIO; flip off only if you've set up per-bucket subdomain routing in front of your server.

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 MinIO. 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 MinIO's defaults — path-style addressing, the `us-east-1` signing region, `MinIO 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 ✅

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?