Bun S3
AWS S3 (and any S3-compatible bucket) via Bun's native Bun.S3Client instead of @aws-sdk/client-s3 - Bun-only, with no extra peer dependencies.
Installation
This adapter has no extra peer dependencies, but it requires the Bun runtime - it’s built on Bun’s native Bun.S3Client, which Node doesn’t provide. (Outside Bun it throws unless you hand it a Bun.S3Client-shaped client yourself.)
npm install files-sdkpnpm add files-sdkyarn add files-sdkbun add files-sdknub add files-sdkaube add files-sdkUsage
import { Files } from "files-sdk";
import { bunS3 } from "files-sdk/bun-s3";
const files = new Files({
adapter: bunS3({
bucket: "uploads",
region: "us-east-1",
// accessKeyId / secretAccessKey auto-loaded by Bun from
// S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY (or AWS_* equivalents)
}),
});
// Or hand it the singleton Bun.s3 client directly:
new Files({ adapter: bunS3({ client: Bun.s3 }) });
The main differences from files-sdk/s3:
copy()streams bytes through your process, because Bun doesn’t expose a server-sideCopyObjectprimitive.upload()throws onmetadataandcacheControl, becauseBun.S3Client.write()has no equivalent options.signedUploadUrl()throws onmaxSize, because Bun exposes presigned URLs only, not S3 POST policy fields. It also throws oncontentType: Bun’s presigned PUT signs only thehostheader, so it can’t enforce a Content-Type. (TheuseFilesgateway falls back to proxying such uploads through your server.)- Pause/resume via
controlworks within one process only, and there are no provider-native conditional operations.
Reach for files-sdk/s3 on the same bucket when you need any of those.
Options
client?BunS3ClientLike
A pre-configured `Bun.S3Client`-shaped instance — for example the global `Bun.s3`, or one constructed with specific credentials elsewhere in your app. When set, the adapter uses it as-is and rejects any of `bucket`, `region`, `endpoint`, `virtualHostedStyle`, `accessKeyId`, `secretAccessKey`, `sessionToken` at construction (they would be silently ignored otherwise). When unset, the adapter constructs its own client from the options below.
BunS3ClientLikebucket?string
S3 bucket name. Scopes operations and is exposed as `adapter.bucket`. Falls back to `S3_BUCKET` / `AWS_BUCKET` via Bun's built-in resolution.
stringregion?string
AWS region (e.g. `us-east-1`). Falls back to `S3_REGION` / `AWS_REGION` via Bun's resolution.
stringendpoint?string
Override the S3 service endpoint. Use this to point at S3-compatible services (R2, DigitalOcean Spaces, Wasabi, MinIO, ...).
stringvirtualHostedStyle?boolean
Use virtual-hosted-style addressing (`https://<bucket>.<endpoint>`) instead of path-style. Defaults to `false` — flip on for endpoints that require it.
booleanaccessKeyId?string
Static access key ID. Skip to let Bun resolve it from `S3_ACCESS_KEY_ID` / `AWS_ACCESS_KEY_ID`.
stringsecretAccessKey?string
Static secret access key. Skip to let Bun resolve it from `S3_SECRET_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY`.
stringsessionToken?string
Static session token for temporary credentials. Skip to let Bun resolve it from `S3_SESSION_TOKEN` / `AWS_SESSION_TOKEN`.
stringpublicBaseUrl?string
Origin used to build URLs from `url()`. When set, `url(key)` returns `${publicBaseUrl}/${key}` and skips signing — use this if your bucket is fronted by a CDN or has a public-read policy. Passing `responseContentDisposition` still forces a signed URL even when this is set, because a permanent CDN URL has no signature in which to bind the override. When unset, `url()` returns a presigned GetObject (1-hour default).
stringdefaultUrlExpiresIn?number
Default expiry, in seconds, for the presigned URLs returned by `url()` when `publicBaseUrl` isn't set. Defaults to 3600 (1 hour). Per-call `url(key, { expiresIn })` overrides.
numberCompatibility
| Method | Status | Notes |
|---|---|---|
upload |
⚠️ | User metadata and cacheControl throw - Bun.S3Client.write() exposes neither field. Reach for s3() on the same bucket if you need them. Stream bodies are wrapped in a Response and handed to Bun’s writer. Pause/resume via control is in-process only — Bun’s S3 client exposes no resumable upload id, so chunks are buffered and a token cannot resume in a new process. |
download |
✅ | |
delete |
✅ | |
list |
⚠️ | Bun’s list response carries no Content-Type, so type is inferred from the key’s extension (application/octet-stream when unknown), like the rest of the S3 family. Use head() for the stored value. delimiter folding works. |
search |
✅ | |
head |
✅ | |
exists |
✅ | |
copy |
⚠️ | Client-side stream copy - Bun.S3Client doesn’t expose a server-side CopyObject, so the source is streamed through this process and re-uploaded. Doubled bandwidth, not atomic, and drops Content-Disposition/cache headers/user metadata/ACL (only Content-Type is preserved). Reach for s3() on the same bucket for server-side copy. |
url |
✅ | Presigned URLs are capped at 7 days (the SigV4 limit); a longer expiresIn throws. |
signedUploadUrl |
⚠️ | PUT URL only - Bun exposes presigned URLs, not S3 POST policy fields, so maxSize throws (no content-length-range policy). contentType throws too, because Bun signs only the host header and can’t bind a Content-Type. Enforce upload caps and types at your application gateway instead. |