S3-compatible providers
Cloudflare R2, MinIO, Scaleway, Wasabi — the endpoint is the only difference.
Set endpoint and s3nd adjusts two defaults for you:
regionbecomes"auto", which is the convention for providers that ignore the region but whose SDK still demands one.forcePathStylebecomestrue—https://endpoint/bucket/keyrather thanhttps://bucket.endpoint/key. Self-hosted gateways require it and managed providers accept it.
Both stay overridable.
Cloudflare R2
const store = createBucket({
bucket: 'my-bucket',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
publicUrl: 'https://cdn.example.com', // your R2 custom domain
})R2 has no egress fees, which makes the public-URL path particularly attractive: upload through your server, serve through the custom domain.
MinIO
const store = createBucket({
bucket: 'my-bucket',
endpoint: 'http://localhost:9000',
credentials: { accessKeyId: 'minioadmin', secretAccessKey: 'minioadmin' },
})A local MinIO is the cheapest way to exercise the real code path in development and in CI:
docker run -p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \
quay.io/minio/minio server /data --console-address ":9001"Scaleway Object Storage
const store = createBucket({
bucket: 'my-bucket',
region: 'fr-par',
endpoint: 'https://s3.fr-par.scw.cloud',
credentials: {
accessKeyId: process.env.SCW_ACCESS_KEY!,
secretAccessKey: process.env.SCW_SECRET_KEY!,
},
})Scaleway does use the region, so pass it explicitly rather than letting it default to "auto".
Wasabi
const store = createBucket({
bucket: 'my-bucket',
region: 'eu-central-1',
endpoint: 'https://s3.eu-central-1.wasabisys.com',
credentials: {
accessKeyId: process.env.WASABI_ACCESS_KEY!,
secretAccessKey: process.env.WASABI_SECRET_KEY!,
},
})A note on ACLs
upload() accepts an acl option, but most buckets created after 2023 have ACLs disabled —
AWS turns on "bucket owner enforced" by default, and R2 has no ACL support at all. Setting acl
on such a bucket fails the request. Make objects public with a bucket policy or a CDN in front,
and leave acl alone.