Strapi Upload Providers — Local vs S3 vs Cloudinary vs Cloudflare R2
In this tutorial, you will learn how to configure Strapi upload providers — from the default local filesystem to cloud storage solutions like Amazon S3, Cloudinary, and Cloudflare R2 — enabling scalable, reliable media storage for production deployments.
What You'll Learn
- The limitations of Strapi's default local upload provider
- How to configure the Amazon S3 upload provider
- How to configure Cloudinary for image hosting
- How to configure Cloudflare R2 for S3-compatible storage
- How to switch between providers using environment variables
- How providers affect file URLs and performance
Why It Matters
Strapi's default local upload provider stores files on the server's filesystem. This works for development but is problematic in production: files are not backed up, they are lost if the server is replaced, they cannot be served from a CDN, and they consume server disk space. Cloud storage providers solve all these problems while often improving performance through global CDN distribution.
Real-World Use
A recipe site with 10,000 high-resolution food photos stores them on Amazon S3 with CloudFront CDN. Images are served from 200+ edge locations worldwide. Loading times dropped from 2 seconds to 200ms. The Strapi server disk stays clean. Backups are handled by S3's 99.999999999% durability. The team sleeps better at night.
Learning Path
flowchart LR A["Media Upload"] --> B["Upload Providers
-- You are here"]:::current B --> C["Image Optimization"] C --> D["File Management"] D --> E["File Security"] classDef current fill:#4945ff,color:#fff,stroke-width:2px
Local Provider (Default)
The local provider is built into Strapi and requires no configuration.
// Default local provider
// Files are stored at: /public/uploads/
// File URLs: http://localhost:1337/uploads/filename.jpg
// Pros: Zero configuration, works immediately
// Cons: No backups, no CDN, fills server disk, lost on redeploy
// Use for: Development, prototyping, low-traffic personal projects
For production, you should switch to a cloud provider. The local provider is simply not designed for production use.
Amazon S3 Provider
Amazon S3 (Simple Storage Service) is the most widely used cloud storage solution.
# Install the S3 provider
npm install @strapi/provider-upload-aws-s3
// config/plugins.js
module.exports = {
upload: {
config: {
provider: "aws-s3",
providerOptions: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION || "us-east-1",
params: {
Bucket: process.env.AWS_S3_BUCKET || "my-strapi-uploads",
},
baseUrl: process.env.AWS_S3_BASE_URL, // Optional: CDN URL
},
sizeLimit: 10 * 1024 * 1024, // 10MB
},
},
};
AWS credentials should be stored in environment variables. Never hardcode them.
// .env file for S3
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_TOKEN=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_REGION=us-east-1
AWS_S3_BUCKET=my-strapi-uploads
After configuring the S3 provider, all uploads go directly to S3. The file URL in the API response reflects the S3 URL or your custom CDN URL.
Cloudinary Provider
Cloudinary is a cloud-based media management platform with built-in image optimization and transformation.
# Install the Cloudinary provider
npm install @strapi/provider-upload-cloudinary
// config/plugins.js
module.exports = {
upload: {
config: {
provider: "cloudinary",
providerOptions: {
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
},
sizeLimit: 10 * 1024 * 1024, // 10MB
},
},
};
Cloudinary's advantage over S3: it automatically optimizes images, generates responsive formats, and provides URL-based transformations.
// Cloudinary URL transformations (not in Strapi config, but in your frontend)
// Original: https://res.cloudinary.com/mycloud/image/upload/v1/pizza.jpg
// With transformations:
// https://res.cloudinary.com/mycloud/image/upload/w_400,h_300,c_fill,q_auto,f_auto/v1/pizza.jpg
// You can append transformation parameters to the URL:
// w_400 — width 400px
// h_300 — height 300px
// c_fill — crop mode: fill
// q_auto — automatic quality
// f_auto — automatic format (WebP when supported)
Cloudflare R2 Provider
Cloudflare R2 is S3-compatible object storage with no egress fees, making it cheaper than S3 for frequently accessed files.
# Install the S3 provider (R2 is S3-compatible)
npm install @strapi/provider-upload-aws-s3
// config/plugins.js
module.exports = {
upload: {
config: {
provider: "aws-s3",
providerOptions: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
region: "auto", // R2 uses "auto"
endpoint: process.env.R2_ENDPOINT, // e.g., https://account.r2.cloudflarestorage.com
params: {
Bucket: process.env.R2_BUCKET || "my-strapi-uploads",
},
baseUrl: process.env.R2_PUBLIC_URL, // Optional: custom domain
},
},
},
};
The key difference from S3: you must specify the endpoint parameter because R2 uses a different endpoint URL than AWS.
Provider Comparison
| Provider | Egress Fees | Built-in CDN | Image Optimization | Cost for 100GB |
|---|---|---|---|---|
| Local | Zero | No | No | Server disk cost |
| S3 | $0.09/GB | CloudFront (extra) | No (Lambda@Edge) | ~$2.50/month |
| Cloudinary | Included | Yes | Yes (best-in-class) | ~$1.50/month |
| R2 | Zero | Yes (Cloudflare) | No | ~$0.36/month |
Choose based on your priorities: cost (R2), image optimization (Cloudinary), or ecosystem integration (S3).
Switching Providers with Environment Variables
Use environment variables to switch providers between environments:
// config/plugins.js
module.exports = {
upload: {
config: {
provider: process.env.UPLOAD_PROVIDER || "local",
providerOptions: {
// Local provider options (empty)
...(process.env.UPLOAD_PROVIDER === "aws-s3" && {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
params: { Bucket: process.env.AWS_S3_BUCKET },
}),
...(process.env.UPLOAD_PROVIDER === "cloudinary" && {
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
}),
},
},
},
};
With this setup, each environment sets UPLOAD_PROVIDER to the appropriate value. Development uses local. Staging and production use cloud providers.
File URLs in Responses
The provider affects the URLs returned by the API:
// Local provider URL:
"url": "/uploads/pizza_c4d3f2b1e5.jpg"
// S3 provider URL:
"url": "https://my-bucket.s3.us-east-1.amazonaws.com/pizza_c4d3f2b1e5.jpg"
// Cloudinary provider URL:
"url": "https://res.cloudinary.com/mycloud/image/upload/v1/pizza_c4d3f2b1e5.jpg"
// R2 provider URL (with custom domain):
"url": "https://media.example.com/pizza_c4d3f2b1e5.jpg"
Your frontend should use the URL as provided by the API. If you switch providers, the URLs change automatically for new uploads. Existing files retain their original provider URLs.
Common Mistakes
Using local storage in production. Server disk fills up, files are lost on redeploy, and there is no backup. Always use cloud storage for production.
Hardcoding cloud provider credentials. Credentials in source code are exposed to anyone with Repository access. Use environment variables or a secrets manager.
Not setting a base URL or CDN URL. Without a custom domain, file URLs use the cloud provider's default domain. Set up a custom domain for consistent, branded file URLs.
Forgetting to configure CORS on the cloud provider. Cloud providers block requests from unknown origins by default. Configure CORS on your S3 bucket or Cloudinary account to allow your frontend domain.
Not testing provider configuration before going live. Test upload, retrieval, and deletion with each provider in a development environment before switching production. A misconfigured provider can break all file uploads.
Practice Questions
Why should you use a cloud storage provider instead of Strapi's local provider in production? Answer: Local storage has no backups, fills server disk, is lost on redeploy, and cannot be served from a CDN. Cloud providers offer durability, scalability, and CDN distribution.
Which provider would you choose if cost is the primary concern? Answer: Cloudflare R2 has zero egress fees and very low storage costs. It is the cheapest option for serving files to users.
How do you switch from the local provider to S3 after existing files have been uploaded? Answer: New uploads go to S3 automatically after changing the provider configuration. Existing files remain on the local filesystem. You must manually migrate them to S3 and update their database records.
Challenge: Set up three provider configurations (local for development, Cloudinary for staging, R2 for production) using environment variables: (1) Create separate
.envfiles for each environment, (2) Configureconfig/plugins.jsto read provider settings from environment variables, (3) Test uploads in each environment, (4) Verify file URLs are correct for each provider, (5) Write a Migration script that copies existing local files to the cloud provider.
FAQ
Mini Project
Your task: Configure and compare multiple upload providers.
- Start with the local provider and upload 3 images. Note the file URLs.
- Create an S3 bucket (or use a free Cloudinary account).
- Install and configure the S3 or Cloudinary provider.
- Upload 3 more images. Note the new URLs.
- Compare: response format, URLs, upload speed, and file Accessibility.
- Configure the provider to use environment variables for all settings.
- Write a small script that migrates files from the local provider to the cloud provider by fetching the old file and re-uploading it with the correct file ID.
What's Next
Now that you understand upload providers, proceed to Image Optimization to learn about responsive images, WebP format conversion, compression, and serving optimized images through your chosen provider. After that, explore File Management for advanced file operations.
Related lessons:
- Node.js File Upload — How uploads work programmatically
- REST API — Upload endpoints and file responses
- WordPress Media Library — Compare media management approaches
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro