Skip to main content
S3 backend for remix/file-storage. Use this package when you want the FileStorage API backed by AWS S3 or an S3-compatible provider.

Features

  • S3-Compatible API - Works with AWS S3 and S3-compatible APIs (MinIO, LocalStack, etc.)
  • Metadata Preservation - Preserves File metadata (name, type, lastModified)
  • Runtime-Agnostic Signing - Uses aws4fetch for SigV4 signing

Installation

npm install remix

API Reference

createS3FileStorage

Create an S3-backed file storage instance.
options
S3FileStorageOptions
required
Configuration options for S3 storage

S3FileStorageOptions

accessKeyId
string
required
AWS access key ID
secretAccessKey
string
required
AWS secret access key
bucket
string
required
S3 bucket name
region
string
required
AWS region (e.g., us-east-1)
endpoint
string
Custom endpoint URL for S3-compatible providers (e.g., MinIO, LocalStack)
forcePathStyle
boolean
Use path-style URLs instead of virtual-hosted-style. Required for some S3-compatible providers.
fetch
typeof fetch
Custom fetch implementation. Defaults to global fetch.

Returns

storage
FileStorage
A FileStorage instance backed by S3

Usage Examples

AWS S3

Connect to AWS S3:
import { createS3FileStorage } from 'remix/file-storage-s3'

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

// Store a file
await storage.set(
  'uploads/hello.txt',
  new File(['hello world'], 'hello.txt', { type: 'text/plain' })
)

// Retrieve a file
let file = await storage.get('uploads/hello.txt')
console.log(await file?.text())

// Remove a file
await storage.remove('uploads/hello.txt')

MinIO

Connect to MinIO or other S3-compatible providers:
import { createS3FileStorage } from 'remix/file-storage-s3'

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

LocalStack

Connect to LocalStack for local development:
import { createS3FileStorage } from 'remix/file-storage-s3'

let storage = createS3FileStorage({
  accessKeyId: 'test',
  secretAccessKey: 'test',
  bucket: 'test-bucket',
  region: 'us-east-1',
  endpoint: 'http://localhost:4566',
  forcePathStyle: true,
})

File Upload Handling

Integrate with form data parser for file uploads:
import { createS3FileStorage } from 'remix/file-storage-s3'
import { parseFormData } from 'remix/form-data-parser'
import { createRouter } from 'remix/fetch-router'

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

let router = createRouter()

router.post('/upload', async ({ request }) => {
  let formData = await parseFormData(request, {
    uploadHandler: async (fileUpload) => {
      // Stream file directly to S3
      let key = `uploads/${Date.now()}-${fileUpload.name}`
      await storage.put(key, fileUpload)
      return key
    },
  })

  let fileKey = formData.get('file')
  return Response.json({ fileKey })
})

List Files

List files in a directory:
import { createS3FileStorage } from 'remix/file-storage-s3'

let storage = createS3FileStorage({ /* ... */ })

// List all files with a prefix
let result = await storage.list({ prefix: 'uploads/' })

for (let file of result.files) {
  console.log(file.key, file.size, file.lastModified)
}

// Paginate through results
if (result.cursor) {
  let nextPage = await storage.list({
    prefix: 'uploads/',
    cursor: result.cursor,
  })
}

Environment Variables

Store credentials in environment variables:
.env
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
S3_BUCKET=my-app-uploads
S3_REGION=us-east-1

Best Practices

  • Store credentials in environment variables, never in code
  • Use IAM roles when running on AWS infrastructure
  • Set appropriate bucket policies and CORS configuration
  • Use separate buckets for different environments (dev, staging, production)
  • Implement file size limits to prevent abuse
  • Consider using presigned URLs for large file uploads

File Storage

Core FileStorage interface and API

Form Data Parser

Parse file uploads from forms

Build docs developers (and LLMs) love