Skip to main content
Key/value storage interfaces for server-side File objects. file-storage gives Remix apps one consistent API across local disk and memory backends.

Features

  • Simple API - Intuitive key/value API (like Web Storage, but for Files instead of strings)
  • Multiple Backends - Built-in filesystem and memory backends
  • Streaming Support - Stream file content to and from storage
  • Metadata Preservation - Preserves all File metadata including file.name, file.type, and file.lastModified

Installation

npm i remix

Usage

File System Storage

import { createFsFileStorage } from 'remix/file-storage/fs'

let storage = createFsFileStorage('./user/files')

let file = new File(['hello world'], 'hello.txt', { type: 'text/plain' })
let key = 'hello-key'

// Put the file in storage.
await storage.set(key, file)

// Then, sometime later...
let fileFromStorage = await storage.get(key)
// All of the original file's metadata is intact
fileFromStorage.name // 'hello.txt'
fileFromStorage.type // 'text/plain'

// To remove from storage
await storage.remove(key)

Memory Storage

Memory storage is useful for testing and development:
import { createMemoryFileStorage } from 'remix/file-storage/memory'

let storage = createMemoryFileStorage()

let file = new File(['test data'], 'test.txt', { type: 'text/plain' })
await storage.set('test-key', file)

let retrieved = await storage.get('test-key')
console.log(await retrieved.text()) // 'test data'

Working with File Uploads

file-storage pairs well with form-data-parser for handling file uploads:
import { createFsFileStorage } from 'remix/file-storage/fs'
import { parseFormData } from 'remix/form-data-parser'

let storage = createFsFileStorage('./uploads')

async function handleUpload(request: Request) {
  let formData = await parseFormData(request)
  let file = formData.get('avatar')

  if (file instanceof File) {
    // Generate a unique key for the file
    let key = `${Date.now()}-${file.name}`
    
    // Store the file
    await storage.set(key, file)
    
    return Response.json({
      success: true,
      key,
      filename: file.name,
      size: file.size,
      type: file.type,
    })
  }

  return Response.json({ error: 'No file uploaded' }, { status: 400 })
}

Serving Files from Storage

Use with response helpers to serve files with proper HTTP semantics:
import { createFsFileStorage } from 'remix/file-storage/fs'
import { createFileResponse } from 'remix/response/file'

let storage = createFsFileStorage('./uploads')

async function handleDownload(request: Request, key: string) {
  let file = await storage.get(key)
  
  if (!file) {
    return new Response('File not found', { status: 404 })
  }

  return createFileResponse(file, request, {
    cacheControl: 'public, max-age=3600',
  })
}

Streaming Large Files

File storage works efficiently with large files using streams:
import { createFsFileStorage } from 'remix/file-storage/fs'

let storage = createFsFileStorage('./large-files')

// Store a large file (uses streaming internally)
let largeFile = new File([await readFileData()], 'video.mp4', {
  type: 'video/mp4',
})
await storage.set('video-key', largeFile)

// Retrieve and stream to response
let file = await storage.get('video-key')
return new Response(file.stream(), {
  headers: {
    'Content-Type': file.type,
    'Content-Disposition': `attachment; filename="${file.name}"`,
  },
})

API Reference

createFsFileStorage(directory)

Creates a file storage instance that stores files on the filesystem. Parameters:
  • directory: string - Path to the directory where files will be stored
Returns: FileStorage

createMemoryFileStorage()

Creates a file storage instance that stores files in memory. Returns: FileStorage

FileStorage

Interface for file storage implementations.
interface FileStorage {
  /** Store a file with the given key */
  set(key: string, file: File): Promise<void>
  
  /** Retrieve a file by key */
  get(key: string): Promise<File | null>
  
  /** Check if a file exists */
  has(key: string): Promise<boolean>
  
  /** Remove a file by key */
  remove(key: string): Promise<void>
  
  /** List all file keys (optional pagination) */
  list(options?: ListOptions): Promise<ListResult>
}

interface ListOptions {
  /** Maximum number of keys to return */
  limit?: number
  /** Pagination cursor from previous list() call */
  cursor?: string
}

interface ListResult {
  /** Array of file keys */
  keys: string[]
  /** Cursor for next page (if more results available) */
  cursor?: string
}

File Metadata

All standard File properties are preserved:
  • name: string - The filename
  • size: number - File size in bytes
  • type: string - MIME type
  • lastModified: number - Last modified timestamp

External Storage Backends

For cloud storage, use external backends:
  • form-data-parser - Parse multipart/form-data requests with file uploads
  • lazy-file - The streaming File implementation used internally
  • response/file - Create file responses with proper HTTP semantics

Build docs developers (and LLMs) love