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
Local File
S3 (Access Keys)
S3 (IAM Role)
S3-Compatible
Configure the local file provider: 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)
Configure S3 with access key authentication: import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig ({
modules: [
{
resolve: "@medusajs/medusa/file" ,
options: {
providers: [
{
resolve: "@medusajs/medusa/file-s3" ,
id: "s3" ,
options: {
file_url: process . env . S3_FILE_URL ,
access_key_id: process . env . S3_ACCESS_KEY_ID ,
secret_access_key: process . env . S3_SECRET_ACCESS_KEY ,
region: process . env . S3_REGION ,
bucket: process . env . S3_BUCKET ,
prefix: "medusa" ,
cache_control: "public, max-age=31536000" ,
},
},
],
},
},
] ,
})
Environment variables: S3_FILE_URL = https://your-bucket.s3.amazonaws.com
S3_ACCESS_KEY_ID = AKIAIOSFODNN7EXAMPLE
S3_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
S3_REGION = us-east-1
S3_BUCKET = medusa-files
Configure S3 with IAM role authentication (for EC2, ECS, Lambda): import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig ({
modules: [
{
resolve: "@medusajs/medusa/file" ,
options: {
providers: [
{
resolve: "@medusajs/medusa/file-s3" ,
id: "s3" ,
options: {
file_url: process . env . S3_FILE_URL ,
authentication_method: "s3-iam-role" ,
region: process . env . S3_REGION ,
bucket: process . env . S3_BUCKET ,
prefix: "medusa" ,
},
},
],
},
},
] ,
})
With IAM role authentication, credentials are automatically retrieved from the
environment. Ensure your EC2/ECS instance has the appropriate IAM role attached.
Configure S3-compatible storage (MinIO, DigitalOcean Spaces, etc.): import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig ({
modules: [
{
resolve: "@medusajs/medusa/file" ,
options: {
providers: [
{
resolve: "@medusajs/medusa/file-s3" ,
id: "s3" ,
options: {
file_url: process . env . S3_FILE_URL ,
access_key_id: process . env . S3_ACCESS_KEY_ID ,
secret_access_key: process . env . S3_SECRET_ACCESS_KEY ,
region: process . env . S3_REGION ,
bucket: process . env . S3_BUCKET ,
endpoint: process . env . S3_ENDPOINT ,
additional_client_config: {
forcePathStyle: true ,
},
},
},
],
},
},
] ,
})
For DigitalOcean Spaces: S3_FILE_URL = https://your-space.nyc3.digitaloceanspaces.com
S3_ENDPOINT = https://nyc3.digitaloceanspaces.com
S3_REGION = nyc3
S3_BUCKET = your-space
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