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:

code
npm install @strapi/provider-upload-aws-s3

Other 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.

code
module.exports = ({ env }) => ({
code
upload: {
code
config: {
code
provider: 'aws-s3',
code
providerOptions: {
code
accessKeyId: env('AWS_ACCESS_KEY_ID'),
code
secretAccessKey: env('AWS_ACCESS_SECRET'),
code
region: env('AWS_REGION'),
code
params: { Bucket: env('AWS_BUCKET') },
code
},
code
},
code
},
code
});

Set Environment Variables

Store provider credentials and settings in .env so they"re not hardcoded:

code
AWS_ACCESS_KEY_ID=your-access-key-id
code
AWS_SECRET_ACCESS_KEY=your-secret-access-key
code
AWS_REGION=your-region
code
AWS_BUCKET=your-bucket-name

After 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:

code
module.exports = [
code
'strapi::errors',
code
{
code
name: 'strapi::body',
code
config: { formLimit: '200mb', jsonLimit: '200mb', textLimit: '200mb' },
code
},
code
'strapi::security',
code
'strapi::cors',
code
];

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):

code
{
code
"kind": "collectionType",
code
"collectionName": "videos",
code
"attributes": {
code
"title": { "type": "string" },
code
"video": {
code
"type": "media",
code
"multiple": false,
code
"required": true,
code
"allowedTypes": ["videos"]
code
}
code
}
code
}

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:

code
{
code
"id": 1,
code
"title": "Sample Video",
code
"video": {
code
"url": "https://your-bucket.s3.amazonaws.com/file.mp4",
code
"mime": "video/mp4",
code
"provider": "aws-s3"
code
}
code
}

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:

code
// ./src/middlewares/cdn-url.js
code
module.exports = (config, { strapi }) => {
code
return async (ctx, next) => {
code
await next();
code

code
if (ctx.response?.body) {
code
const rewriteUrl = (item) => {
code
if (item?.url?.startsWith('/uploads/')) {
code
item.url = `https://your-cdn-domain.com${item.url}`;
code
}
code
};
code

code
const rewriteMedia = (data) => {
code
if (Array.isArray(data)) {
code
data.forEach(rewriteMedia);
code
} else if (data?.attributes) {
code
for (const key in data.attributes) {
code
const field = data.attributes[key];
code
if (field?.data) {
code
if (Array.isArray(field.data)) {
code
field.data.forEach((d) => rewriteUrl(d.attributes));
code
} else {
code
rewriteUrl(field.data.attributes);
code
}
code
}
code
}
code
}
code
};
code

code
rewriteMedia(ctx.response.body.data);
code
}
code
};
code
};

Enable it in config/middlewares.js:

code
module.exports = [
code
// other middlewares
code
'global::cdn-url',
code
];

This ensures all media URLs returned by Strapi go through your CDN.