Integrating headless media services in Strapi ensures that videos, images, and other large files are stored, processed, and delivered efficiently. Local storage is fine for testing. But in production, it creates problems, such as limited disk space, slow delivery, and missing optimizations like streaming or global caching. Connecting Strapi to a cloud media service solves these issues by separating media management from your application code.
Prerequisites
- A running Strapi project
- Node.js v18+ installed (use node -v to check your Node version)
- An account with a storage provider (AWS S3, GCP, etc.)
- API Credentials and Bucket/Container Details
Install Provider Plugins
Strapi needs a provider plugin to upload files to external storage instead of the local filesystem. For AWS S3, install:
npm install @strapi/provider-upload-aws-s3Other providers (GCP, Azure, DigitalOcean) have their own plugins.
Configure Provider Settings
After installing a plugin, integrate Strapi to the storage provider by updating config/plugins.js. This integrates Strapi"s upload system with your external storage account.
module.exports = ({ env }) => ({upload: {config: {provider: 'aws-s3',providerOptions: {accessKeyId: env('AWS_ACCESS_KEY_ID'),secretAccessKey: env('AWS_ACCESS_SECRET'),region: env('AWS_REGION'),params: { Bucket: env('AWS_BUCKET') },},},},});Set Environment Variables
Store provider credentials and settings in .env so they"re not hardcoded:
AWS_ACCESS_KEY_ID=your-access-key-idAWS_SECRET_ACCESS_KEY=your-secret-access-keyAWS_REGION=your-regionAWS_BUCKET=your-bucket-nameAfter this, all Strapi uploads go directly to the external provider.
Increase Upload Limits
Large media files (e.g., videos) often exceed default request limits. You can increase limits in config/middlewares.js:
module.exports = ['strapi::errors',{name: 'strapi::body',config: { formLimit: '200mb', jsonLimit: '200mb', textLimit: '200mb' },},'strapi::security','strapi::cors',];Create a Media Collection Type
Define a collection for videos or images with fields for metadata. This makes media files queryable via API. Example (video):
{"kind": "collectionType","collectionName": "videos","attributes": {"title": { "type": "string" },"video": {"type": "media","multiple": false,"required": true,"allowedTypes": ["videos"]}}}Upload Media via Admin Panel
When files are uploaded in the Strapi admin panel, they are stored in the configured provider. Strapi records metadata and the storage URL:
{"id": 1,"title": "Sample Video","video": {"url": "https://your-bucket.s3.amazonaws.com/file.mp4","mime": "video/mp4","provider": "aws-s3"}}Optimize Media Delivery with a CDN
Storage ensures persistence, but delivery performance requires a Content Delivery Network (CDN). A CDN caches files on edge servers worldwide so users download from the closest location, reducing latency and handling traffic spikes.
Integrate a CDN
Without a CDN, every media request hits your storage bucket directly. It slows down delivery for users far from your server"s region and risks bottlenecks during traffic spikes. Integrating a CDN ensures media is cached and served from edge locations worldwide.
Step 1: Create a CDN distribution (e.g., CloudFront for S3, Azure CDN for Blob).
Step 2: Connect it to your storage bucket.
Step 3: Configure caching rules and CORS headers.
Step 4: Replace Strapi"s default media URLs with CDN URLs.
Configure CDN URLs
By default, Strapi returns storage URLs. To serve files through your CDN, you can create a custom global middleware. Create middleware in ./src/middlewares/cdn-url/index.js:
// ./src/middlewares/cdn-url.jsmodule.exports = (config, { strapi }) => {return async (ctx, next) => {await next();
if (ctx.response?.body) {const rewriteUrl = (item) => {if (item?.url?.startsWith('/uploads/')) {item.url = `https://your-cdn-domain.com${item.url}`;}};
const rewriteMedia = (data) => {if (Array.isArray(data)) {data.forEach(rewriteMedia);} else if (data?.attributes) {for (const key in data.attributes) {const field = data.attributes[key];if (field?.data) {if (Array.isArray(field.data)) {field.data.forEach((d) => rewriteUrl(d.attributes));} else {rewriteUrl(field.data.attributes);}}}}};
rewriteMedia(ctx.response.body.data);}};};Enable it in config/middlewares.js:
module.exports = [// other middlewares'global::cdn-url',];This ensures all media URLs returned by Strapi go through your CDN.
