Skip to main content

File Storage Providers

File storage providers handle file uploads, downloads, and storage management. They support both public files (accessible via URL) and private files (requiring presigned URLs).

Available File Storage Providers

Medusa includes two file storage providers:

Local File Provider

@medusajs/medusa/file-local - Stores files on the local filesystem. Use cases:
  • Development and testing
  • Local deployments
  • Simple file storage needs
Limitations:
  • Not suitable for production with multiple servers
  • No true private file support (files are in static directory)
  • Limited scalability

S3 File Provider

@medusajs/medusa/file-s3 - Stores files in Amazon S3 or S3-compatible storage. Use cases:
  • Production deployments
  • Scalable cloud storage
  • Multi-server environments
  • CDN integration
Features:
  • True public/private file support
  • Presigned URLs for secure access
  • Multiple authentication methods (access keys or IAM roles)
  • Compatible with S3-compatible services (MinIO, DigitalOcean Spaces, etc.)

Installation

Both providers are included in the core Medusa package:
npm install @medusajs/medusa
For S3, also install the AWS SDK:
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner

Configuration

Configure the local file provider:
medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"

export default defineConfig({
modules: [
{
  resolve: "@medusajs/medusa/file",
  options: {
    providers: [
      {
        resolve: "@medusajs/medusa/file-local",
        id: "local",
        options: {
          upload_dir: "static",
          backend_url: "http://localhost:9000/static",
        },
      },
    ],
  },
},
],
})
Options:
  • upload_dir - Directory for storing files (default: static)
  • private_upload_dir - Directory for private files (default: static)
  • backend_url - Base URL for accessing files (default: http://localhost:9000/static)

File Provider Interface

All file providers extend AbstractFileProviderService and implement the following methods:

upload(file: ProviderUploadFileDTO): Promise<ProviderFileResultDTO>

Uploads a file.
const result = await fileProvider.upload({
  filename: "product-image.jpg",
  mimeType: "image/jpeg",
  content: base64EncodedContent,
  access: "public", // or "private"
})
// Result: { url: "https://...", key: "product-image-123.jpg" }
Parameters:
  • filename - Original filename
  • mimeType - MIME type of the file
  • content - File content (base64 or UTF-8 string)
  • access - "public" or "private"
Returns:
  • url - Public URL (for public files) or identifier
  • key - File key/identifier for future operations

getUploadStream(fileData: ProviderUploadStreamDTO): Promise<UploadStreamResult>

Gets a writable stream for uploading large files.
const { writeStream, promise, url, fileKey } =
  await fileProvider.getUploadStream({
    filename: "large-video.mp4",
    mimeType: "video/mp4",
    access: "public",
  })

// Pipe file to the stream
readableStream.pipe(writeStream)

// Wait for upload to complete
const result = await promise

delete(files: ProviderDeleteFileDTO | ProviderDeleteFileDTO[]): Promise<void>

Deletes one or more files.
await fileProvider.delete({
  fileKey: "product-image-123.jpg",
})

// Or delete multiple files
await fileProvider.delete([
  { fileKey: "file1.jpg" },
  { fileKey: "file2.jpg" },
])

getDownloadStream(file: ProviderGetFileDTO): Promise<Readable>

Gets a readable stream for downloading a file.
const stream = await fileProvider.getDownloadStream({
  fileKey: "product-image-123.jpg",
})

// Pipe to response
stream.pipe(res)

getAsBuffer(file: ProviderGetFileDTO): Promise<Buffer>

Gets file content as a buffer.
const buffer = await fileProvider.getAsBuffer({
  fileKey: "product-image-123.jpg",
})

getPresignedDownloadUrl(file: ProviderGetFileDTO): Promise<string>

Generates a presigned URL for downloading a private file.
const url = await fileProvider.getPresignedDownloadUrl({
  fileKey: "private-document-123.pdf",
})
// Result: "https://...?signature=..."
Presigned URLs are temporary and expire after a configured duration (default: 1 hour for S3).

getPresignedUploadUrl(fileData: ProviderGetPresignedUploadUrlDTO): Promise<ProviderFileResultDTO>

Generates a presigned URL for client-side uploads.
const { url, key } = await fileProvider.getPresignedUploadUrl({
  filename: "user-avatar.jpg",
  mimeType: "image/jpeg",
  access: "public",
  expiresIn: 3600, // 1 hour
})

// Client can now upload directly to this URL

Using the File Module

Access file providers through the File Module:
import { Modules } from "@medusajs/framework/utils"

const fileModule = container.resolve(Modules.FILE)

// Upload a file
const file = await fileModule.createFiles({
  filename: "product-image.jpg",
  mimeType: "image/jpeg",
  content: base64Content,
  access: "public",
})

// Get download URL for private file
const url = await fileModule.getPresignedDownloadUrl(file.key)

// Delete a file
await fileModule.deleteFiles(file.id)

Creating Custom File Providers

Create a custom file provider by extending AbstractFileProviderService:
packages/modules/providers/file-custom/src/services/custom-file.ts
import { AbstractFileProviderService } from "@medusajs/framework/utils"
import { FileTypes } from "@medusajs/framework/types"
import { Readable } from "stream"

export class CustomFileService extends AbstractFileProviderService {
  static identifier = "custom-file"

  constructor(container, options) {
    super()
    this.options_ = options
    // Initialize storage client
    this.client_ = new StorageClient(options)
  }

  async upload(
    file: FileTypes.ProviderUploadFileDTO
  ): Promise<FileTypes.ProviderFileResultDTO> {
    // Upload file to your storage service
    const result = await this.client_.upload({
      filename: file.filename,
      content: Buffer.from(file.content, "base64"),
      acl: file.access === "public" ? "public-read" : "private",
    })

    return {
      url: result.publicUrl,
      key: result.fileId,
    }
  }

  async getUploadStream(
    fileData: FileTypes.ProviderUploadStreamDTO
  ): Promise<UploadStreamResult> {
    // Return writable stream
    const stream = this.client_.createWriteStream(fileData.filename)

    const promise = new Promise((resolve, reject) => {
      stream.on("finish", () => {
        resolve({ url: stream.url, key: stream.fileId })
      })
      stream.on("error", reject)
    })

    return {
      writeStream: stream,
      promise,
      url: stream.url,
      fileKey: stream.fileId,
    }
  }

  async delete(files: FileTypes.ProviderDeleteFileDTO[]): Promise<void> {
    const fileArray = Array.isArray(files) ? files : [files]
    await this.client_.deleteMultiple(
      fileArray.map((f) => f.fileKey)
    )
  }

  async getDownloadStream(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<Readable> {
    return this.client_.createReadStream(file.fileKey)
  }

  async getAsBuffer(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<Buffer> {
    return this.client_.getBuffer(file.fileKey)
  }

  async getPresignedDownloadUrl(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<string> {
    return this.client_.getSignedUrl(file.fileKey, 3600)
  }

  async getPresignedUploadUrl(
    fileData: FileTypes.ProviderGetPresignedUploadUrlDTO
  ): Promise<FileTypes.ProviderFileResultDTO> {
    const signedUrl = await this.client_.getUploadUrl(
      fileData.filename,
      fileData.expiresIn ?? 3600
    )

    return {
      url: signedUrl,
      key: fileData.filename,
    }
  }
}
Register your custom provider:
packages/modules/providers/file-custom/src/index.ts
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
import { CustomFileService } from "./services/custom-file"

export default ModuleProvider(Modules.FILE, {
  services: [CustomFileService],
})

Reference

  • Local File Source: packages/modules/providers/file-local/src/services/local-file.ts
  • S3 Source: packages/modules/providers/file-s3/src/services/s3-file.ts
  • Base class: packages/core/utils/src/file/abstract-file-provider.ts
  • Types: packages/core/types/src/file/provider.ts

Next Steps

Payment Providers

Configure payment processing

Notification Providers

Send file upload notifications

Build docs developers (and LLMs) love