Skip to content

file-storage-s3 ​

The file-storage-s3 package provides an S3-compatible file storage backend that implements the FileStorage interface. It works with Amazon S3, Cloudflare R2, MinIO, and any other service that implements the S3 API.

Installation ​

The S3 file storage backend is included with Remix. No additional installation is needed.

Import ​

ts
import { createS3FileStorage } from 'remix/file-storage-s3'

API ​

createS3FileStorage(options) ​

Creates a file storage backend that reads and writes files to an S3-compatible object store.

ts
function createS3FileStorage(options: S3FileStorageOptions): FileStorage

The returned object implements the full FileStorage interface: set, get, has, and remove.

Options ​

OptionTypeRequiredDescription
bucketstringYesThe S3 bucket name.
regionstringYesThe AWS region (e.g. 'us-east-1').
credentialsobjectNoAWS credentials. Uses the default credential chain if omitted.
credentials.accessKeyIdstring--AWS access key ID.
credentials.secretAccessKeystring--AWS secret access key.
endpointstringNoCustom endpoint URL for S3-compatible services.
forcePathStylebooleanNoUse path-style URLs instead of virtual-hosted-style. Required for some S3-compatible services.
prefixstringNoA prefix prepended to all keys. Useful for organizing files within a bucket.

Examples ​

Amazon S3 ​

ts
import { createS3FileStorage } from 'remix/file-storage-s3'

let storage = createS3FileStorage({
  bucket: 'my-app-uploads',
  region: 'us-east-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

// Store a file
await storage.set('avatars/user-123.jpg', file)

// Retrieve a file
let file = await storage.get('avatars/user-123.jpg')

// Check existence
let exists = await storage.has('avatars/user-123.jpg')

// Delete a file
await storage.remove('avatars/user-123.jpg')

Cloudflare R2 ​

ts
let storage = createS3FileStorage({
  bucket: 'my-bucket',
  region: 'auto',
  endpoint: 'https://ACCOUNT_ID.r2.cloudflarestorage.com',
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  },
})

MinIO ​

ts
let storage = createS3FileStorage({
  bucket: 'my-bucket',
  region: 'us-east-1',
  endpoint: 'http://localhost:9000',
  forcePathStyle: true,
  credentials: {
    accessKeyId: 'minioadmin',
    secretAccessKey: 'minioadmin',
  },
})

With a Key Prefix ​

Organize files within a shared bucket:

ts
let avatars = createS3FileStorage({
  bucket: 'my-app',
  region: 'us-east-1',
  prefix: 'avatars/',
})

let documents = createS3FileStorage({
  bucket: 'my-app',
  region: 'us-east-1',
  prefix: 'documents/',
})

// Writes to s3://my-app/avatars/user-123.jpg
await avatars.set('user-123.jpg', file)

// Writes to s3://my-app/documents/report.pdf
await documents.set('report.pdf', file)

Upload Handler ​

Use with parseFormData to stream uploads directly to S3:

ts
import { parseFormData } from 'remix/form-data-parser'
import { createS3FileStorage } from 'remix/file-storage-s3'

let storage = createS3FileStorage({
  bucket: 'my-uploads',
  region: 'us-east-1',
})

router.map(uploadRoute, async ({ request }) => {
  let formData = await parseFormData(request, {}, async (fileUpload) => {
    let key = `uploads/${Date.now()}-${fileUpload.name}`
    await storage.set(key, fileUpload)
    return key
  })

  let fileKey = formData.get('file') as string
  return Response.json({ key: fileKey })
})

Released under the MIT License.