Programmatic Video Branding with Cloudinary Transforms
Producing polished, on-brand video content usually means hours of manual editing. For the Jamstack Explorers project, a video-heavy educational site, the goal was automation: could the repetitive parts of video post-production be handled without dropping quality? Cloudinary’s URL-based transformation API made it possible to inject consistent branding into every video with code, removing manual editing steps entirely and enabling site-wide branding updates from a single source.
What Branding Looks Like on Video
To make videos feel cohesive, each one gets three extra pieces: an opening title scene, a short intro bumper clip, and an outro bumper that transitions to the next video or signals mission completion.
Without these elements, a video starts and ends abruptly. With them applied via Cloudinary, the video feels like part of a larger narrative. The core content stays intact, but the framing makes it professional and contextual. This entire process runs automatically through Cloudinary’s media delivery and transformation layer.
The Cloudinary URL as a Transformation Tool
Cloudinary works by serving assets—images, video, and more—from URLs. After uploading an asset, you can alter its delivery by adding transformations to that URL.
https://res.cloudinary.com/netlify/image/upload/v1605632851/explorers/avatar.jpg
^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
| | |
V V V
cloud (account) name version (optional) file name
Automatic Format and Quality Optimization
Performance tuning traditionally requires generating multiple file versions and juggling <picture> elements for fallbacks. Cloudinary simplifies this by letting you append format and quality flags directly to the URL. Adding f_auto,q_auto tells Cloudinary to serve the most efficient format the visitor's browser supports—WebP, AVIF, and so on—at a reasonable quality. In practice, this changed a 97.6kB JPG into a 15.4kB WebP with a small URL edit.

Other common image tricks—like resizing with w_150 or applying e_grayscale effects—work the same way, by appending parameters to the URL.

Using the Node SDK for Readable Transformations
Hand-written URLs become unwieldy as transformations stack up. The Cloudinary Node SDK keeps the code readable and maintainable. Install it via npm using credentials from your Cloudinary console, then initialize it with your cloud_name and API keys, ideally using environment variables to keep secrets out of version control.
const cloudinary = require('cloudinary').v2;
// TODO replace these values with your own Cloudinary credentials
cloudinary.config({
cloud_name: 'your_cloud_name',
api_key: 'your_api_key',
api_secret: 'your_api_secret',
});
The same transformation from the URL approach becomes a structured object:
{
asset_id: 'fca4abba96ffdf70ef89498aa340ae4e',
public_id: 'explorers/avatar',
version: 1605632851,
version_id: 'b8a923931af20404e89d03852ff1bff1',
signature: 'e7201c9ab36cb5b6a0545cee4f5f8ee27fb7f99f',
width: 300,
height: 300,
format: 'jpg',
resource_type: 'image',
created_at: '2020-11-17T17:07:31Z',
bytes: 97633,
type: 'upload',
url: 'http://res.cloudinary.com/netlify/image/upload/v1605632851/explorers/avatar.jpg',
secure_url: 'https://res.cloudinary.com/netlify/image/upload/v1605632851/explorers/avatar.jpg',
access_mode: 'public',
eager: [
{
transformation: 'e_grayscale,f_auto,q_auto,w_150',
width: 150,
height: 150,
bytes: 6192,
format: 'jpg',
url: 'http://res.cloudinary.com/netlify/image/upload/e_grayscale,f_auto,q_auto,w_150/v1605632851/explorers/avatar.jpg',
secure_url: 'https://res.cloudinary.com/netlify/image/upload/e_grayscale,f_auto,q_auto,w_150/v1605632851/explorers/avatar.jpg'
}
]
}
These options live under the eager property, paired with a full URL for each transformed asset. While this is overkill for simple cases, the SDK proves its worth when dealing with the layered transformations needed to brand video.
Assembling Branded Video
Video transformations follow the same logic as images: upload the file, modify the URL, and Cloudinary returns the edited video. For Jamstack Explorers, building the branded final video involved four categories of transformation: overlays, transitions, text overlays, and splicing.
Transitioning Between Video Segments
To insert bumpers, you add a second “layer” to the transformation stack using the overlay parameter. Its value is set to video:publicID, where the public ID uses colons in place of slashes. Telling Cloudinary to treat that layer as a transition requires a specific file type: a luma matte, which masks two videos together based on black and white regions to create a stylized fade.
Each video and the transition get their own object layer. The transition layer sets its effect to transition, a flag that signals Cloudinary to use it as a matte. Common format, quality, and sizing rules shared by all videos can be extracted into a shared variable like videoBaseTransformations to avoid repetition.
const videoBaseTransformations = {
fetch_format: 'auto',
quality: 'auto',
height: 360,
width: 600,
crop: 'fill',
}
cloudinary.uploader
.explicit('explorers/bumper', {
// these two properties match the beginning of the URL:
// <https://res.cloudinary.com/netlify/image/upload/>...
//
resource_type: 'video',
type: 'upload',
// "eager" means we want to run these transformations ahead of time to avoid
// a slow first load time
eager: [
videoBaseTransformations,
{
overlay: 'video:explorers:LCA-07-lifecycle-hooks',
...videoBaseTransformations,
},
{
overlay: 'video:explorers:transition',
effect: 'transition',
},
{ flags: 'layer_apply' }, // <= apply the transformation
{ flags: 'layer_apply' }, // <= apply the actual video
],
// allow this transformed image to be cached to avoid re-running the same
// transformations over and over again
overwrite: false,
})
.then((result) => {
console.log(result);
});
Splicing a Title Card Clip
Creating a title card requires two distinct steps: extract a video segment to act as the background, then overlay the text.
For the background, the approach is to splice in three seconds taken from the start of a dedicated intro clip. The splice flag appends this new clip to the main video directly, without any transition. To tailor that clip further, you can set audio_codec to none for silence, use end_offset set to 3 to capture the first three seconds, and apply the accelerate effect with a value of -25 to slow it by 25%.
# create a new directory
mkdir cloudinary-video
# move into the new directory
cd cloudinary-video/
# initialize a new Node project
npm init -y
# install the Cloudinary Node SDK
npm install cloudinary
Overlay Title Text
Text uses the same overlay parameter, but the value is an object of font settings. Cloudinary supports a large catalog of fonts, acceptable for common web typefaces.
cloudinary.uploader
.explicit('explorers/bumper', {
// these two properties match the beginning of the URL:
// <https://res.cloudinary.com/netlify/image/upload/>...
//
resource_type: 'video',
type: 'upload',
// "eager" means we want to run these transformations ahead of time to avoid
// a slow first load time
eager: [
videoBaseTransformations,
{
overlay: 'video:explorers:LCA-07-lifecycle-hooks',
...videoBaseTransformations,
},
{
overlay: 'video:explorers:transition',
effect: 'transition',
},
{ flags: 'layer_apply' }, // <= apply the transformation
{ flags: 'layer_apply' }, // <= apply the actual video
// add the outro bumper and a transition
{
overlay: 'video:explorers:countdown',
...videoBaseTransformations,
},
{
overlay: 'video:explorers:transition',
effect: 'transition',
},
{ flags: 'layer_apply' },
{ flags: 'layer_apply' },
// splice a title card at the beginning of the video
{
overlay: 'video:explorers:intro',
flags: 'splice', // splice this into the video
...videoBaseTransformations,
},
{
audio_codec: 'none', // remove the audio
end_offset: 3, // shorten to 3 seconds
effect: 'accelerate:-25', // slow down 25% (to ~4 seconds)
},
{
overlay: {
font_family: 'roboto', // lots of Google Fonts are supported
font_size: 40,
text_align: 'center',
text: 'Lifecycle Hooks', // this can be any text you want
},
width: 500,
crop: 'fit',
color: 'white',
},
{ flags: 'layer_apply' },
{
flags: 'layer_apply',
start_offset: 0, // put this at the beginning of the video
},
],
// allow this transformed image to be cached to avoid re-running the same
// transformations over and over again
overwrite: false,
})
.then((result) => {
console.log(result);
});
Configure the font’s size and alignment, then set a width of 500px to bound the layout. Adding a crop value of fit ensures long titles wrap within the defined space. To keep text readable against a dark cinematic background, set the color value to white.
Automation is the Key Ingredient
Assembling bumpers, cutting clips, and inserting overlays by hand for each piece of content is the reason branding often falls by the wayside. The architecture described here eliminates that friction entirely. Because every branding element is defined as code, each new video can get the same treatment with zero manual editing steps.
The biggest payoff is future-proofing. Updating the site's identity means changing the title and bumper assets in one codebase. The transformation logic handles those updates across every existing and future video automatically—no more touching archived original files.
Resources to Continue
- Source code for this tutorial.
- The Jamstack Explorers source code for its real-world implementation.
- Cloudinary’s video transformation API documentation.
- Learn the fundamentals on Jamstack Explorers.



