Add Cloudinary Support to Your Strapi Application
Published: 2022-01-24 18:02:01

Author: Precious Luke

## Description

In today's article, I will show you how to simply change the default upload provider of your Strapi application to [Cloudinary](https://cloudinary.com/). It is really easy and very straightforward. I will be showing you how to connect your Strapi application to use Cloudinary as a storage place for assets(photos and videos)

## **Prerequisites**

To follow along with this tutorial you will need to have the following installed on your machine:

- [Node.js](https://nodejs.org/en/) V14+
- [N](https://www.npmjs.com/)[PM](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/)
- A [Cloudinary](https://cloudinary.com/) account
## **What is Cloudinary?**

As stated on their [FAQs](https://cloudinary.com/faq) page, Cloudinary is a very useful image and video management solution for websites and mobile apps. It covers everything from image and video uploads, storage, manipulations, optimizations to delivery. Cloudinary can deliver up to terabytes of data through a fast [Content Delivery Network](https://en.wikipedia.org/wiki/Content_delivery_network) (CDN).

## **Step 1 - Set up Cloudinary**

One of the first things you will need to do is to create a free account on Cloudinary. Once you managed to create your free account, you will be asked to confirm your email address. After confirmation, subsequently, whenever you log in,  you will be redirected to the management dashboard of your account.


![Cloudinary-dashboard](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1639669959728_2021-12-16_16-52.png)


On the dashboard page you will find your **Account Details** that you will need to save for later:

- **Cloud** **N****ame**
- **API Key**
- **API Secret**

Make sure to keep these details **secret** and do not share them with anyone.

## **Step 2 - Back-end Set up**

Open up a terminal of your choice and navigate to the folder you want your project stored. I will create mine under C:\Projects, but you can store it wherever you want.
To follow along with this tutorial you will need an already up and running Strapi project or simply create one with the following command in your terminal:


    yarn create strapi-app strapi-cloudinary --quickstart

Once the creation of your project is completed you will be prompted to `<http://localhost:1337/admin>` to create the first admin user for your project, after the creation, you will be logged in to the administration panel.

After you managed to get into your administration panel, head to [Content-Types Builder](http://localhost:1337/admin/plugins/content-type-builder) and create a collection-type called **Posts**. 
Create required fields in order to store post related data for our blog, in this case, we are having these fields:

- **title**: text (short text)
- **published_date**: datetime
- **cover:** media (single media)
- **content**: rich text
![Adding collection types](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638860916007_Main+post.png)





![Adding fields in Strapi](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638861038346_trrs.png)



After you have added every field, hit **Save.** This will restart the server.


![Save fields](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638861198357_saved.png)



Now make sure to add permissions to your **Posts** collection-type so it will be available to unauthenticated users on your site. Go under [**Settings → Roles**](http://localhost:1337/admin/settings/users-permissions/roles) **( Users & Permissions plugin ) → Public** and simply check the boxes next to **find** and **findOne** actions for the **posts** endpoint**.** Click **Save** when done.


![Users & Permission settings](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638861321151_firstrole.png)



![Adding permissions for the Post content type](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638861663829_permit.png)


Now that we have our project set up, open the project's folder with your favorite IDE and install **strapi-provider-upload-cloudinary** package to your project.
Run this in your terminal at your project's location and you should be good to go:

`yarn add @strapi-provider-upload-cloudinary` 


Make sure your Strapi server is not running at the moment of installation, you can restart it right after  by running `npm run develop`.
After completion, create the following file `./config/plugins.js` and add the below lines of code to it:


    module.exports = ({ env }) => ({
      // ...
      upload: {
        config: {
          provider: 'cloudinary',
          providerOptions: {
            cloud_name: env('CLOUDINARY_NAME'),
            api_key: env('CLOUDINARY_KEY'),
            api_secret: env('CLOUDINARY_SECRET'),
          },
          actionOptions: {
            upload: {},
            delete: {},
          },
        },
      },
      // ...
    });

Under the root directory of your project create a .env file and add the following variables and with their respective values. These can be found in your Cloudinary dashboard under **Account Details**:


    CLOUDINARY_NAME = cloudinary-name
    CLOUDINARY_KEY = cloudinary-key
    CLOUDINARY_SECRET = cloudinary-secret



## **Step 3- Adding Some Post**

Before we move ahead, we need add some contents. Go to [Content Manager](http://localhost:1337/admin/content-manager), and add some entires. Make sure you **Save** and **Publish.**


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638881201611_publishing.png)


I will add three entries of post. 


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638882166058_publised.png)

## Step 4 - Fixing Preview Issue on Strapi Admin

After uploading a photo, it will upload to Cloudinary, but on your Strapi admin, you wont be able to preview the photo.  You can fix this by replacing `strapi::security` string with the object below in `./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,
            },
          },
        },
      },

It should look like so:


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638882971347_fixed.png)


Go to this URL on your browser: http://localhost:1337/api/posts?populate=*


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1638946398633_api.png)




## Step 5 - Fetching Data with GraphQL

We can also use GraphQL an alternative to REST to fetch data. To do that, first, install Graphql with this `npm run strapi install graphql`.
After that is done, go to http://localhost:1337/graphql. 


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1639378292070_2021-12-13_07-51.png)


You can start querying the data, to do so, input this in the GraphQL player environment and hit the Play button:


      query Post {
        posts {
          data {
            id
            attributes {
              title
              content
              published_date
              cover {
                data {
                  id
                  attributes {
                    url
                  }
                }
              }
            }
          }
        }
      }

Response should look like so:


    {
      "data": {
        "posts": {
          "data": [
            {
              "id": "4",
              "attributes": {
                "title": "J cole",
                "content": "This is a realcoleworld. This is a realcoleworld. This is a realcoleworld. This is a realcoleworld. This is a realcoleworld. This is a realcoleworld.",
                "published_date": "2021-12-05T23:00:00.000Z",
                "cover": {
                  "data": {
                    "id": "4",
                    "attributes": {
                      "url": "https://res.cloudinary.com/localhost101/image/upload/v1638876030/J_Cole_3842b0a6a6.jpg"
                    }
                  }
                }
              }
            },
            {
              "id": "5",
              "attributes": {
                "title": "Kendrick Lamar",
                "content": "Just let me be me. Just let me be me. Just let me be me. Just let me be me. Just let me be me.",
                "published_date": "2021-12-14T23:00:00.000Z",
                "cover": {
                  "data": {
                    "id": "5",
                    "attributes": {
                      "url": "https://res.cloudinary.com/localhost101/image/upload/v1638881415/kendr_3883e77ac9.jpg"
                    }
                  }
                }
              }
            },
            {
              "id": "6",
              "attributes": {
                "title": "Davido",
                "content": "Davido is the baddest. Davido is the baddest. Davido is the baddest. Davido is the baddest.Davido is the baddest.",
                "published_date": "2021-12-13T23:00:00.000Z",
                "cover": {
                  "data": {
                    "id": "6",
                    "attributes": {
                      "url": "https://res.cloudinary.com/localhost101/image/upload/v1638881498/davido_d54888ae15.webp"
                    }
                  }
                }
              }
            }
          ]
        }
      }
    }


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1639379100684_2021-12-13_08-04.png)


****
## Step 6 - Adding Frontend

Great, now let's begin by creating our front-end application so we can display our images. I will be using [Next](http://next.org)[.js](https://nextjs.org) for this tutorial but you can use any front-end framework you are comfortable with.
Inside a terminal of your choice create a folder where you would like your project to be stored and navigate to it.
One of the first things you will need to do is to install the create-next-app package globally by running one of the following commands:

`yarn global add @create-next-app/core`

or

`npm install --global @create-next-app/core`

Once everything is installed successfully you can start creating your NextJS application. The following line will save your time and will set up a quickstart template.

`yarn create next-app frontend-cloudinary`

To make sure everything installed correctly simply start your app with:

`yarn dev`
Before continuing the development let's install a few more dependencies that will be used throughout the application.
`yarn add @apollo/react-hooks apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag isomorphic-unfetch next-with-apollo`
I will be using Apollo in order to interact with the GraphQL endpoint from the Strapi server. 

`@apollo/react-hooks` is needed because we will be using React’s [useQuery](https://react-query.tanstack.com/guides/queries).
`[apollo-cache-inmemory](https://www.npmjs.com/package/apollo-cache-inmemory)` is used because we want to make use of caching.  `[apollo-client](https://www.apollographql.com/docs/react/)` is a GraphQL state management library for managing both local and remote data.  `[apollo-link-http](https://www.npmjs.com/package/apollo-link-http)`  is the most common Apollo Link, a system of modular components for GraphQL networking.  `[graphql-tag](https://www.npmjs.com/package/graphql-tag)` is JavaScript template literal tag that parses GraphQL query strings into the standard GraphQL AST. This requires us to install `graphql` because it uses it under the hood. `[isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch)` is needed because it helps to switches between [unfetch](https://github.com/developit/unfetch) & [node-fetch](https://github.com/bitinn/node-fetch) for client & server.  `[next-with-apollo](https://github.com/lfades/next-with-apollo)` is for Apollo higher order component.


The following dependencies are important. Under the root directory of your front-end app create the following file `./utils/apollo.js` and paste the following code inside:


    import { ApolloClient } from "apollo-client";
    import { InMemoryCache } from "apollo-cache-inmemory";
    import withApollo from "next-with-apollo";
    import { createHttpLink } from "apollo-link-http";
    import fetch from "isomorphic-unfetch";
    
    const link = createHttpLink({
      fetch, 
      uri: "http://localhost:1337/graphql"
    });
    
    export default withApollo(
      ({ initialState }) => new ApolloClient({
          link: link,
          cache: new InMemoryCache().restore(initialState || {})
        })
    );

From line 1 to 5, we imported the from the dependencies we had just installed.  From line 7 to 10, we created a constant variable *link* which uses a method  *createHttpLink* from `apollo-link-http`. What this does is it fetches the data from the url: http://localhost:1337/graphql.

From line 12 to 17, *withApollo* is exported.

Under the root directory of your front-end app create the following file `./utils/apollo.js` and paste the following code in this file:


    import '../styles/globals.css'
    import React from "react";
    import { ApolloProvider } from "@apollo/react-hooks";
    import withData from "../utils/apollo";
    
    function MyApp({ Component, pageProps, apollo }) {
      return (
        <ApolloProvider client={apollo}>
          <Component {...pageProps} />
        </ApolloProvider>
      )
    }
    
    export default withData(MyApp)

The `Component` prop is the active `page`, so whenever you navigate between routes, `Component` will change to the new `page`. Therefore, any props you send to `Component` will be received by the `page`.
[For more details about the](https://nextjs.org/docs/advanced-features/custom-app) `[_app.js](https://nextjs.org/docs/advanced-features/custom-app)` [file read](https://nextjs.org/docs/advanced-features/custom-app) [here.](https://nextjs.org/docs/advanced-features/custom-app)

One of the next steps is to create a reusable **Query** component so that we can use it anywhere in our app. It will wrap your other components so that each time you will need to fetch data from your GraphQL endpoint, it will pass down the data as props to the children components.

Under the root directory of your front-end app create the following file `./components/query.js` and paste the following code inside:



    import React from "react";
    import { useQuery } from "@apollo/client";
    const Query = ({ children, query }) => {
      const { data, loading, error } = useQuery(query);
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error: {JSON.stringify(error)}</p>;
      return children({data} );
    };
    export default Query;

One last thing to do before putting everything together is to create the actual GraphQL query. Create the following file `./apollo/queries/posts/posts.js` and add the below code inside it:


    import gql from "graphql-tag";
    
    const POSTS_QUERY = gql`
    query Posts {
        posts {
          id
          title
          content
          published_at
          cover {
            url
          }
        }
      }
    `;
    
    export default POSTS_QUERY;

This is simply targeting the needed response based on the data structure we have on our Strapi collection. We are importing *gql* from `graphql-tag`  for this. Then we went ahead to query the posts collections and its fields.

Now that we have everything ready for our app, let's start by setting it up together.
You will use the Next's Image component to render the images hosted on Cloudinary.
The Image component is provided by Next.js as of version 10.0.0. It allows for [Automatic Image Optimization](https://nextjs.org/docs/basic-features/image-optimization#loaders) that will work with any image source you like. You will have optimized images even if you host them on an external data source, like in our case.

In order to have your images from Cloudinary optimized you will have to make the following changes to the next.config.js file:


    module.exports = {
        images: {
          domains: ["res.cloudinary.com"],
        },
      }


On the other hand, if you don't want your image to be optimized by the component you could set the in-optimized prop to true. Like so, the configuration above is not mandatory.
The code in your `index.js` file can simply be overwritten by the following:


    
    import Head from 'next/head'
    import styles from '../styles/Home.module.css'
    import Query from "../components/query";
    import POSTS_QUERY from "../apollo/queries/posts/posts";
    import Image from 'next/image'
    
    export default function Home() {
      return (
        <div className={styles.container}>
          <Query query = {POSTS_QUERY}>
            {({data:posts}) => {
              {const coder = posts.posts.data}
              {console.log(coder)}
              
                return(
                <div>
                  <Head>
                    <title>Create Next App</title>
                    <link rel="icon" href="/favicon.ico" />
                  </Head>
                
     
                  <main className={styles.main}>
                    <h1 className={styles.title}>
                      Welcome to <a href="https://nextjs.org">Next.js!</a>
                    </h1>
                    <p className={styles.description}>
                      Get started by editing{' '}
                      <code className={styles.code}>pages/index.js</code>
                    </p>
                    <div className = {styles.grid}> 
                     
                      {coder.map((post) => (
                        
                          <div className = {styles.card}>
                            <div className = {styles.image}>
                              <Image src = {post.attributes.cover.data.attributes.url} width={150} height={150}></Image>
                            </div>
                            <h1>{post.attributes.title}</h1>
                           
                          </div>
                        
                     ))}
                    </div>
                  </main>
                  <footer className={styles.footer}>
                    <a
                      href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
                      target="_blank"
                      rel="noopener noreferrer"
                    >
                      Powered by{' '}
                      <img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
                    </a>
                  </footer>
                </div>
                )
              }
              
            }
            
          </Query>
        </div>
        
      )};

Now if you start your application with yarn develop your images should be nicely displayed on the home page.


![](https://paper-attachments.dropbox.com/s_00BFC2D3C57BAD5A1263654F5D21D0D01B780FCF92FEBB22F8E99C3AB0BAA17B_1641486120360_full+fledge.png)


[T](https://github.com/bigpreshy/frontend-cloudinary)[his is the Github link for the frontend code](https://github.com/bigpreshy/frontend-cloudinary)[.](https://github.com/bigpreshy/frontend-cloudinary)

## Conclusion

Congratulations! Here are some of the things achieved in this tutorial: we discussed how to set up a Strapi instance(locally) and we also saw how to create a Cloudinary account and get credentials from it before putting them in `.env` file of our Strapi application. Again, we added post collection types structure and added some data to it to show you how it can be consumed. 
We also went ahead and built a frontend that is displaying the uploaded images.
At the end of this article, you should easily change the default Strapi provider to Cloudinary. You can check more about other upload providers [on the Strapi documentation](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) [here](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) or [on NPM](https://www.npmjs.com/search?q=strapi-provider-upload-&ranking=popularity). You can even create one of your own.