These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Cloudinary?
Cloudinary is a cloud-based platform that provides solutions for managing and optimizing media assets like images and videos. Cloudinary offers a comprehensive set of tools for image and video uploading, storage, transformation, and delivery.
Cloudinary's services allow developers to automate tasks like resizing, cropping, and applying effects, making it easier to manage media in web and mobile applications. The platform also integrates with various content management systems (CMS) and frameworks. With its advanced optimization capabilities, Cloudinary helps reduce the load time of media-rich websites, ensuring faster performance and improved user experience.
Why Integrate Cloudinary with Strapi
Integrating Cloudinary with Strapi creates a powerful technical foundation with several key benefits:
- Automated optimization: Cloudinary automatically handles file formats, device-specific sizing, and intelligent cropping. While traditional methods to optimize images with PHP require significant effort, Cloudinary automates the process, saving valuable development time.
- Performance boost: Global CDN network reduces latency for users worldwide.
- Resource efficiency: Offloads media handling, freeing server resources for core application functions.
- Workflow improvements: Strapi's admin interface, part of this leading open-source CMS, works seamlessly with Cloudinary's media tools, enhancing efficiency and enabling multi-platform publishing.
- Unlimited scalability: Removes local storage constraints as your media library grows.
- Technical flexibility: Combines Strapi's headless CMS capabilities, including Strapi API integrations, with Cloudinary's robust media APIs.
- Simplified development: Provides ready-to-use CDN links, eliminating manual optimization steps.
Cloudinary’s integration with Strapi excels for content-rich applications like e-commerce platforms, media-intensive blogs, and complex web applications. For instance, in e-commerce, leveraging AI tools for eCommerce alongside this integration can significantly enhance user experiences.
Keep in touch with the latest Strapi and Cloudinary updates
How to Integrate Cloudinary with Strapi
In this section, we will walk you through the steps required to integrate Cloudinary with your Strapi project.
Prerequisites
Before starting to integrate Cloudinary with Strapi, make sure you have:
- Node.js version 14 or higher installed
- NPM or Yarn package manager
- A Cloudinary account (you can start with a free tier)
- A Strapi application (version 4 or later)
Setting Up Your Cloudinary Account
- Sign up for a Cloudinary account at Cloudinary's website.
- Confirm your email address via the verification link sent to you.
- Once logged in, you'll be redirected to your account management dashboard.
- Locate your API credentials (Cloud Name, API Key, and API Secret) in the dashboard. You'll need these for the integration.
Installing the Cloudinary Provider in Strapi
To integrate Cloudinary with your Strapi project, you need to install the Cloudinary provider package:
1npm install @strapi/provider-upload-cloudinary
Or if you prefer using Yarn:
1yarn add @strapi/provider-upload-cloudinary
Configuring Environment Variables
Create or edit the .env
file at the root of your Strapi project and add the following variables:
1CLOUDINARY_NAME=your_cloud_name
2CLOUDINARY_KEY=your_api_key
3CLOUDINARY_SECRET=your_api_secret
Replace the placeholder values with your actual Cloudinary credentials.
Setting Up Strapi Upload Provider to Integrate with Cloudinary
Create or edit the ./config/plugins.js
file in your Strapi project and add the following configuration:
1module.exports = ({ env }) => ({
2 upload: {
3 config: {
4 provider: 'cloudinary',
5 providerOptions: {
6 cloud_name: env('CLOUDINARY_NAME'),
7 api_key: env('CLOUDINARY_KEY'),
8 api_secret: env('CLOUDINARY_SECRET'),
9 },
10 actionOptions: {
11 upload: {},
12 delete: {},
13 },
14 },
15 },
16});
This configuration tells Strapi to use Cloudinary as the upload provider instead of the default local storage.
Testing the Integration
1. Restart your Strapi server to apply the changes:
1npm run develop
Or with Yarn:
1yarn develop
2. Access your Strapi admin panel (typically at http://localhost:1337/admin).
3. Navigate to the Media Library in the sidebar menu.
4. Upload a test image by clicking "Add new assets" and selecting a file from your computer.
5. Verify that the upload was successful:
- Check for a 200 status code in your terminal.
- Confirm that the image appears in both your Strapi Media Library and your Cloudinary dashboard.
If you encounter any issues with image previews in the Strapi admin panel, you may need to configure CORS settings in your Cloudinary account:
- Go to the Delivery or Access Control section in your Cloudinary dashboard settings.
- Add your Strapi domain to the allowed CORS origins.
Additionally, explore new Strapi features like live preview and on-the-fly relations to enhance your development experience.
Keep in touch with the latest Strapi and Cloudinary updates
Project Example: How to Integrate Cloudinary with Strapi in a Media Gallery Application
Let's examine a practical implementation of integrating Cloudinary with Strapi through a media gallery application. Consider a photo-sharing platform where users upload, browse, and share high-quality images. Using Strapi as the content backend and integrating Cloudinary for media processing creates a robust technical foundation that scales effectively.
Example Code Implementation
1. Strapi Content Type Configuration (Gallery Collection)
Create a content type for photo galleries in Strapi:
1// Path: src/api/gallery/content-types/gallery/schema.json
2
3{
4 "kind": "collectionType",
5 "collectionName": "galleries",
6 "info": {
7 "singularName": "gallery",
8 "pluralName": "galleries",
9 "displayName": "Photo Gallery",
10 "description": "A collection of curated images"
11 },
12 "options": {
13 "draftAndPublish": true
14 },
15 "attributes": {
16 "title": {
17 "type": "string",
18 "required": true
19 },
20 "description": {
21 "type": "text"
22 },
23 "photos": {
24 "type": "media",
25 "multiple": true,
26 "required": true,
27 "allowedTypes": ["images"]
28 },
29 "category": {
30 "type": "enumeration",
31 "enum": ["nature", "architecture", "people", "travel", "abstract"],
32 "default": "nature"
33 },
34 "slug": {
35 "type": "uid",
36 "targetField": "title"
37 },
38 "featured": {
39 "type": "boolean",
40 "default": false
41 }
42 }
43}
2. Frontend Upload Component (React)
This component handles image uploads to Strapi, which then uses Cloudinary for storage:
1// ImageUploader.jsx
2
3import React, { useState } from 'react';
4import axios from 'axios';
5
6const ImageUploader = ({ galleryId, onUploadComplete }) => {
7 const [files, setFiles] = useState([]);
8 const [uploading, setUploading] = useState(false);
9 const [progress, setProgress] = useState(0);
10
11 const handleFileChange = (e) => {
12 setFiles(Array.from(e.target.files));
13 };
14
15 const handleSubmit = async (e) => {
16 e.preventDefault();
17 if (files.length === 0) return;
18
19 setUploading(true);
20 setProgress(0);
21
22 // Create FormData object
23 const formData = new FormData();
24 files.forEach(file => {
25 formData.append('files', file);
26 });
27
28 // If uploading to a specific gallery
29 formData.append('ref', 'api::gallery.gallery');
30 formData.append('refId', galleryId);
31 formData.append('field', 'photos');
32
33 try {
34 const response = await axios.post(`${process.env.REACT_APP_STRAPI_URL}/api/upload`, formData, {
35 headers: {
36 'Content-Type': 'multipart/form-data',
37 Authorization: `Bearer ${localStorage.getItem('token')}`,
38 },
39 onUploadProgress: (progressEvent) => {
40 const percentCompleted = Math.round(
41 (progressEvent.loaded * 100) / progressEvent.total
42 );
43 setProgress(percentCompleted);
44 },
45 });
46
47 onUploadComplete(response.data);
48 setFiles([]);
49 setUploading(false);
50 } catch (error) {
51 console.error('Upload failed:', error);
52 setUploading(false);
53 }
54 };
55
56 return (
57 <div className="uploader-container">
58 <form onSubmit={handleSubmit}>
59 <div className="file-input-wrapper">
60 <input
61 type="file"
62 multiple
63 accept="image/*"
64 onChange={handleFileChange}
65 disabled={uploading}
66 />
67 <label>
68 {files.length > 0
69 ? `${files.length} file(s) selected`
70 : 'Choose images to upload'}
71 </label>
72 </div>
73
74 {files.length > 0 && (
75 <div className="selected-files">
76 <h4>Selected Files:</h4>
77 <ul>
78 {files.map((file, index) => (
79 <li key={index}>{file.name} ({Math.round(file.size / 1024)} KB)</li>
80 ))}
81 </ul>
82 </div>
83 )}
84
85 {uploading && (
86 <div className="progress-bar">
87 <div
88 className="progress"
89 style={{ width: `${progress}%` }}
90 ></div>
91 <span>{progress}%</span>
92 </div>
93 )}
94
95 <button
96 type="submit"
97 disabled={files.length === 0 || uploading}
98 className="upload-button"
99 >
100 {uploading ? 'Uploading...' : 'Upload Images'}
101 </button>
102 </form>
103 </div>
104 );
105};
106
107export default ImageUploader;
3. Displaying Responsive Images with Cloudinary Transformations
Leverage Cloudinary URLs for on-the-fly image transformations:
1// ResponsiveGallery.jsx
2import React, { useEffect, useState } from 'react';
3import axios from 'axios';
4
5const ResponsiveGallery = ({ galleryId }) => {
6 const [images, setImages] = useState([]);
7 const [loading, setLoading] = useState(true);
8
9 useEffect(() => {
10 const fetchGalleryImages = async () => {
11 try {
12 const response = await axios.get(
13 `${process.env.REACT_APP_STRAPI_URL}/api/galleries/${galleryId}?populate=photos`
14 );
15
16 if (response.data.data.photos.data) {
17 setImages(response.data.data.photos.data);
18 }
19 setLoading(false);
20 } catch (error) {
21 console.error('Failed to fetch gallery images:', error);
22 setLoading(false);
23 }
24 };
25
26 fetchGalleryImages();
27 }, [galleryId]);
28
29 // Function to transform Cloudinary URLs for responsive images
30 const getResponsiveImageUrl = (url, width, height, options = {}) => {
31 // Extract the base Cloudinary URL
32 if (!url || !url.includes('cloudinary')) return url;
33
34 // Parse the existing URL to maintain the cloud name and other parameters
35 const urlParts = url.split('/upload/');
36 if (urlParts.length !== 2) return url;
37
38 // Construct transformation string
39 let transformation = `c_fill,w_${width},h_${height}`;
40
41 // Add quality parameter if specified
42 if (options.quality) {
43 transformation += `,q_${options.quality}`;
44 }
45
46 // Add format parameter if specified
47 if (options.format) {
48 transformation += `,f_${options.format}`;
49 }
50
51 // Return the transformed URL
52 return `${urlParts[0]}/upload/${transformation}/${urlParts[1]}`;
53 };
54
55 if (loading) {
56 return <div className="loading">Loading gallery...</div>;
57 }
58
59 if (images.length === 0) {
60 return <div className="no-images">No images found in this gallery</div>;
61 }
62
63 return (
64 <div className="responsive-gallery">
65 <div className="gallery-grid">
66 {images.map((image) => {
67 const imageUrl = image.url;
68
69 return (
70 <div key={image.id} className="gallery-item">
71 <picture>
72 {/* Mobile devices */}
73 <source
74 media="(max-width: 640px)"
75 srcSet={getResponsiveImageUrl(imageUrl, 300, 300, { quality: 'auto', format: 'webp' })}
76 />
77
78 {/* Tablets */}
79 <source
80 media="(max-width: 1024px)"
81 srcSet={getResponsiveImageUrl(imageUrl, 500, 500, { quality: 'auto', format: 'webp' })}
82 />
83
84 {/* Desktops */}
85 <source
86 srcSet={getResponsiveImageUrl(imageUrl, 800, 800, { quality: 'auto', format: 'webp' })}
87 />
88
89 {/* Fallback image */}
90 <img
91 src={getResponsiveImageUrl(imageUrl, 500, 500)}
92 alt={image.alternativeText || 'Gallery image'}
93 loading="lazy"
94 className="gallery-image"
95 />
96 </picture>
97 {image.caption && (
98 <div className="image-caption">{image.caption}</div>
99 )}
100 </div>
101 );
102 })}
103 </div>
104 </div>
105 );
106};
107
108export default ResponsiveGallery;
4. Server-Side Processing for Album Creation
1// Path: src/api/gallery/controllers/gallery.js
2'use strict';
3
4/**
5 * Custom controller for gallery management
6 */
7
8const { createCoreController } = require('@strapi/strapi').factories;
9
10module.exports = createCoreController('api::gallery.gallery', ({ strapi }) => ({
11 // Custom create method with metadata extraction
12 async create(ctx) {
13 try {
14 // Extract and validate the request body
15 const { title, description, category } = ctx.request.body;
16
17 // Create a gallery entry first
18 const gallery = await strapi.entityService.create('api::gallery.gallery', {
19 data: {
20 title,
21 description,
22 category,
23 slug: title.toLowerCase().replace(/\s+/g, '-'),
24 publishedAt: new Date(),
25 },
26 });
27
28 return { data: gallery };
29 } catch (error) {
30 ctx.body = error;
31 return ctx.badRequest(`Gallery creation failed: ${error.message}`);
32 }
33 },
34
35 // Custom method to fetch galleries with optimized images
36 async findFeatured(ctx) {
37 try {
38 const galleries = await strapi.entityService.findMany('api::gallery.gallery', {
39 filters: { featured: true },
40 populate: ['photos'],
41 sort: { createdAt: 'desc' },
42 limit: 6,
43 });
44
45 // Transform the response to include optimized preview URLs
46 const transformed = galleries.map(gallery => {
47 const { id, title, description, slug, photos } = gallery;
48
49 // Get cover image (first image or default)
50 const coverImage = photos && photos.length > 0
51 ? photos[0]
52 : null;
53
54 // Get Cloudinary-optimized thumbnail URL if available
55 let thumbnailUrl = null;
56 if (coverImage && coverImage.url && coverImage.url.includes('cloudinary')) {
57 const urlParts = coverImage.url.split('/upload/');
58 thumbnailUrl = `${urlParts[0]}/upload/c_fill,w_400,h_300/${urlParts[1]}`;
59 }
60
61 return {
62 id,
63 title,
64 description,
65 slug,
66 photoCount: photos ? photos.length : 0,
67 thumbnailUrl
68 };
69 });
70
71 return { data: transformed };
72 } catch (error) {
73 return ctx.badRequest(`Failed to fetch featured galleries: ${error.message}`);
74 }
75 }
76}));
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.
For more details, visit the Strapi documentation and the Cloudinary documentation.
Frequently Asked Questions
What are the prerequisites for integrating Cloudinary with Strapi?
Before integrating Cloudinary with Strapi, ensure you have Node.js version 14 or higher, NPM or Yarn package manager, a Cloudinary account, and a Strapi application version 4 or later.
How can I set up my Cloudinary account for integration with Strapi?
To set up your Cloudinary account, sign up at Cloudinary's website, verify your email address, and locate your API credentials (Cloud Name, API Key, and API Secret) in your dashboard. These credentials are necessary for the integration with Strapi.
How do I install the Cloudinary provider in Strapi?
Install the Cloudinary provider package in your Strapi project using NPM (npm install @strapi/provider-upload-cloudinary
) or Yarn (yarn add @strapi/provider-upload-cloudinary
).
How do I configure environment variables for Cloudinary in Strapi?
Create or edit the .env
file at the root of your Strapi project and add your Cloudinary credentials (CLOUDINARY_NAME
, CLOUDINARY_KEY
, CLOUDINARY_SECRET
) to it, replacing the placeholder values with your actual Cloudinary credentials.
What steps are involved in setting up Strapi to use Cloudinary as the upload provider?
To use Cloudinary as the upload provider, create or edit the ./config/plugins.js
file in your Strapi project with the configuration that specifies Cloudinary as the provider and includes your Cloudinary credentials and options for upload and delete actions.
What advanced configuration options are available for Cloudinary integration with Strapi?
Advanced configuration options include setting custom folder structures in Cloudinary for organized asset management, specifying transformation options for automatic image processing, and configuring security settings to load assets from Cloudinary's domain securely.
Where can I get help if I encounter issues when integrating Cloudinary with Strapi?
For personalized guidance, participate in Strapi Open Office Hours for direct access to technical experts, or connect with the Strapi community through forums, Discord, and GitHub Issues for technical discussions and problem-solving.