How to Apply Image Transformations in JavaScript with the Cloudinary SDK

With Cloudinary, images can be optimised, transformed, uploaded, and delivered dynamically.
In this article, we will cover how to transform a static image into an interactive and dynamic asset using Cloudinary’s Transformation URL API and the JavaScript URL-Gen SDK.
By the end, you will have a working demo that dynamically updates an image in the browser using Cloudinary transformations.
In this tutorial, you will:
Set up a project with Vite and the Cloudinary SDK.
Import the required classes and helpers.
Create a CloudinaryApp class to organise your code.
Apply transformations like crop, sepia, grayscale, brightness, contrast, rounded corners, and text overlays.
Update the image dynamically in the browser with error handling.
For this tutorial, you need the starter code. Fork this repository to get started:
https://github.com/IfyN/cloudinary-code-it
Setting up the Project
You will be installing Vite for this project, since Vite provides a development server that allows us to run JavaScript and ensures that the Cloudinary SDK imports work in the browser.
Tools used in this tutorial
VS Code as the editor
Node.js + npm for package management
Vite as the development server
Cloudinary URL-Gen SDK for transformations
To set up Vite, navigate to your project folder and run:
npm install vite --save-dev
You also need to install Cloudinary’s URL-Gen SDK:
npm install @cloudinary/url-gen
The @cloudinary/url-gen package provides us with classes and helper modules such as:
CloudinaryImage – a class for creating image instances.
Actions – a module with helper functions for common transformations such as crop, sepia, cartoonify, overlay, and grayscale.
TextStyle – defines font family, size, and weight for text overlays.
Position – defines where overlays are placed on the image.
compass – a helper for setting gravity (north, south, east, west, etc.).
These will be used in this tutorial.
Create a .js file inside src/. In this file, import the following:
import { CloudinaryImage, Actions } from "@cloudinary/url-gen";
import { TextStyle } from "@cloudinary/url-gen/qualifiers/textStyle";
import { source } from "@cloudinary/url-gen/actions/overlay";
import { text as clText } from "@cloudinary/url-gen/qualifiers/source";
import { Position } from "@cloudinary/url-gen/qualifiers";
import { compass } from "@cloudinary/url-gen/qualifiers/gravity”
Note: If you are using an AI coding assistant inside your IDE, it might auto-suggest the following import:
import { compass } from "@cloudinary/transformation-builder-sdk/qualifiers/gravity";
This is incorrect. The accurate import, according to Cloudinary’s documentation, is:
import { compass } from "@cloudinary/url-gen/qualifiers/gravity";
Using the wrong path will lead to an unexpected 404 error or malformed URLs.
Creating the CloudinaryApp Class
To keep your code structured, we will create a CloudinaryApp class. This class will:
Set the initial image state.
Listen for button click events.
Apply Cloudinary transformations depending on which button is clicked.
Update the <img> in the DOM.
Here is the starting point:
class CloudinaryApp {
constructor() {
this.currentImageUrl =
"https://res.cloudinary.com/demo/image/upload/sample.jpg";
this.init();
}
init() {
this.setupEventListeners();
}
setupEventListeners() {
const buttons = document.querySelectorAll(".btn");
buttons.forEach((button) => {
button.addEventListener("click", (e) => {
const transformation = e.target.dataset.transformation;
this.applyTransformation(transformation);
this.updateActiveButton(e.target);
});
});
}
}
The constructor sets the starting image URL and immediately calls init(). When you create a new CloudinaryApp(), the constructor sets the starting image and immediately calls init(). That way, all your buttons are ready to go as soon as the app runs.
Adding the Buttons
For the app to work, you also need buttons in your HTML that trigger the different transformations. Each button uses a data-transformation attribute to specify which transformation should be applied.
<div class="btn-group">
<button class="btn" data-transformation="original">Original</button>
<button class="btn" data-transformation="text">Text overlay </button>
<button class="btn" data-transformation="crop">Crop Square</button>
<button class="btn" data-transformation="grayscale">Grayscale</button>
<button class="btn" data-transformation="sepia">Sepia</button>
<button class="btn" data-transformation="brightness">Brightness</button>
<button class="btn" data-transformation="contrast">Contrast</button>
<button class="btn" data-transformation="rounded">Rounded</button>
</div>
Using data-transformation keeps the HTML and JavaScript loosely coupled. When you click a button, the setupEventListeners() method reads the data-transformation value and passes it into applyTransformation(). This makes the code easier to extend, you can add new buttons with different transformations without touching the JavaScript logic.
Applying Cloudinary Transformations
Inside the applyTransformation() method, use the Cloudinary SDK to build transformation URLs. Each case creates a new CloudinaryImage instance and applies one or more actions:
applyTransformation(transformation) {
try {
let transformedUrl;
switch (transformation) {
case "original":
transformedUrl = this.currentImageUrl;
break;
case "text":
const textImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = textImage
.overlay(
source(
clText(
"Pink Flower",
new TextStyle("Arial", 100).fontWeight("bold")
).textColor("white")
).position(new Position().gravity(compass("north")).offsetY(20))
)
.toURL();
break;
case "crop":
const cropImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = cropImage
.resize(Actions.Resize.fill().width(400).height(400))
.toURL();
break;
case "grayscale":
const grayscaleImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = grayscaleImage
.effect(Actions.Effect.grayscale())
.toURL();
break;
case "sepia":
const sepiaImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = sepiaImage.effect(Actions.Effect.sepia(80)).toURL();
break;
case "brightness":
const brightnessImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = brightnessImage
.adjust(Actions.Adjust.brightness(30))
.toURL();
break;
case "contrast":
const contrastImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = contrastImage
.adjust(Actions.Adjust.contrast(30))
.toURL();
break;
case "rounded":
const roundedImage = new CloudinaryImage("sample", {
cloudName: "demo",
});
transformedUrl = roundedImage
.roundCorners(Actions.RoundCorners.byRadius(50))
.toURL();
break;
default:
transformedUrl = this.currentImageUrl;
}
// Update the image
this.updateImage(transformedUrl);
} catch (error) {
console.error(`Error applying ${transformation} transformation:`, error);
}
}
All the code inside the applyTransformation method is wrapped in a try...catch block. This is done to make debugging easier. If a transformation fails because of a wrong import, an invalid method call, or a malformed URL, the error is caught and logged in the console with the name of the transformation.
After the transformation URL is created, the updateImage method replaces the src of the <img> element with the new URL. This makes the browser reload the image and display the updated transformation immediately.
updateImage(url) {
const image = document.getElementById("mainImage");
if (image) {
image.src = url;
}
}
At the bottom of your file, initialise the app once the DOM is ready:
document.addEventListener("DOMContentLoaded", () => {
new CloudinaryApp();
});
You now have a working demo that applies Cloudinary image transformations in JavaScript using the URL-Gen SDK. With just a few lines of code, you can dynamically crop, resize, adjust brightness and contrast, apply filters, and even overlay text on images in the browser. This not only makes your assets more interactive but also helps optimise performance by delivering images tailored to your users’ needs.
From here, you can expand the project by combining multiple transformations, experimenting with advanced effects, or integrating these techniques into larger applications. To keep exploring, check out the Cloudinary documentation for more transformation options.
👉 Fork the starter repo, try out your own transformations, and share what you build.



