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-sdkpnpm add files-sdkyarn add files-sdkbun add files-sdknub add files-sdkaube add files-sdkFor 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-presignerpnpm add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigneryarn add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presignerbun add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presignernub add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigneraube add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presignerWith 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:
ReadableStreambodies are buffered in memory before a single PUT (a lone PUT needs aContent-Length, and single-request uploads cap at 5 GB).multipartand resumable (control) uploads throw instead of engaging the S3 multipart API.- Bulk deletes fan out as per-key
delete()calls instead of batchedDeleteObjectsrequests. 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;maxSizethrows 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
onProgressreporting falls back to the SDK’s generic reporting. files.rawis the aws4fetchAwsClient, not anS3Client.
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
bucketstring
MinIO bucket name. The adapter scopes all operations to it.
stringendpointstring
MinIO server URL, e.g. `http://localhost:9000`. Include the scheme — `http://` for local dev, `https://` in production.
stringaccessKeyId?string
Static credentials. Falls back to `MINIO_ACCESS_KEY_ID`; required if that env var isn't set.
stringsecretAccessKey?string
Static credentials. Falls back to `MINIO_SECRET_ACCESS_KEY`; required if that env var isn't set.
stringregion?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.
stringforcePathStyle?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.
booleanpublicBaseUrl?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).
stringdefaultUrlExpiresIn?number
Default expiry, in seconds, for the presigned URLs returned by `url()` when `publicBaseUrl` is not set. Defaults to 3600 (1 hour).
numberclient?"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.
"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.
(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). |