Configuration
Every option createBucket accepts, and the environment variable behind it.
createBucket({
bucket: 'my-bucket', // required (or S3ND_BUCKET / S3_BUCKET)
region: 'eu-west-3', // or S3ND_REGION / AWS_REGION / AWS_DEFAULT_REGION
credentials: {
// omit to use the AWS default provider chain
accessKeyId: '…',
secretAccessKey: '…',
sessionToken: '…', // optional
},
endpoint: 'https://…', // S3-compatible storage — or S3ND_ENDPOINT / S3_ENDPOINT
forcePathStyle: true, // defaults to true when `endpoint` is set
publicUrl: 'https://cdn…', // public bucket or CDN — or S3ND_PUBLIC_URL / S3_PUBLIC_URL
prefix: 'snapshots', // internal namespace, applied on the way in and out
maxSize: 5 * 1024 * 1024, // reject bigger writes before any network call
syncCode: { length: 8 }, // shape of the codes store.codes makes and reads
client: myS3Client, // bring your own S3Client
})Options
| Option | Environment fallback | Notes |
|---|---|---|
bucket | S3ND_BUCKET, S3_BUCKET | The only required option. |
region | S3ND_REGION, AWS_REGION, AWS_DEFAULT_REGION | Defaults to "auto" when an endpoint is set. |
credentials | — | Omit for the AWS default provider chain. |
endpoint | S3ND_ENDPOINT, S3_ENDPOINT | For R2, MinIO, Scaleway, Wasabi… |
forcePathStyle | — | Defaults to true with a custom endpoint, false otherwise. |
publicUrl | S3ND_PUBLIC_URL, S3_PUBLIC_URL | Must be an absolute URL. Trailing slash optional. |
prefix | — | Applied by every method. |
maxSize | — | In bytes. Enforced before any network call. |
syncCode | — | { length, alphabet }. Defaults to eight Crockford base32 characters — see Configuring codes. |
client | — | An S3Client you built yourself. |
The prefix is a namespace, not part of the key
The prefix is applied on the way in and stripped on the way out, so the identifier you store in your database is the one you hand back to every method:
const store = createBucket({ bucket: 'my-bucket', prefix: 'snapshots' })
const result = await store.putSnapshot('K7QP2M4X', state)
result.key // "K7QP2M4X" — the code the user carries
result.path // "snapshots/K7QP2M4X" — the real object key, for the AWS console
await store.getSnapshot('K7QP2M4X') // reads snapshots/K7QP2M4X
await store.delete('K7QP2M4X') // deletes snapshots/K7QP2M4XEvery method takes a per-call prefix that overrides the bucket-level one, which is how you keep
transfer codes, per-account backups and attachments apart:
await store.putSnapshot(`user-${userId}`, state, { prefix: 'backups' })
await store.getSnapshot(`user-${userId}`, { prefix: 'backups' })Separating them early is what lets you give each one its own S3 lifecycle rule: a transfer code should expire in a day, a backup should not expire at all.
Public URLs
Set publicUrl when the bucket is served publicly or sits behind a CDN. Two things change:
upload()returns a ready-to-useurlin its result.getUrl()returns the public URL instead of a presigned one, unless you passsigned: true.
const store = createBucket({ bucket: 'my-bucket', publicUrl: 'https://cdn.example.com' })
await store.getUrl('avatars/42.png')
// → "https://cdn.example.com/avatars/42.png"
await store.getUrl('avatars/42.png', { signed: true, expiresIn: 60 })
// → a presigned URL, even though publicUrl is setWithout publicUrl, upload() leaves url undefined on purpose. A URL that answers 403 is worse
than no URL at all.
Size caps
maxSize is checked against the body size before anything is sent, so an oversized upload costs
you nothing:
const store = createBucket({ bucket: 'my-bucket', maxSize: 4 * 1024 * 1024 })
await store.upload(tooBig) // throws S3ndError { code: 'FILE_TOO_LARGE' }Set it below your runtime's own request limit and you get a clean 413 instead of a truncated request. See How big can a snapshot be.
Bringing your own client
client takes over completely — region, credentials, endpoint and retry strategy all come from
it:
import { S3Client } from '@aws-sdk/client-s3'
const store = createBucket({
bucket: 'my-bucket',
client: new S3Client({ region: 'eu-west-3', maxAttempts: 5 }),
})A client you passed in is yours to close: store.destroy() leaves it alone.