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

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-sdk
pnpm add files-sdk
yarn add files-sdk
bun add files-sdk
nub add files-sdk
aube add files-sdk

Usage

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-side CopyObject primitive.
  • upload() throws on metadata and cacheControl, because Bun.S3Client.write() has no equivalent options.
  • signedUploadUrl() throws on maxSize, because Bun exposes presigned URLs only, not S3 POST policy fields. It also throws on contentType: Bun’s presigned PUT signs only the host header, so it can’t enforce a Content-Type. (The useFiles gateway falls back to proxying such uploads through your server.)
  • Pause/resume via control works 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

PropType
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.

TypeBunS3ClientLike
bucket?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.

Typestring
region?string

AWS region (e.g. `us-east-1`). Falls back to `S3_REGION` / `AWS_REGION` via Bun's resolution.

Typestring
endpoint?string

Override the S3 service endpoint. Use this to point at S3-compatible services (R2, DigitalOcean Spaces, Wasabi, MinIO, ...).

Typestring
virtualHostedStyle?boolean

Use virtual-hosted-style addressing (`https://<bucket>.<endpoint>`) instead of path-style. Defaults to `false` — flip on for endpoints that require it.

Typeboolean
accessKeyId?string

Static access key ID. Skip to let Bun resolve it from `S3_ACCESS_KEY_ID` / `AWS_ACCESS_KEY_ID`.

Typestring
secretAccessKey?string

Static secret access key. Skip to let Bun resolve it from `S3_SECRET_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY`.

Typestring
sessionToken?string

Static session token for temporary credentials. Skip to let Bun resolve it from `S3_SESSION_TOKEN` / `AWS_SESSION_TOKEN`.

Typestring
publicBaseUrl?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).

Typestring
defaultUrlExpiresIn?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.

Typenumber

Compatibility

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.

Was this page helpful?