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.