How to build a Gatsby Image Gallery Theme

Banner for a MediaJam post

Ashutosh K Singh

In this media jam, we will build a Gatsby Theme for an Image Gallery. The images will be stored and fetched from Cloudinary. We will conclude this jam by publishing our theme to the npm registry.

If you want to jump right into the code, check out the GitHub Repo here and the NPM package here.

Code Sandbox

What are Gatsby Themes?

Gatsby Themes are plugins that include a gatsby-config.js file and pre-configured functionalities, UI design. These themes are Node.js packages that implement Gatsby APIs and are commonly installed through a registry like npm.

How to setup Yarn workspace

Make sure you have yarn installed on your local dev machine. You can read more about yarn installation here.

You will also need to install the Gatsby command-line tool (CLI). The gatsby-cli is the main entry point for getting up and running with a Gatsby application. You can read more about gatsby-cli here.

Run the following command in the terminal to install yarn and gatsby-cli globally.

1npm install --global yarn
2npm install -g gatsby-cli

Run the following command to create a package.json in your root directory.

1touch package.json

Add the following code to package.json.

2 "private": true,
3 "workspaces": ["gatsby-theme-cloudinary-gallery", "demo"]

You will create two workspaces named gatsby-theme-cloudinary-gallery and demo. Here gatsby-theme-cloudinary-gallery is the Gatsby Theme, and the demo is the Gatsby site to test the theme.

The gatsby-theme-cloudinary-gallery will be installed as a dependency in the demo workspace to test the theme before publishing it to the npm registry.

Run the following command in your project's root directory.

1mkdir gatsby-theme-cloudinary-gallery
2mkdir demo
3cd gatsby-theme-cloudinary-gallery
4npm init -y
5cd ..
6cd demo
7npm init -y
8cd ..

The above commands will create two directories named gatsby-theme-cloudinary-gallery and demo—each with a package.json in it.

Your file tree will look like this.

2├── gatsby-theme-cloudinary-gallery
3│ └── package.json
4├── demo
5│ └── package.json
6└── package.json

Update gatsby-theme-cloudinary-gallery/package.json like this.

2 "name": "gatsby-theme-cloudinary-gallery",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "build": "gatsby build",
8 "develop": "gatsby develop",
9 "clean": "gatsby clean"
10 },
11 "keywords": [
12 "gatsby",
13 "gatsby-theme",
14 "gatsby-plugin",
15 "react",
16 "images",
17 "cloudinary",
18 "gallery",
19 "cloudinary-gallery"
21 "author": "",
22 "license": "MIT"

Inside the gatsby-theme-cloudinary-gallery directory, create a file named index.js. Run the following command to create it.

1touch gatsby-theme-cloudinary-gallery/index.js

This file is needed since it is present as the main entry point in the package.json. You can leave it empty for now; you will update it in the last section to export the Gallery component.

Update demo/package.json like this.

2 "name": "demo",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "build": "gatsby build",
8 "develop": "gatsby develop",
9 "clean": "gatsby clean"
10 },
11 "keywords": [],
12 "author": "",
13 "license": "MIT"

Run the following command to add dependencies to the gatsby-theme-cloudinary-gallery and demo workspace.

1yarn workspace demo add gatsby react react-dom gatsby-theme-cloudinary-gallery@*
2yarn workspace gatsby-theme-cloudinary-gallery add -P gatsby react react-dom
3yarn workspace gatsby-theme-cloudinary-gallery add -D gatsby react react-dom

You install gatsby, react, and react-dom in the demo workspace to set up a Gatsby site. You also install the gatsby-theme-cloudinary-gallery in the demo workspace as a dependency.

In the gatsby-theme-cloudinary-gallery workspace, you install gatsby, react, and react-dom as both dev dependencies and peer dependencies.

Dev dependencies are the packages you need during project development.

Peer dependencies are used to specify that your package is compatible with a specific version of the npm package.

During installation, if the package already exists in the node_modules directory, it will do nothing. But if it is not present, a warning will be shown to the user.

Since gatsby, react, and react-dom will be present in every Gatsby site, it is unlikely that this warning will be shown to the user.

Run the following command to check demo is using the gatsby-theme-cloudinary-gallery from the workspace.

1yarn workspaces info

You should see an output similar to this.

2 "gatsby-theme-cloudinary-gallery": {
3 "location": "gatsby-theme-cloudinary-gallery",
4 "workspaceDependencies": [],
5 "mismatchedWorkspaceDependencies": []
6 },
7 "demo": {
8 "location": "demo",
9 "workspaceDependencies": [
10 **"gatsby-theme-cloudinary-gallery"**
11 ],
12 "mismatchedWorkspaceDependencies": []
13 }

In this jam, you will use Tailwind CSS to style your theme. Run the following command to install it.

1yarn workspace gatsby-theme-cloudinary-gallery add gatsby-plugin-postcss tailwindcss@latest postcss@latest autoprefixer@latest

Next, run the following command to generate your tailwind.config.js file.

1cd gatsby-theme-cloudinary-gallery
2npx tailwindcss init

The last command will generate a tailwind.config.js file in the gatsby-theme-cloudinary-gallery directory. Update the tailwind.config.js file like this.

1module.exports = {
2 purge: ['./src/**/*.{js,jsx,ts,tsx}'],
3 darkMode: false, // or 'media' or 'class'
4 theme: {
5 extend: {},
6 },
7 variants: {
8 extend: {},
9 },
10 plugins: [],

Run the following command to create a global.css file inside the gatsby-theme-cloudinary-gallery directory under the src/styles folder.

1mkdir -p gatsby-theme-cloudinary-gallery/src/styles
2touch gatsby-theme-cloudinary-gallery/src/styles/global.css

Add the following code to the global.css file.

1/* gatsby-theme-cloudinary-gallery/src/styles/global.css */
2@tailwind base;
3@tailwind components;
4@tailwind utilities;

Create gatsby-browser.js and gatsby-config.js files in the gatsby-theme-cloudinary-gallery directory by running the following command.

1touch gatsby-theme-cloudinary-gallery/gatsby-browser.js
2touch gatsby-theme-cloudinary-gallery/gatsby-config.js

Add the following code to the gatsby-browser.js file.

1import './src/styles/global.css';

Add the following code to the gatsby-config.js file.

1module.exports = {
2 siteMetadata: {
3 title: "Gatsby Theme Cloudinary Gallery",
4 description:
5 "Gatbsy Theme to add a Cloudinary Image Gallery to your Gatsby site.",
6 social: [
7 {
8 name: "GitHub",
9 url: "",
10 },
11 ],
12 },
13 plugins: [
14 {
15 resolve: `gatsby-plugin-postcss`,
16 options: {
17 postCssPlugins: [require("tailwindcss")],
18 },
19 },
20 ],

You can refer to the official documentation for detailed instructions on installing Tailwind CSS with Gatsby.

How to create Heading Component

In this section, you will create the Heading component that will show the following heading: Gatsby Theme Cloudinary Gallery

Run the following command to create a components folder in the gatsby-theme-cloudinary-gallery/src directory.

Inside the components directory, create a file named Heading.js.

1mkdir gatsby-theme-cloudinary-gallery/src/components
2touch gatsby-theme-cloudinary-gallery/src/components/Heading.js

Add the following code to the Heading.js file.

1import React from "react";
3const Heading = () => {
4 return (
5 <h1 className="text-4xl md:text-5xl text-center text-gray-800 font-sans font-medium">
6 Gatsby Theme Cloudinary Gallery
7 </h1>
8 );
11export default Heading;

The above code creates a simple heading styled with Tailwind CSS.

The next step is to create a page to use this Heading component. Run the following command to create a index.js file in the src/pages sub-directory.

1mkdir gatsby-theme-cloudinary-gallery/src/pages
2touch gatsby-theme-cloudinary-gallery/src/pages/index.js

Add the following code to the pages/index.js file.

1import React from "react";
2import Heading from "../components/Heading.js";
4const Homepage = () => {
5 return (
6 <div className="bg-gray-200 min-h-screen p-8">
7 <Heading />
8 </div>
9 );
12export default Homepage;

In the above code, you first import the Heading component and then use it inside the Homepage component.

The last step is to create a gatsby-node.js file that will create the page using Gatsby createPages method and the pages/index.js file.

Run the following command to create it.

1touch gatsby-theme-cloudinary-gallery/gatsby-node.js

Add the following code to the gatsby-node.js file.

1exports.createPages = async ({ actions }, options) => {
2 const basePath = options.basePath || "/";
4 actions.createPage({
5 path: basePath,
6 component: require.resolve("./src/pages/index.js"),
7 });

You export the createPages method to create your page. You can read more about createPages method here.

The above code creates a page dynamically at the / route. The options object contains the props passed by the user when using the theme. You can read more about createPage action here.

By default, the / route is used as the basePath. If the user passes a value to the basePath prop in the theme configuration, that value will be used instead.

Start your development server by running the following command.

1yarn workspace gatsby-theme-cloudinary-gallery develop

Head over to http://localhost:8000/ in your browser. Here is how your app will look like.


You can stop the development server by hitting CTRL+C in the terminal.

You can also configure the demo workspace to show the heading or the theme. Create a file named gatsby-config.js in the demo directory by running the following command.

1touch demo/gatsby-config.js

Add the following code to gatsby-config.js.

1module.exports = {
2 plugins: [
3 {
4 resolve: "gatsby-theme-cloudinary-gallery",
5 options: {
6 basePath:"/gallery",
7 },
8 },
9 ],

Since you have passed /gallery to the basePath prop, the theme or the heading will be displayed at the /gallery route.

Start the development server for the demo workspace.

1yarn workspace demo develop

Head over to http://localhost:8000/gallery in the browser. Here is how your app will look like.


How to create Gatsby Nodes from Images

In this section, you will fetch the images from Cloudinary and create Gatsby Nodes from them.

Nodes are the center of Gatsby's data system. All the data that is added to Gatsby is modeled using nodes. You can read more about them here.

Nodes are created by calling the createNode action. Nodes can be any object. Each Node has to be structured in a pre-defined manner, each with a unique id.

In gatsby-node.js file, create a function named createNodeData. This function will convert image resources into node data.

1const createNodeData = (gatsby, image, options) => {
2 return {
3 id: gatsby.createNodeId(`cloudinary-gallery-${image.public_id}`),
4 parent: null,
5 public_id: image.public_id,
6 cloud_name: options.cloudName,
7 internal: {
8 type: "CloudinaryGallery",
9 content: JSON.stringify(image),
10 contentDigest: gatsby.createContentDigest(image),
11 },
12 };

The createNodeData function takes three arguments, gatsby, image, and the options object. Here image is the image resource fetched from Cloudinary.

You are not creating a node in the above function, just structuring the data in the way needed to create the node.

Each image resource has a public_id which is passed in the public_id key. The cloudName is passed as a prop to the theme by the user. This cloudName is accessed using the options object, similar to the basePath prop, and passed to the cloud_name key.

The createNodeId is a utility function to generate globally unique and stable node IDs. You can read more about the createNodeId function here.

The parent is a key reserved for plugins who wish to extend other nodes. It is not needed in this theme, so you pass null to it.

The type is the globally unique node type. This will be used when running GraphQL queries.

The contentDigest is a short digital summary of the content of this node, and the content exposes the raw content of this node that any transformation plugin can take and further process it.

You can read more about Node Creation here.

Navigate to your Cloudinary dashboard in the browser and copy your Cloudinary Cloud Name, API Key, and API Secret from there.

Cloudinary Dashboard

The next step is to create a function to fetch the images from Cloudinary and create nodes from them. You will use the cloudinary npm package to do so.

Run the following command to install cloudinary and cloudinary-react.

1yarn workspace gatsby-theme-cloudinary-gallery add cloudinary cloudinary-react

Import cloudinary at the top of the gatsby-node.js file.

1// gatsby-theme-cloudinary-gallery/gatsby-node.js
3const cloudinary = require("cloudinary").v2;

Create a function named createCloudinaryImageNodes in the gatsby-node.js file.

1const createCloudinaryImageNodes = (gatsby, cloudinary, options) => {
2 const cloudinaryOptions = {
3 type: options.type || "upload",
4 max_results: options.maxResults || 10,
5 prefix: options.prefix || '',
6 };
8 return cloudinary.api.resources(cloudinaryOptions, (error, result) => {
9 => {
10 const nodeData = createNodeData(gatsby, resource, options);
11 gatsby.actions.createNode(nodeData);
12 });
13 });

In the above code, the createCloudinaryImageNodes function takes gatsby, cloudinary, and options as arguments.

You create a cloudinaryOptions object that contains the props to modify the Cloudinary fetch request. The user passes these props in the gatsby-config.js file similar to the basePath prop and is accessed using the options object.

These props are:

  • type -This is the storage type: upload, private, authenticated, facebook, twitter, etc. Defaults to upload.
  • maxResults - Maximum number of assets to return. Default - 10.
  • prefix -Find all resources with a public ID that starts with the given prefix. This is helpful when you want to access the images present in a particular folder.

The above props are optional, and hence a default value is passed in each of them. You can create other fields like resource_type, tags, context, etc., in the cloudinaryOptions object. In this jam, we will restrict to these three props only.

You pass the cloudinaryOptions object in the cloudinary.api.resources() method. This method fetches the image resources from your Cloudinary account based on the options or the cloudinaryOptions object. You can read more about the api.resources method here.

The result.resources array contains the data of each image which is passed to the createNodeData function to reformat the data in the way needed to create the node.

The reformatted data is then passed to gatsby's createNode() action creating a new node. You can read more about this action here.

Finally, you use the createCloudinaryImageNodes function inside the sourceNodes Gatsby API to source the nodes.

Add the following code for the sourceNodes function in the gatsby-node.js file.

1exports.sourceNodes = (gatsby, options) => {
2 cloudinary.config({
3 cloud_name: options.cloudName,
4 api_key: options.apiKey,
5 api_secret: options.apiSecret,
6 });
8 return createCloudinaryImageNodes(gatsby, cloudinary, options);

In the sourceNodes function, you create the Cloudinary instance and pass the API keys to it. These API keys are passed as props to the theme by the user and accessed using the options object.

You can read more about sourceNodes here.

To test the image nodes, update the demo/gatsby-config.js file like this. Make sure to add your API keys in the options object. You can also pass value to optional fields like basePath, type, etc.

1// demo/gatsby-config.js
2module.exports = {
3 plugins: [
4 {
5 resolve: "gatsby-theme-cloudinary-gallery",
6 options: {
10 },
11 },
12 ],

Start the development server by running the following command.

1yarn workspace demo develop

Navigate to localhost:8000/___graphql in your browser. You will see the GraphiQL playground.

GraphQL playground

Paste the following query in the GraphiQL playground.

1query MyQuery {
2 allCloudinaryGallery {
3 nodes {
4 id
5 cloud_name
6 }
7 }

Run the query by clicking the Run icon.

You will see your cloudName and public_id of the images in the result tab.

GraphQL playground with query

You have successfully created Nodes from the images fetched from Cloudinary.

How to create the Gallery Component

In this section, you will create the Gallery component to display the images in a grid layout.

Create a file named Gallery.js inside the gatsby-theme-cloudinary-gallery/src/components directory by running the following command.

1touch gatsby-theme-cloudinary-gallery/src/components/Gallery.js

Add the following code to the Gallery.js file.

1import React from "react";
2import { Image, Transformation } from "cloudinary-react";
4const Gallery = ({ cloudinaryImages }) => {
5 return (
6 <main className="container mx-auto grid grid-cols-1 md:grid-cols-2
7 lg:grid-cols-3 gap-3 p-8">
8 {cloudinaryImages.length > 0 &&
9 => (
10 <Image
11 key={}
12 cloudName={cloudinaryImage.cloud_name}
13 publicId={cloudinaryImage.public_id}
14 className="w-full block mx-auto"
15 >
16 <Transformation
17 fetchFormat="auto"
18 width="800"
19 height="800"
20 loading="lazy"
21 radius="20"
22 gravity="face"
23 crop="fill"
24 />
25 </Image>
26 ))}
27 </main>
28 );
31export default Gallery;

In the above code, you take the images as a prop in the Gallery component. You map over the cloudinaryImages array to display the images using the Image component from the cloudinary-react package. Tailwind CSS is used to style the gallery and create the grid layout.

You transform the images using the Transformation component. Here are all the default transformations applied to the images.

fetchFormatautoThe format of the fetched image. Using auto, delivers the image in the most optimized format for each browser that requests it. For more details see image format conversion.
width800Width of the fetched image.
height800Height of the fetched image.
loadinglazyLazy Loading to delay loading images if they are not yet visible on the screen.
radius20Round the corner of the images.
gravityfaceTo automatically crop an image so that the detected face(s) is used as the center of the derived picture, set the gravity parameter to faces - the region of the image that includes all the faces detected.
cropfillResizes the image to fill the specified dimensions without distortion. The image may be cropped as a result. For more details see.

You can add more transformations in the above code as per the requirements of your theme.

Update the pages/index.js file like this.

1import React from "react";
2import { graphql, useStaticQuery } from "gatsby";
3import Gallery from "../components/Gallery.js";
4import Heading from "../components/Heading.js";
6const Homepage = () => {
7 const data = useStaticQuery(graphql`
8 query {
9 allCloudinaryGallery {
10 nodes {
11 public_id
12 id
13 cloud_name
14 }
15 }
16 }
17 `);
19 const images = data.allCloudinaryGallery.nodes;
21 return (
22 <div className="bg-gray-200 min-h-screen p-8">
23 <Heading />
24 <Gallery cloudinaryImages={images} />
25 </div>
26 );
29export default Homepage;

You use the useStaticQuery hook from Gatsby to fetch the data using the query discussed above. The useStaticQuery hook fetches data using a GraphQL query. This fetched data is parsed and injected into your React components. You can read more about this hook here.

You pass the images array, which contains id, image's public_id, and cloud_name, to the Gallery component.

Start the demo workspace development server and head over to http://localhost:8000/ in the browser.

Your app will look similar to this. The images may vary from the photos in your Cloudinary dashboard.

Cloudinary Image Gallery

What if the user wants to embed the image gallery in another page and not create a specific route for it. For this, you can export the pages/index.js file in the gatsby-theme-cloudinary-gallery/index.js file.

Add the following code to the gatsby-theme-cloudinary-gallery/index.js file.

2export { default as Gallery } from "./src/pages/index";

Now, you can import this Gallery component in any page of your Gatsby site and use the component inside it.

1import { Gallery } from "gatsby-theme-cloudinary-gallery";
3const Homepage = () => (
4 <div>
5 <Gallery />
6 </div>
9export default Homepage;

Here is how the embed Image Gallery will look like.

Gallery Component

How to publish your theme to NPM

In this section, you will publish your Gatsby Theme to the npm registry.

Create a file in the gatsby-theme-cloudinary-gallery directory and create a simple documentation for your theme. You can refer to the file of this project here.

Update the name field in the gatsby-theme-cloudinary-gallery/package.json with your GitHub handle like this.

2"name": "@lelouchb/gatsby-theme-cloudinary-gallery",

Create an NPM account if you haven't already from the NPM Signup page.

NPM SignUp

Login to your NPM account by runnning the following command in the terminal.

1npm adduser

You will be prompted for your username, password, and email id.

Once you have logged in, run the following command in your project's root terminal to publish your theme.

1cd gatsby-theme-cloudinary-gallery
2npm publish --access public

After the theme has been published you will see a message similar to this in your terminal.

1npm notice
2npm notice 📦 @lelouchb/gatsby-theme-cloudinary-gallery@1.0.0
3npm notice === Tarball Contents ===
4npm notice 962B package.json
5npm notice === Tarball Details ===
6npm notice name: @lelouchb/gatsby-theme-cloudinary-gallery
7npm notice version: 1.0.0
8npm notice filename: @lelouchb/gatsby-theme-cloudinary-gallery-1.0.0.tgz
9npm notice package size: 531.7 kB
10npm notice unpacked size: 1.7 MB
11npm notice shasum: e98761e3870f1f50fatWrERwybd5c6819e809548eaed82ef3
12npm notice integrity: sha512-wCSHgJpQcgprkJeo-Po+aG[...]PJL3PXjK9ZkdA==
13npm notice total files: 237
15+ @lelouchb/gatsby-theme-cloudinary-gallery@1.0.0

You can view this theme on NPM here.


In this media jam, we discussed how to create a Gatsby Theme for Image Gallery. We saw how to integrate Tailwind CSS with a Gatsby site.

Finally, we published our Gatsby Theme to the npm registry.

Here are a few resources that you might find helpful:

Happy coding!

Ashutosh K Singh

JavaScript Developer

I'm a JavaScript Developer & Technical Writer. I develop awesome stuff with JavaScript and love to write about them.