S3 Storage
The @commercejs/storage-s3 package implements the StorageProvider interface for S3-compatible object storage. It powers product image uploads, asset management, and file serving for Commerce.js Cloud's native platform.
!NOTE External platform adapters (Salla, Medusa) manage their own media — this provider is for the native Commerce.js platform where Commerce.js is the system of record.
Installation
pnpm add @commercejs/storage-s3
Quick Start
import { S3StorageProvider } from '@commercejs/storage-s3'
const storage = new S3StorageProvider({
endpoint: 'https://s3.us-east-1.amazonaws.com',
region: 'us-east-1',
bucket: 'my-store-assets',
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
publicUrl: 'https://cdn.example.com',
})
// Upload a product image
const result = await storage.upload({
filename: 'hero.jpg',
mimeType: 'image/jpeg',
content: imageBytes,
directory: 'products',
})
// → { key: 'products/hero-a1b2c3d4.jpg', url: 'https://cdn.example.com/products/hero-a1b2c3d4.jpg' }
Configuration
| Option | Type | Required | Description |
|---|---|---|---|
endpoint | string | ✅ | S3-compatible endpoint URL |
region | string | ✅ | Region (e.g., 'us-east-1', 'auto' for R2) |
bucket | string | ✅ | Bucket name |
accessKeyId | string | ✅ | Access key ID |
secretAccessKey | string | ✅ | Secret access key |
publicUrl | string | — | CDN base URL for public files |
prefix | string | — | Global key prefix (e.g., 'uploads/') |
forcePathStyle | boolean | — | Path-style URLs (required for MinIO) |
Methods
All methods follow the StorageProvider interface from @commercejs/types.
upload
Uploads a file and returns the storage key + public URL:
const result = await storage.upload({
filename: 'hero.jpg',
mimeType: 'image/jpeg',
content: imageBytes, // Uint8Array
directory: 'products', // optional path prefix
})
console.log(result.key) // 'products/hero-a1b2c3d4.jpg'
console.log(result.url) // 'https://cdn.example.com/products/hero-a1b2c3d4.jpg'
Keys are auto-generated with a random suffix to prevent collisions.
delete
Deletes a file by its storage key:
await storage.delete('products/hero-a1b2c3d4.jpg')
getUrl
Returns the public/CDN URL for a stored file:
const url = storage.getUrl('products/hero-a1b2c3d4.jpg')
// With publicUrl → 'https://cdn.example.com/products/hero-a1b2c3d4.jpg'
// Without → 'https://bucket.s3.us-east-1.amazonaws.com/products/hero-a1b2c3d4.jpg'
getPresignedUploadUrl
Generates a presigned PUT URL for direct browser-to-S3 uploads:
const { url, headers } = await storage.getPresignedUploadUrl('products/hero.jpg', {
contentType: 'image/jpeg',
expiresIn: 600, // 10 minutes
})
// In the browser:
await fetch(url, { method: 'PUT', headers, body: file })
getPresignedDownloadUrl
Generates a presigned GET URL for private file downloads:
const { url } = await storage.getPresignedDownloadUrl('invoices/inv-001.pdf', {
expiresIn: 900, // 15 minutes
})
S3-Compatible Services
The provider works with any S3-protocol service by changing the endpoint:
// AWS S3
new S3StorageProvider({ endpoint: 'https://s3.us-east-1.amazonaws.com', region: 'us-east-1', ... })
// Cloudflare R2
new S3StorageProvider({ endpoint: 'https://<account>.r2.cloudflarestorage.com', region: 'auto', ... })
// DigitalOcean Spaces
new S3StorageProvider({ endpoint: 'https://nyc3.digitaloceanspaces.com', region: 'nyc3', ... })
// MinIO (self-hosted)
new S3StorageProvider({ endpoint: 'http://localhost:9000', forcePathStyle: true, ... })
Using with the Orchestrator
import { createCommerce } from '@commercejs/core'
import { S3StorageProvider } from '@commercejs/storage-s3'
const commerce = createCommerce({
adapter,
storage: new S3StorageProvider({
endpoint: 'https://s3.us-east-1.amazonaws.com',
region: 'us-east-1',
bucket: 'my-store',
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
publicUrl: 'https://cdn.example.com',
}),
})
// Upload through the orchestrator
const result = await commerce.uploadFile({
filename: 'hero.jpg',
mimeType: 'image/jpeg',
content: imageBytes,
directory: 'products',
})
// Get a presigned upload URL for client-side uploads
const { url, headers } = await commerce.getPresignedUploadUrl('products/hero.jpg', {
contentType: 'image/jpeg',
})