Skip to content

Editor-Friendly Image Transformation and Optimized Delivery With Cloudinary and Decap CMS

How and why you should empower editors with a simple interface to upload and transform images.

Content managers and editors need a simple, intuitive way of handling images in a content management system (CMS). They should also be able to upload, adjust, and remove assets without impacting site performance with bad transformations. As a safeguard, developers need to control the quality and size of the images delivered to the site. 

In this blog post, we’ll explore how to set up a CMS that will allow editors to upload images and apply transformations, and for developers to control the quality and size of the delivered images.

To achieve this, we’ll use Decap CMS, a git-based CMS that’s easy to set up and configure. We’ll also use Cloudinary, a digital asset management (DAM) solution to upload images and apply transformations to them. Finally, we’ll use Hugo, a static site generator, to build the site.

In this blog post:

  • Using Hugo, A Static Site Generator
  • Adding Decap CMS
  • Decap Media Libraries
  • Configuring the Cloudinary Widget
  • Constructing Cloudinary Transformation URLs
  • Caveats

We need a front matter-based site generator and something to serve the CMS locally. We could also use Gatsby, Next.js, or any other site generator. I like Hugo because it’s extremely fast and makes the most out of vanilla HTML, JS, and CSS. That way, there is no need for a JS framework.

If you don’t have it yet, you need to install Hugo. Then start with a blank Hugo site:

hugo new site site-name

cd site-nameCode language: JavaScript (javascript)

When choosing a CMS for a website with a lot of media, consider the media library and what you want from it. For more complex sites, I recommend using a dedicated image and video API, such as Cloudinary, which is what we’ll use here. However, the default media library that comes with Decap CMS is sufficient for simple sites.

Decap CMS is the most popular open-source git-based CMS. It’s integrated with the Cloudinary Media Library widget and is easy to set up. Find more details at Add Decap CMS to your site.

All we have to do to install Decap is add two files to our Hugo project. See the Site Generator Overview for details on other SSGs.

Add index.html and config.yml to the static/admin folder:


├ index.html

└ config.ymlCode language: JavaScript (javascript)

index.html is the page that will serve the CMS React app.

<!doctype html><html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="robots" content="noindex" /><title>Content Manager</title></head><body><!-- Include the script that builds the page and powers Decap CMS -->

 <script src="^3.0.0/dist/decap-cms.js"></script></body></html>Code language: HTML, XML (xml)

config.yml is the configuration file for Decap CMS. We’ll use the git-gateway backend, which allows you to add editors to your site CMS without giving them direct write access to your GitHub or GitLab repository. We’ll also use the local_backend option to run the CMS locally.

local_backend: true # enables us to run cms locally


 name: git-gateway

 branch: master # Branch to update (optional; defaults to master)Code language: PHP (php)

Decap has a default media library that works with media in the repository. However, git isn’t a good place to store media, so I recommend using a digital asset manager like Cloudinary. Decap’s Cloudinary integration allows us to access our Cloudinary cloud from image widgets in the CMS and easily retrieve asset URLs in various forms. This is what we’ll use here.

We just have to add these lines to the config.yml file:


 name: cloudinary


   cloud_name: your_cloud_name

   api_key: your_api_key

With this configured, the image widget opens the Cloudinary Media Library widget.

Collections are the way to define content types in Decap CMS. We’ll define a blog folder collection for this example. Here, we configure the CMS to save markdown files to the content/blog folder, which the site generator can render as blog posts.



 - name: blog # Used in routes, e.g., /admin/collections/blog

   label: Blog # Used in the UI

label_singular: post

   folder: content/blog # The path to the folder where the documents are stored

   create: true # Allow users to create new documents in this collection

   fields: [ # The fields for each document, usually in front matter

     {label: Title, name: title, widget: string},

     {label: Featured Image, name: image, widget: image},

     {label: Body, name: body, widget: markdown},

   ]Code language: PHP (php)

Now run Hugo and Decap locally with these commands in two parallel terminals:

npx decap-server hugo server

Open http://localhost:1313/admin/#/ to access the CMS and click New post.

Blog post view

The image input will now open the Cloudinary Media Library widget if you click Choose an image. If you’re not authenticated, you’ll be prompted to log in. If you get the same site error, you can log in to Cloudinary in another tab and refresh the CMS.

Blog post view

Taken from the Decap CMS docs on Cloudinary library.

  • output_filename_only: (default: false)
    By default, the value provided for a selected image is a complete URL for the asset on Cloudinary’s CDN. Setting output_filename_only to true will instead produce just the filename (e.g., image.jpg). This should be true if you’ll be directly embedding Cloudinary transformation URLs in page templates. Refer to Inserting Cloudinary URL in page templates.
  • use_transformations: (default: true)
    If true, it uses the derived URL when available (the URL will include image transformation segments). It has no effect if output_filename_only is set to true.
  • use_secure_url: (default: true)
    Controls whether an http or https URL is provided. It has no effect if output_filename_only is set to true.
  • config
# Global example


 name: cloudinary

 output_filename_only: false


   cloud_name: your_cloud_name

   api_key: your_api_key

   multiple: true

   max_files: 3


     - - fetch_format: auto

         width: 160

         quality: auto

         crop: scale

# You can use all settings globally and per field.

# If no config is set for a field, the global config is used.

# Example field in a collection


- name: 'image'

 widget: 'image'




       - - fetch_format: auto

           width: 300    

           quality: auto

           crop: fill

           effect: grayscaleCode language: PHP (php)

This is where we’re looking for a balance between editor control, UX simplicity, developer control, and performance. The more control we give to editors, the more we have to trust them to make the right decisions. The more we control layouts, the fewer options we leave to editors, but we can optimize for performance.

In general, we have three options:

  1. Construct URLs in layout with filename only.
  2. Allow editors to transform assets through the Cloudinary widget.
  3. Apply default transformations and allow editors to add their own.

Set output_filename_only: true and construct the URL with transformations in your HTML layouts. This is the safest option, but it gives editors no control over the image. The problem is that it won’t output the subfolder, so we’d have to hardcode it. Normally, that isn’t a good idea, as an editor might easily change the media library’s folder.

To avoid that, you can also set use_transformations: false in config to omit editor-set transformations from URLs. You’ll then have to deconstruct the URL and add the transformations in the layout.

This is the most flexible option, but it requires editors to know what they are doing. Nothing extra has to be set in config, as this is the default. Consider educating editors on how to apply the transformations.

Apply transformations by clicking an asset to open the edit screen and use advanced editing.

Edit transformations by clicking advanced editing, then view derived images, then edit.

Decap will apply the transformation you defined in config to the chosen assets. If an editor adds their own transformations, they’ll get chained. You can also prevent the editor from adding transformations by setting use_transformations: false.

Chaining is maybe the best of both worlds if you use good default transformations. I recommend using a set of general optimizers, such as c_fill , w_auto, g_auto, q_auto, and dpr_auto/f_auto. This will make sure that the image is correctly cropped, resized, optimized for the viewport, and served in the best format.

Sometimes editors want to serve different images for mobile and desktop. We can add an optional image field for mobile (or desktop if mobile is the default) and set the default as a fallback. This presents a bit more work for editors, but it’s still easy to do.

If you change default transformations after images are added, you’ll have to go through all the images and reapply them. If you want to avoid that, consider adding the transformations to the layout.

Although Cloudinary stores metadata for assets, Decap CMS only returns the URL. So, if you want to use alt or title, you have to add it as a field in the collection. Thankfully, there are plans to add this feature in the future.

Decap CMS plus Cloudinary is an excellent combination for an editor-friendly media editing experience. Developers can control the quality and size of the images delivered and give editors the amount of control they need.

Discover the ease of building visually stunning, responsive websites with Cloudinary. Tailor your images to fit into any web framework, ensuring an engaging visual experience on every device. Join Cloudinary for free today and bring versatility and responsiveness to your images with just a few clicks.

Tune in for Part 2, where we’ll explore how to serve these URLs optimally with Hugo.

Back to top

Featured Post