Upload Plugin Configuration to store media in Cloudinary folder-wise in Strapi v4
Prerequisites
You have to install these before going forward if it is not on your machine already.
- Node.js: Only Active LTS or Maintenance LTS versions are supported (currently
v18andv20) - Node.js package manager: npm (
v6and above) or yarn - A supported database:
- A Cloudinary account
Step 1 — Create Strapi Project
Run this in your terminal where you want to create project.
// using npm
npx create-strapi-app@latest my-project
// using yarn
yarn create strapi-app my-projectThen you have to select options in the terminal for the initial configuration with database configurations. See Strapi documentation for more details.
Then CLI will automatically install the required packages.
You can run Strapi server by following command.
// using npm
npm run develop --watch-admin
// using yarn
yarn develop --watch-adminStep 2— Set up Cloudinary
First, you have to install @strapi/provider-upload-cloudinary package and run this in your terminal at your project’s location
// using npm
npm install @strapi/provider-upload-cloudinary
// using yarn
yarn add @strapi/provider-upload-cloudinaryAfter installation create a file named plugins.js inside the .config folder. And add the following code lines.
// .config/plugins.js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'cloudinary',
providerOptions: {
cloud_name: env('CLOUDINARY_NAME'),
api_key: env('CLOUDINARY_KEY'),
api_secret: env('CLOUDINARY_SECRET'),
},
actionOptions: {
uploadStream: {},
delete: {},
},
},
},
// ...
});Remember to set environment variables in the .env file in the root directory. You can find the Cloudinary name, API key, and API secret from the dashboard page in the Cloudinary account.
# .env
CLOUDINARY_NAME = cloudinary-name
CLOUDINARY_KEY = cloudinary-key
CLOUDINARY_SECRET = cloudinary-secretNow you can upload media to Cloudinary. You can check by creating a collection type with the Media field. You can see detailed information about how to create collection types from here.
But still, there is a problem. When uploading media, it will upload to Cloudinary, but on your Strapi admin, you can not able to preview the media file. You can fix this by configuring strapi::security middleware.
Add the following code in .config/middlewares.js.
// .config/middlewares.js
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'res.cloudinary.com'],
'media-src': ["'self'", 'data:', 'blob:', 'res.cloudinary.com'],
upgradeInsecureRequests: null,
},
},
},
},
// rest of middlewaresNow you can preview uploaded files from the admin panel as well.
But all the files are uploaded in the root folder in Cloudinary. Now we are going to store uploaded images in the /Strapi-app/Images folder and PDFs in the /Strapi-app/PDFs folder.
Step 3— Configure Cloudinary folder path to upload folder-wise
Add the following to the .env file.
# .env
CLOUDINARY_NAME = cloudinary-name
CLOUDINARY_KEY = cloudinary-key
CLOUDINARY_SECRET = cloudinary-secret
CLOUDINARY_FOLDER=Strapi-AppThen update the upload plugin configuration in the .config/plugins.js file as below.
// .config/plugins.js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'cloudinary',
providerOptions: {
cloud_name: env('CLOUDINARY_NAME'),
api_key: env('CLOUDINARY_KEY'),
api_secret: env('CLOUDINARY_SECRET'),
},
actionOptions: {
uploadStream: {
folder: env("CLOUDINARY_FOLDER"),
},
delete: {},
},
},
},
// ...
});Next, run the following command in the terminal.
// using npm
npm install @strapi/utils
// using yarn
yarn add @strapi/utilsAfter installing the package, create a file named uploadMiddleware.js in the src/middlewares folder and add the following to it.
"use-strict";
const { setCreatorFields } = require("@strapi/utils");
const setPathIdAndPath = async (folder) => {
const { max } = await strapi.db
.queryBuilder("plugin::upload.folder")
.max("pathId")
.first()
.execute();
const pathId = max + 1;
let parentPath = "/";
if (folder.parent) {
const parentFolder = await strapi.entityService.findOne(
"plugin::upload.folder",
folder.parent
);
parentPath = parentFolder.path;
}
return Object.assign(folder, {
pathId,
path: `${parentPath}/${pathId}`,
});
};
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
// Only run this middleware for the upload endpoint
if (ctx.url === "/upload" && ctx.method === "POST") {
if (ctx.request.body) {
try {
let folderName;
const rootFolder = process.env.CLOUDINARY_FOLDER;
// get upload plugin configuration
const config = strapi.config.get("plugin.upload");
// get file extension
const fileInfo = JSON.parse(ctx?.request?.body?.fileInfo);
const extension = fileInfo.name.split(".").pop().toLowerCase();
// config folder option to save in cloudinary
if (
extension === "jpg" ||
extension === "jpeg" ||
extension === "png"
) {
folderName = "Images";
config.actionOptions.uploadStream.folder = `${rootFolder}/${folderName}`;
} else if (extension === "pdf") {
folderName = "PDFs";
config.actionOptions.uploadStream.folder = `${rootFolder}/${folderName}`;
} else {
folderName = "Others";
config.actionOptions.uploadStream.folder = `${rootFolder}/${folderName}`;
}
// get folders in media library
const folders = await strapi.entityService.findMany(
"plugin::upload.folder"
);
// find folder from folders in media library
let folder = folders.find((folder) => folder.name === folderName);
// create folder to save in admin panel if not exist
if (!folder) {
const folderData = { name: folderName, parent: null };
const user = ctx.state.user;
let enrichedFolder = await setPathIdAndPath(folderData);
if (user) {
enrichedFolder = await setCreatorFields({ user })(enrichedFolder);
}
folder = await strapi.entityService.create(
"plugin::upload.folder",
{ data: enrichedFolder }
);
}
// set folder option in request to save in admin panel
fileInfo.folder = folder.id;
ctx.request.body.fileInfo = JSON.stringify(fileInfo);
} catch (error) {
console.error(
"Error parsing or modifying fileInfo or Configurations:",
error
);
}
}
}
// Continue to the next middleware
await next();
};
};You have to add this global middleware to run in .config/middlewares.js. So, update the .config/middlewares.js file as below.
// .config/middlewares.js
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'res.cloudinary.com'],
'media-src': ["'self'", 'data:', 'blob:', 'res.cloudinary.com'],
upgradeInsecureRequests: null,
},
},
},
},
// rest of middlewares
"global::uploadMiddleware"Now, when you upload a media file, it will be stored in Cloudinary according to file type. (image → Strapi-app/Images, PDF → Strapi-app/PDFs). Also, you can see there will be created Images folder and PDFs folder in the Media Library in the admin panel of Strapi.
Happy coding…!!
