<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cloudinary</title>
    <description>The latest articles on DEV Community by Cloudinary (cloudinary).</description>
    <link>https://dev.to/cloudinary</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7286%2F8677e80f-fe6d-40e5-a9d6-c72af0042cd8.png</url>
      <title>DEV Community: Cloudinary</title>
      <link>https://dev.to/cloudinary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudinary"/>
    <language>en</language>
    <item>
      <title>Cloudinary's New Image Generation API: One API, Multiple AI Models, and Built-in Asset Management</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:02:03 +0000</pubDate>
      <link>https://dev.to/cloudinary/cloudinarys-new-image-generation-api-one-api-multiple-ai-models-and-built-in-asset-management-3ldl</link>
      <guid>https://dev.to/cloudinary/cloudinarys-new-image-generation-api-one-api-multiple-ai-models-and-built-in-asset-management-3ldl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;At conference booths, developers often ask whether we support image generation at Cloudinary, given our emphasis on media management. As of now, I can say "YES! yes, we do" - here's how!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cloudinary's Image Generation API lets developers generate images from text prompts using multiple AI model families, then store the result as a managed Cloudinary asset for delivery, optimization, resizing, and transformation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Original image copyright © REUTERS/ABC Affiliate WABC&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this tutorial, we'll use Python to call the API, generate an image, and save the final result to Cloudinary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;A Python script that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate an image from a text prompt&lt;/li&gt;
&lt;li&gt;Choose a model family like &lt;code&gt;flux&lt;/code&gt;, &lt;code&gt;recraft&lt;/code&gt;, &lt;code&gt;gpt-image&lt;/code&gt;, &lt;code&gt;ideogram&lt;/code&gt;, or &lt;code&gt;nano-banana&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Save generated output as a managed Cloudinary asset&lt;/li&gt;
&lt;li&gt;Print the final image URL, public ID, size, and file size&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;First, follow &lt;a href="https://cloudinary.com/documentation/image_generation_addon" rel="noopener noreferrer"&gt;the instructions on Cloudinary docs&lt;/a&gt; to install the image generation add-on to your account. While you're in the Cloudinary console, make note of your API key, secret and the cloud name where you want assets to be stored.&lt;/p&gt;

&lt;p&gt;Install the Python dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export your Cloudinary credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLOUDINARY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLOUDINARY_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-secret"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLOUDINARY_CLOUD_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-cloud-name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's your &lt;a href="https://gist.github.com/jlooper/4ef0ac33a6bd6e943d953f523d606412" rel="noopener noreferrer"&gt;script&lt;/a&gt;. Save it as 'generate.py'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate your first AI image
&lt;/h2&gt;

&lt;p&gt;Run the script with a prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 generate.py &lt;span class="s2"&gt;"A medieval monk"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farplam1tplas8pfkhdaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farplam1tplas8pfkhdaf.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This image was generated with the default Flux model&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The script sends a request to Cloudinary's Image Generation API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model_family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;managed_asset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;public_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it calls the API using HTTP Basic Auth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;IMAGE_GEN_BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/generate/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cloud_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/text_to_image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOUDINARY_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOUDINARY_API_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Choose an AI image model
&lt;/h2&gt;

&lt;p&gt;This is the fun part. You can pick the image generation model that best suits your use case. For example, I find that nano-banana works well with images that include text.&lt;/p&gt;

&lt;p&gt;You can switch model families without changing the rest of your application code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 generate.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"A futuristic Tokyo skyline at sunset"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; flux &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tier&lt;/span&gt; premium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supported model families include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flux
gpt-image
ideogram
recraft
nano-banana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9vz84u8iq56c3vs7lfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9vz84u8iq56c3vs7lfv.png" alt="Another monk" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This was the same prompt, done with Ideogram model&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes it easier to test different visual styles while keeping one integration path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save generated images as Cloudinary assets
&lt;/h2&gt;

&lt;p&gt;The important part of the request is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;managed_asset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;public_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Cloudinary to save the generated image as a managed asset on your account, instead of returning only a temporary output.&lt;/p&gt;

&lt;p&gt;After generation, the script prints something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Image ready!
Public ID: generated/fox-hiking
URL: https://res.cloudinary.com/...
Size: 1024×1024 px
File size: 840 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the image is in Cloudinary, you can resize it, optimize it, crop it, transform it, and deliver it through Cloudinary's CDN. Wicked fast and easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Full usage examples
&lt;/h2&gt;

&lt;p&gt;Generate a new image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 generate.py &lt;span class="s2"&gt;"Marie de France playing pool"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuv5iwudlfmr3567cibqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuv5iwudlfmr3567cibqm.png" alt="Marie de France" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I love this image! Marie de France was a 12th century author of some famous french literature that I particularly like. I don't think she ever played pool, though. Also this pool table is awesomely 'AI-pilled"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use a premium model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 generate.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"A cinematic product photo of a sneaker floating over water"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; flux &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tier&lt;/span&gt; premium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this is useful
&lt;/h2&gt;

&lt;p&gt;Most image generation APIs return an output.&lt;/p&gt;

&lt;p&gt;Cloudinary's Image Generation API returns an output that can immediately become part of your media pipeline.&lt;/p&gt;

&lt;p&gt;That means developers can generate an image and then use the same platform to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store it&lt;/li&gt;
&lt;li&gt;Transform it&lt;/li&gt;
&lt;li&gt;Optimize it&lt;/li&gt;
&lt;li&gt;Resize it&lt;/li&gt;
&lt;li&gt;Deliver it&lt;/li&gt;
&lt;li&gt;Reuse it across applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For apps that already manage media with Cloudinary, AI image generation becomes part of the existing workflow instead of a separate one-off process. The pipeline just got way simplified for you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This Python script is small, but it covers the core production workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate with Cloudinary&lt;/li&gt;
&lt;li&gt;Send a prompt to the Image Generation API&lt;/li&gt;
&lt;li&gt;Choose a model family and tier&lt;/li&gt;
&lt;li&gt;Save the result as a managed asset&lt;/li&gt;
&lt;li&gt;Return a usable image URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building developer tools, e-commerce workflows, campaign generators, or AI-powered creative apps, this approach gives you both image generation and image delivery in the same pipeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/urmty" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>nanobanana</category>
      <category>codenewbie</category>
      <category>images</category>
    </item>
    <item>
      <title>Your model returns a file. Production needs an asset.</title>
      <dc:creator>Nick Bradley</dc:creator>
      <pubDate>Mon, 06 Jul 2026 13:40:43 +0000</pubDate>
      <link>https://dev.to/cloudinary/your-model-returns-a-file-production-needs-an-asset-17ac</link>
      <guid>https://dev.to/cloudinary/your-model-returns-a-file-production-needs-an-asset-17ac</guid>
      <description>&lt;p&gt;Your agent calls an AI image or video model (OpenAI, Google, Runway, fal) and, a few seconds later, gets back a URL or a blob of base64. Feels like the job is done. It isn't. That raw output will probably expire, it's the wrong size and format for wherever it's headed, it carries no record of what made it, and nobody has checked it against your brand. And because it's an agent, there's no human standing by to fix any of that.&lt;/p&gt;

&lt;p&gt;The disappearing URL gets all the attention, but it's only the first of several steps between "the model responded" and "I can ship this." Here's the whole path.&lt;/p&gt;

&lt;p&gt;What this covers: what happens when generated media expires across the major APIs, what "production-ready" actually requires (persist, transform, deliver, brand, find), and how to collapse that pipeline into one step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step zero: the output is built to disappear
&lt;/h2&gt;

&lt;p&gt;Generated media is ephemeral by default, and the details vary more than you'd expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI:&lt;/strong&gt; image models like &lt;code&gt;gpt-image-2&lt;/code&gt; return base64 bytes, no hosted URL at all, so you own storage from the first response. Sora video files last ~48 hours (download URLs ~1 hour), and the Sora video &lt;strong&gt;API is being sunset in September 2026&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google:&lt;/strong&gt; the current image models, &lt;strong&gt;Nano Banana 2 (Gemini 3.1 Flash Image)&lt;/strong&gt; and &lt;strong&gt;Nano Banana Pro (Gemini 3 Pro Image)&lt;/strong&gt;, return bytes inline through the Gemini API. For video, &lt;strong&gt;Veo 3.1&lt;/strong&gt; output is kept &lt;strong&gt;two days&lt;/strong&gt; behind the Files API, then deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runway:&lt;/strong&gt; Gen-4 image and video output URLs expire in roughly &lt;strong&gt;24–48 hours&lt;/strong&gt;; the docs tell you plainly to download and self-host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a bug. These are inference platforms, not asset hosts, and serving files forever is expensive. The trap is that "save it before it vanishes" quietly became &lt;em&gt;your&lt;/em&gt; job, and most teams only find out when a link 404s in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistence alone isn't the finish line
&lt;/h3&gt;

&lt;p&gt;The landscape is shifting, to be fair. &lt;strong&gt;fal.ai now offers fal Assets&lt;/strong&gt;, where generations persist instead of expiring, and platforms like Leonardo keep outputs indefinitely. Even routing layers like &lt;strong&gt;OpenRouter's Unified Image API&lt;/strong&gt; give you one endpoint across dozens of models, though as a router it stores nothing, so persistence is still yours to solve.&lt;/p&gt;

&lt;p&gt;But solving expiry only solves step zero. A file that lives forever in one vendor's library is still unoptimised, unbranded, scattered across whichever providers you happen to use, and only as findable as that vendor's UI. The hard part was never just &lt;em&gt;keeping&lt;/em&gt; the bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why autonomous agents make media persistence harder
&lt;/h2&gt;

&lt;p&gt;When a person generates an image in a web UI, they're right there to download it, crop it, eyeball the brand, and drop it where it belongs. Agentic workflows delete that entire safety net.&lt;/p&gt;

&lt;p&gt;An agent runs in the background. It calls a model, gets a result, and moves on: the output lands in a state object or a database row, and nobody looks until much later. Good agent-state design even says to store large artifacts as files and keep only the path in state. Sound advice, but it assumes the file is somewhere permanent &lt;em&gt;and&lt;/em&gt; usable. A raw model URL is neither. So every step a human would have done by hand now has to happen automatically, at generation time. That's the actual job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "production-ready" actually means
&lt;/h2&gt;

&lt;p&gt;Strip it back to what has to be true before generated media can ship:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Persisted:&lt;/strong&gt; captured the instant it's created, so nothing depends on an expiring URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimised &amp;amp; transformed:&lt;/strong&gt; the right format and size for each destination. A 4 MB PNG becomes a 40 KB WebP thumbnail, a social crop, an AVIF for modern browsers. Video is harder: transcoding to MP4/WebM, an adaptive HLS/DASH stream, a poster frame, often a trimmed cut, all from one master.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivered:&lt;/strong&gt; served fast from a CDN, video streamed adaptively rather than shipped whole from one region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-brand:&lt;/strong&gt; checked against brand rules before a customer sees it. An agent generating customer-facing media with no brand guardrail is a real risk, not a hypothetical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Findable:&lt;/strong&gt; stored with its metadata: prompt, model, use-case, tags, and for video things like duration and captions. An agent that can't search its own output regenerates the same thing twice.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Miss any one of these and you don't have an asset; you have a raw output and a to-do list.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is really an asset pipeline
&lt;/h3&gt;

&lt;p&gt;Every one of those requirements already exists in traditional media workflows. AI generation hasn't removed the pipeline, it has simply automated the first step. Agents make that more obvious because there's no human left to bridge the gap manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the media pipeline yourself
&lt;/h2&gt;

&lt;p&gt;The pattern everyone reaches for looks simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;generate → download the bytes → push to your object store → serve from there&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The store is usually S3, Cloudflare R2, or Supabase, and the mechanics are well-trodden. But persisting the bytes is the easy ~60%. What's left on your plate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Glue code on every generation path&lt;/strong&gt;, with retries for when the URL expires mid-download (worse for large video files).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A transformation service&lt;/strong&gt; (Lambda + sharp, imgix) and, for video, &lt;strong&gt;a transcoding pipeline&lt;/strong&gt; (FFmpeg or managed) for streamable renditions and poster frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CDN&lt;/strong&gt; in front, with adaptive streaming for video.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A brand check&lt;/strong&gt;, which most pipelines just skip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A metadata store&lt;/strong&gt; you build and maintain so assets stay findable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can absolutely assemble this, and if storage cost dominates and your transform needs are light, R2-plus-a-Worker is a sensible call. Just be honest that you're now operating a small media pipeline, not a bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two pipelines, one call
&lt;/h2&gt;

&lt;p&gt;The model step is identical either way. What differs is everything &lt;em&gt;after&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0si4mowxea0fr4cz2ja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0si4mowxea0fr4cz2ja.png" alt="Two pipelines: the DIY chain of services versus a single managed step" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generation call below is deliberately abstracted: whether &lt;code&gt;generateMedia()&lt;/code&gt; wraps OpenAI, Google, Runway, fal, or an OpenRouter route, you're left holding the same thing: an ephemeral URL you now have to productionise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The DIY way:&lt;/strong&gt; download, race the expiry, persist, then start the rest of the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutObjectCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelOutputUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// any provider; ephemeral&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelOutputUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// race the clock&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Model URL expired: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`generations/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// Still TODO, separately: transform/transcode, CDN, brand check, metadata store…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The pluggable way:&lt;/strong&gt; hand the same URL to a media pipeline and get a production asset back in one call. (This example uses Cloudinary, but the shape is what matters.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudinary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudinary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// configured from CLOUDINARY_URL&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent-generations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelName&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;            &lt;span class="c1"&gt;// searchable metadata&lt;/span&gt;
  &lt;span class="na"&gt;eager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;fetch_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="c1"&gt;// optimised derivative&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secure_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// permanent original, CDN-delivered&lt;/span&gt;
&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eager&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;secure_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// optimised WebP thumbnail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It fetches the bytes (no expiry race in your code), stores a permanent original, derives renditions on demand, and tags it with its metadata. Pass &lt;code&gt;resource_type: "video"&lt;/code&gt; and the eager transforms become an adaptive stream and a poster frame instead of image crops. Same call for an image from one provider or a clip from another, and the agent ends up holding one stable, branded, findable URL, no per-model recipe required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or generate straight into your asset platform
&lt;/h2&gt;

&lt;p&gt;If the generator and the asset platform are the same system, there's no ephemeral URL to rescue in the first place. That's the idea behind Cloudinary's new &lt;a href="https://cloudinary.com/documentation/image_generation_addon" rel="noopener noreferrer"&gt;&lt;strong&gt;Image Generation API&lt;/strong&gt;&lt;/a&gt;: a single &lt;code&gt;text_to_image&lt;/code&gt; endpoint across best-in-class model families (FLUX.2, Nano Banana, GPT Image, Recraft, Ideogram), much like the routing layer from earlier, except the output lands as a managed asset rather than a disappearing link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.cloudinary.com/v2/generate/&amp;lt;CLOUD_NAME&amp;gt;/text_to_image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;API_KEY&amp;gt;:&amp;lt;API_SECRET&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "prompt": "a golden retriever wearing a tiny astronaut helmet",
    "model":  { "family": "flux", "tier": "premium" },
    "target": { "target_type": "managed_asset" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is a permanent &lt;code&gt;secure_url&lt;/code&gt; plus the &lt;code&gt;asset_id&lt;/code&gt; and &lt;code&gt;public_id&lt;/code&gt; you use everywhere else in Cloudinary. The image is born persisted, optimisable, and ready to deliver; generation and production collapse into one step. (Omit &lt;code&gt;model&lt;/code&gt; for the Nano Banana default; &lt;code&gt;target&lt;/code&gt; defaults to a short-lived temporary asset if you'd rather not persist.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The honest version: if all you need is the cheapest bucket, R2 wins on raw storage cost, and Supabase now bundles basic transforms and a CDN. Reach for a full media platform when the &lt;em&gt;whole&lt;/em&gt; job matters (persist, transform, deliver, keep on-brand, organise) and you'd rather make one call than orchestrate five.&lt;/p&gt;

&lt;p&gt;But the broader lesson outlives any product choice. &lt;strong&gt;In an agentic system, a generated file is an intermediate value, not a deliverable.&lt;/strong&gt; The model is a component, not the finish line. Treat the path from raw output to production asset as a real pipeline (whether you build it from buckets, workers, transcoders and a metadata table, or hand it to something that does all five) and your agents stop storing links to nothing.&lt;/p&gt;




&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uq5u6" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Stopped Choosing Image Models Manually</title>
      <dc:creator>Ebony Louis</dc:creator>
      <pubDate>Tue, 30 Jun 2026 16:40:40 +0000</pubDate>
      <link>https://dev.to/cloudinary/i-stopped-choosing-image-models-manually-48p7</link>
      <guid>https://dev.to/cloudinary/i-stopped-choosing-image-models-manually-48p7</guid>
      <description>&lt;p&gt;Every few weeks there's a new image model everyone says you should be using: FLUX, Recraft, Ideogram, GPT Image. Each one has different strengths, different tradeoffs, and another decision for developers to make before they've even written a line of code.&lt;/p&gt;

&lt;p&gt;So when I started experimenting with &lt;a href="https://cloudinary.com/documentation/image_generation_addon?utm_source=dev-dot-to&amp;amp;utm_content=imagegen-models-via-console#generating_images_via_the_console" rel="noopener noreferrer"&gt;Cloudinary's new Image Generation API&lt;/a&gt;, I caught myself doing something familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flux&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I realized I shouldn't be choosing the model at all.&lt;/p&gt;

&lt;p&gt;I've spent the last few years building AI agents that decide what actions to take based on context. They choose tools, call APIs, verify their own work. So I stopped making this decision myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with hardcoding models
&lt;/h2&gt;

&lt;p&gt;Most examples, including the ones you'll find in documentation, explicitly choose a model. That makes perfect sense for a quickstart, but production applications don't receive the same prompt over and over again.&lt;/p&gt;

&lt;p&gt;One request might be "create a photorealistic product shot." Another might be "design a conference poster with large readable typography." Another might be "generate a hand drawn sketchnote from these lecture notes." You probably wouldn't use the same model for all three. Different models have genuinely different strengths. Some are better at photorealism, others produce stronger illustrations, others handle typography more effectively.&lt;/p&gt;

&lt;p&gt;So instead of asking which model I should hardcode, I started asking whether my agent should decide instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  My first attempt was simple
&lt;/h2&gt;

&lt;p&gt;I wrote a &lt;code&gt;choose_model()&lt;/code&gt; function that matches keywords in the prompt to model strengths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;choose_model_simple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;prompt_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompt_lower&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;poster&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typography&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sign&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ideogram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt likely needs strong text handling.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompt_lower&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;realistic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portrait&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cinematic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flux&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt is photorealistic, so Flux is a strong fit.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompt_lower&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;illustration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sketchnote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diagram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recraft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt sounds illustration-heavy.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nano-banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No specific requirement detected; using the default.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works for obvious cases. But it breaks the moment prompts get ambiguous, and prompts are almost always a little ambiguous.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dfda73gxf84vv5lbdob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dfda73gxf84vv5lbdob.png" alt="keyword router" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The keyword router explains why it selected Ideogram before calling Cloudinary.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why keyword matching isn't enough
&lt;/h2&gt;

&lt;p&gt;Try this prompt: "A sketchnote of my lecture on photorealistic rendering techniques."&lt;/p&gt;

&lt;p&gt;A keyword router sees "photorealistic" and picks Flux. But the output you actually want is an illustration. The word photorealistic describes the &lt;em&gt;subject&lt;/em&gt;, not the &lt;em&gt;style&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A keyword router matches words. Your agent understands intent. It understands that the output should be a sketchnote, not a photorealistic rendering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Swapping in Claude
&lt;/h2&gt;

&lt;p&gt;Replacing the keyword router with Claude is one function change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;choose_model_claude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a model router for an image generation pipeline.
Given a prompt, return ONLY a JSON object:
{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;family&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;One sentence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}
Available families: flux, ideogram, recraft, gpt-image, nano-banana.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Prompt: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keyword router makes a decision by matching keywords. Claude reasons about the intent of the request before choosing a model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzndkm5vu0uz4tzpz38ff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzndkm5vu0uz4tzpz38ff.png" alt="diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice what changed. The agent isn't generating the image, it isn't replacing Cloudinary. It's making a routing decision. Cloudinary remains the execution layer while the agent decides which model best fits the request. Because Cloudinary exposes multiple model families through the same API, the router only has to decide &lt;strong&gt;what&lt;/strong&gt; to use, not &lt;strong&gt;how&lt;/strong&gt; to call it.&lt;/p&gt;

&lt;p&gt;This example uses a small routing script to make that decision explicit, but the same idea applies to larger agent workflows. If you're already building agents, model selection can become just another reasoning step before the API call. I wrote it as a standalone script because it's easier to understand, easier to experiment with, and easier to adapt to your own projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;p&gt;The model choice matters more than I expected. The same prompt can produce very different results depending on which model you pick, and those differences aren't subtle. Text rendering in particular is still surprisingly hard. Generating a beautiful image is relatively easy, but generating a beautiful image with readable text is a different problem entirely. &lt;/p&gt;

&lt;p&gt;What I found most interesting was that the routing decision itself was the interesting part, not the generation. Once you separate "which model should I use" from "call the API," that first question becomes something an agent can reason about rather than something you have to answer in advance every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Cloudinary?
&lt;/h2&gt;

&lt;p&gt;I wasn't looking for another image model. I was looking for a consistent interface to multiple image models. Without that, swapping models means learning new APIs, new authentication, and new response shapes.&lt;/p&gt;

&lt;p&gt;With Cloudinary's Image Generation API, the router just returns a family name. Everything else stays the same.&lt;/p&gt;

&lt;p&gt;Generated images also land directly in Cloudinary as managed assets, immediately available for transformation and delivery without me needing to do any extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;If you'd like to experiment with this pattern, I've shared the complete routing script as a GitHub Gist.&lt;/p&gt;

&lt;p&gt;The script uses Cloudinary's Image Generation API to select a model, generate the image, and save it as a managed asset.&lt;/p&gt;




&lt;p&gt;To get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View the &lt;a href="https://gist.github.com/EbonyLouis/cafc39bf5b438b38585e639df16d6477" rel="noopener noreferrer"&gt;routing script (GitHub Gist)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://cloudinary.com/documentation/image_generation_addon?utm_source=dev-dot-to&amp;amp;utm_content=imagegen-models-via-console#generating_images_via_the_console" rel="noopener noreferrer"&gt;Image Generation docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browse the &lt;a href="https://link.cloudinary.com/uq5vj" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where I think this is going
&lt;/h2&gt;

&lt;p&gt;Every few weeks there's another model everyone says you should be using.&lt;/p&gt;

&lt;p&gt;I don't think any of us will be choosing most of them manually.&lt;/p&gt;

&lt;p&gt;We'll be spending more time teaching our agents how to choose instead.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uqvzi" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automate Your Media at Scale: 6 Cloudinary Workflows You Can Import Into n8n Today</title>
      <dc:creator>Eitan Peer</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:58:22 +0000</pubDate>
      <link>https://dev.to/cloudinary/automate-your-media-at-scale-6-cloudinary-workflows-you-can-import-into-n8n-today-22m1</link>
      <guid>https://dev.to/cloudinary/automate-your-media-at-scale-6-cloudinary-workflows-you-can-import-into-n8n-today-22m1</guid>
      <description>&lt;p&gt;Media is where automation pipelines quietly fall apart.&lt;/p&gt;

&lt;p&gt;This post walks through six importable n8n workflows, built on the Cloudinary community node, that hand those media steps back to the pipeline.&lt;/p&gt;

&lt;p&gt;You've wired up a beautiful no-code flow — a new product lands in your PIM, a webhook fires, records sync. Then someone has to &lt;em&gt;resize the hero image for the storefront, cut the launch video into a square for the feed and a vertical for Reels, export a poster frame, and add captions for accessibility.&lt;/em&gt; So the "automated" pipeline stops and waits for a human with Photoshop and a video editor.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/n8n-nodes-cloudinary" rel="noopener noreferrer"&gt;Cloudinary community node for n8n&lt;/a&gt; just closed that gap. The latest release turns Cloudinary's transformation, delivery, and video player engine into native n8n steps, so media work becomes just another node in the flow.&lt;/p&gt;

&lt;p&gt;Below are &lt;strong&gt;six ready-to-import example workflows&lt;/strong&gt; — two clicks to load — designed to run on a free Cloudinary account, subject to &lt;a href="https://cloudinary.com/pricing" rel="noopener noreferrer"&gt;usage limits&lt;/a&gt; (credits, video processing, and some add-ons count against your plan). Each one is self-describing (every workflow ships with a sticky-note README inside n8n) and contains &lt;strong&gt;no credentials&lt;/strong&gt; — you add your own. Each one below links straight to its JSON.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💾 &lt;strong&gt;One-time setup gotcha worth knowing up front:&lt;/strong&gt; after you import a workflow and pick your Cloudinary credential on the first node, &lt;strong&gt;press Cmd/Ctrl+S to save before running.&lt;/strong&gt; That single save persists the credential to every Cloudinary node in the flow at once — so a full &lt;strong&gt;Execute Workflow&lt;/strong&gt; run finds credentials everywhere. Skip the save and the run can fail node-after-node with &lt;em&gt;"Node does not have any credentials set,"&lt;/em&gt; even though the credential looks selected. (More in Add your Cloudinary credentials.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One master video cut for every social platform&lt;/li&gt;
&lt;li&gt;Uniform, AI-filled catalog images at scale&lt;/li&gt;
&lt;li&gt;A paste-in responsive srcset&lt;/li&gt;
&lt;li&gt;An embeddable, adaptive video player&lt;/li&gt;
&lt;li&gt;An automated media-library governance audit&lt;/li&gt;
&lt;li&gt;Accessible video with AI-generated captions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's new in the Cloudinary n8n node
&lt;/h2&gt;

&lt;p&gt;The release adds three things that matter for automation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A Transform resource that builds delivery URLs — with no upload and no API call.&lt;/strong&gt; Optimize, Resize and Crop with an additional conversion option for images and trimming and thumbnails for video. Each operation returns a finished &lt;code&gt;secure_url&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; a reusable transformation string. Because they're pure URL construction, they're instant and free of round-trips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Transformations that compose three ways.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compose: Combine Transformations&lt;/strong&gt; chains several steps (trim → crop → optimize) inside a single node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continue From Transformation&lt;/strong&gt; chains steps &lt;em&gt;across&lt;/em&gt; nodes — wire one node's transformation output into the next node's input and they compound into one URL.&lt;/li&gt;
&lt;li&gt;And the same transformation string drops straight into the &lt;strong&gt;Video Player&lt;/strong&gt; widget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Advanced media features — no code required:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generative Fill&lt;/strong&gt; — extend an image to a new shape with AI-painted background instead of cropping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Player&lt;/strong&gt; widget — generate an embeddable player with HLS adaptive streaming (and an MP4 progressive fallback), a poster, picture-in-picture, and &lt;strong&gt;on-demand AI captions, transcripts, and chapters&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asset Search&lt;/strong&gt; — paginate through all matching assets, one n8n item per asset, for governance and audits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's put them to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One source video → a cut for every platform
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; marketing ships one master video. It needs a 9:16 for TikTok/Reels, a 1:1 for the feed, and a 16:9 for YouTube — each trimmed, each with a poster frame, each optimized. Today that's a manual export-fest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781532950/ax/01-social-video-variations.json" rel="noopener noreferrer"&gt;01-social-video-variations.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upload the master once, then three &lt;strong&gt;Compose: Combine Transformations&lt;/strong&gt; nodes each build a single delivery URL that &lt;strong&gt;trims → crops to the platform's aspect ratio (with auto-focus to keep the subject in frame) → auto-optimizes&lt;/strong&gt;. A matching &lt;strong&gt;Video: Thumbnail&lt;/strong&gt; generates each poster. A final Code node collects everything into a tidy list of &lt;code&gt;{platform, video_url, poster_url}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvvnvnqze7l4h2iq1e5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvvnvnqze7l4h2iq1e5q.png" alt="The workflow canvas showing the fan-out from Upload Source Video to the three platform variants and their posters." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; one trigger produces every platform's asset, ready to hand to a social scheduler or CMS. Add a platform by copying a node and changing one aspect ratio.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bulk product photos → uniform, AI-filled catalog images
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; product photography arrives in every shape and on every background. Your storefront wants identical, square, optimized product images for thousands of SKUs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781533869/ax/02-ecommerce-image-pipeline.json" rel="noopener noreferrer"&gt;02-ecommerce-image-pipeline.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each product, the flow ingests the photo, then runs &lt;strong&gt;Image: Crop with Generative Fill&lt;/strong&gt; — instead of cropping the product, Cloudinary &lt;em&gt;extends&lt;/em&gt; the canvas to a perfect 1200×1200 square and &lt;strong&gt;paints a matching background with AI&lt;/strong&gt; (guided by a prompt like &lt;em&gt;"clean seamless studio background"&lt;/em&gt;). It then chains a &lt;strong&gt;Convert → WebP&lt;/strong&gt; step onto that same transformation. Out comes one catalog-ready URL per product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Feitanpeer-res.cloudinary.com%2Fimage%2Fupload%2Fc_pad%2Cw_1200%2Ch_1200%2Cb_white%2Fbo_3px_solid_rgb%3A888888%2Fl_n8n_blog%3Aproducts%3Aanalog-watch%2Fb_gen_fill%2Cc_pad%2Cw_1200%2Ch_1200%2Fbo_3px_solid_rgb%3A888888%2Ffl_layer_apply%2Cg_west%2Cx_1206%2Fb_white%2Cco_rgb%3A222222%2Cl_text%3AArial_56_bold%3A%25C2%25A0Original%25C2%25A0%2Ffl_layer_apply%2Cg_north_west%2Cx_40%2Cy_40%2Fb_white%2Cco_rgb%3A222222%2Cl_text%3AArial_56_bold%3A%25C2%25A0Generative%2520Fill%25C2%25A0%2Ffl_layer_apply%2Cg_north_west%2Cx_1246%2Cy_40%2Ff_auto%2Fq_auto%2Fn8n_blog%2Fproducts%2Fanalog-watch" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Feitanpeer-res.cloudinary.com%2Fimage%2Fupload%2Fc_pad%2Cw_1200%2Ch_1200%2Cb_white%2Fbo_3px_solid_rgb%3A888888%2Fl_n8n_blog%3Aproducts%3Aanalog-watch%2Fb_gen_fill%2Cc_pad%2Cw_1200%2Ch_1200%2Fbo_3px_solid_rgb%3A888888%2Ffl_layer_apply%2Cg_west%2Cx_1206%2Fb_white%2Cco_rgb%3A222222%2Cl_text%3AArial_56_bold%3A%25C2%25A0Original%25C2%25A0%2Ffl_layer_apply%2Cg_north_west%2Cx_40%2Cy_40%2Fb_white%2Cco_rgb%3A222222%2Cl_text%3AArial_56_bold%3A%25C2%25A0Generative%2520Fill%25C2%25A0%2Ffl_layer_apply%2Cg_north_west%2Cx_1246%2Cy_40%2Ff_auto%2Fq_auto%2Fn8n_blog%2Fproducts%2Fanalog-watch" alt="An off-center product photo on the left, the square AI-filled result on the right." width="2412" height="1206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; consistent, optimized product imagery with zero designer time — and it scales to your whole catalog by swapping the input list for a Cloudinary Search or a PIM lookup.&lt;/p&gt;

&lt;p&gt;Generative Fill is a generative-AI effect: it uses a special transformation count, is subject to your &lt;a href="https://cloudinary.com/pricing" rel="noopener noreferrer"&gt;plan limits&lt;/a&gt;, and may render asynchronously on the first request. &lt;a href="https://cloudinary.com/users/register_free" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt; to try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. One image → a responsive srcset you can paste into any page
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; you're still hand-exporting @1x/@2x assets, and shipping desktop-sized images to phones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781533869/ax/03-responsive-srcset.json" rel="noopener noreferrer"&gt;03-responsive-srcset.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Point it at one source image and a list of widths. It builds an &lt;strong&gt;Image: Resize&lt;/strong&gt; URL per width (using &lt;em&gt;Limit&lt;/em&gt; fit so it never upscales), chains &lt;strong&gt;Optimize&lt;/strong&gt; (&lt;code&gt;f_auto&lt;/code&gt;/&lt;code&gt;q_auto&lt;/code&gt;) onto each so Cloudinary serves AVIF/WebP/JPEG per browser at the right quality, and assembles a ready-to-paste &lt;code&gt;&amp;lt;img srcset=…&amp;gt;&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tyo41izoak8s5h3dkw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tyo41izoak8s5h3dkw1.png" alt="The Build srcset HTML node output, showing the generated img srcset markup." width="799" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; crisp Retina displays &lt;em&gt;and&lt;/em&gt; lightweight mobile loads from a single asset — with the markup generated for you. Change a breakpoint by editing one array.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A source video → a polished, embeddable player
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; putting a real video player on a landing page or CMS usually means a JS build step and a streaming setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781599646/ax/04-embeddable-video-player.json" rel="noopener noreferrer"&gt;04-embeddable-video-player.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow trims a preview cut, grabs a matching poster frame (chained onto the trim), and generates a &lt;strong&gt;Video Player&lt;/strong&gt; embed with &lt;strong&gt;HLS adaptive streaming and an MP4 progressive fallback&lt;/strong&gt;, picture-in-picture, playsinline, and muted autoplay-on-scroll. It outputs both an &lt;code&gt;embed_url&lt;/code&gt; and a responsive &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; snippet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclj8hz7j1o504lhyt5j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclj8hz7j1o504lhyt5j6.png" alt="The rendered Cloudinary Video Player in a browser, with the controls and poster visible." width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; a production-grade, adaptive player as a paste-in snippet — no front-end code.&lt;/p&gt;

&lt;p&gt;The workflow feeds the player the &lt;em&gt;trim&lt;/em&gt; transformation, not an &lt;em&gt;optimize&lt;/em&gt; one — adaptive streaming (HLS/DASH) can't be combined with a format-pinned transform (&lt;code&gt;f_auto:video&lt;/code&gt;). Helpfully, the node catches that exact conflict and tells you how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Audit and govern a sprawling library — automatically
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; at scale, media libraries rot. Untagged assets, oversized files, content nobody owns. Cleaning it up by hand doesn't happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781533869/ax/05-asset-governance-audit.json" rel="noopener noreferrer"&gt;05-asset-governance-audit.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asset → Search Assets&lt;/strong&gt; with &lt;em&gt;Return All&lt;/em&gt; paginates through all matching assets (it handles pagination and surfaces rate-limit errors clearly), emitting one n8n item per asset with its tags. The template is scoped to &lt;code&gt;tags=n8n-blog&lt;/code&gt; so it's safe to run as-is — change or remove that expression (e.g. &lt;code&gt;folder:products/*&lt;/code&gt;, or leave it empty) to scan more broadly, up to your whole library. A Code node applies your governance rules — here, &lt;em&gt;flag anything untagged or over 1 MB&lt;/em&gt;. Flagged assets get a &lt;code&gt;needs-review&lt;/code&gt; tag appended (existing tags preserved); everything rolls up into an audit report you can send to Slack or email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ls0b0pij5oebglnuqw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ls0b0pij5oebglnuqw2.png" alt="The Audit Report workflow canvas." width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; continuous, policy-driven hygiene that works identically on 50 assets or 50,000. Swap the tag action for structured-metadata updates or (carefully) deletes.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Accessible video with AI-generated captions — no .vtt files
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; captions are often required by accessibility standards and regulations (such as WCAG, the ADA, and the European Accessibility Act), and a global audience needs subtitles in several languages. Authoring caption files for every video, by hand, doesn't scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt; &lt;a href="https://product-demos-res.cloudinary.com/raw/upload/v1781599628/ax/06-accessible-video-player.json" rel="noopener noreferrer"&gt;06-accessible-video-player.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the workflow that shows what Cloudinary's video features can do. The &lt;strong&gt;Video Player&lt;/strong&gt; node ships an accessibility-first config — keyboard controls on, &lt;strong&gt;no autoplay-with-sound&lt;/strong&gt;, playsinline, chapters button visible — plus an &lt;strong&gt;AI-Generated Content&lt;/strong&gt; section that's just a set of toggles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate Captions&lt;/strong&gt; → the player &lt;strong&gt;auto-generates a transcript&lt;/strong&gt; and shows it as toggleable captions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Title / Description / Chapters&lt;/strong&gt; → AI-generated metadata and chapter markers for navigation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;enabled by default&lt;/strong&gt; on most accounts, so there's nothing to switch on. The player only generates content that doesn't already exist, returning a 202 while it works and caching the result. Nobody writes a caption file: no code is needed to generate captions — the workflow's Code node only formats the player embed snippet.&lt;/p&gt;

&lt;p&gt;Need subtitles in other languages? Set &lt;strong&gt;Subtitle Languages&lt;/strong&gt; (e.g. &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;fr-FR&lt;/code&gt;) for auto-translated tracks. Translation uses the &lt;strong&gt;Google Translate add-on&lt;/strong&gt;: register for it (free tier available) on the &lt;a href="https://console.cloudinary.com/settings/addons" rel="noopener noreferrer"&gt;Cloudinary Add-ons page&lt;/a&gt;, then enable it under &lt;strong&gt;Settings → Security → Unsigned add-on transformations&lt;/strong&gt;. The original-language captions above need no add-on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx09c4vqzzrylyv22u2yp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx09c4vqzzrylyv22u2yp.png" alt="The player with the auto-generated captions toggled on and the language menu open." width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The win:&lt;/strong&gt; videos with dialogue can get captions, chapters, and optional translated tracks automatically — as part of the same flow that published them, with no caption files to author.&lt;/p&gt;

&lt;p&gt;Auto-generation lives in the player's JS config (not iframe URL params), so this workflow outputs a &lt;code&gt;player.source(...)&lt;/code&gt; snippet for the &lt;a href="https://cloudinary.com/documentation/cloudinary_video_player" rel="noopener noreferrer"&gt;Cloudinary Video Player SDK&lt;/a&gt;. On most accounts the unsigned actions for generating video details, transcripts, and chapters are enabled by default — see the &lt;a href="https://cloudinary.com/documentation/video_player_customization" rel="noopener noreferrer"&gt;customization docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to import these workflows
&lt;/h2&gt;

&lt;p&gt;Every workflow is a single &lt;code&gt;.json&lt;/code&gt; file — each section above links straight to it. Importing one takes a few seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the Cloudinary node (once)
&lt;/h3&gt;

&lt;p&gt;Follow &lt;a href="https://docs.n8n.io/integrations/community-nodes/installation/verified-install/#install-a-community-node" rel="noopener noreferrer"&gt;these instructions&lt;/a&gt; to install the Cloudinary community node (&lt;code&gt;n8n-nodes-cloudinary&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufskmpi32nq7lg28p1wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufskmpi32nq7lg28p1wv.png" alt="The " width="766" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Import the workflow
&lt;/h3&gt;

&lt;p&gt;Open the workflows list, click the &lt;strong&gt;⋯&lt;/strong&gt; menu (top-right) → &lt;strong&gt;Import from File…&lt;/strong&gt;, and select one of the &lt;code&gt;.json&lt;/code&gt; files. (You can also drag the file onto the canvas.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxf905bk6t4llrl856cwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxf905bk6t4llrl856cwk.png" alt="The workflow ⋯ menu open, with Import from File… highlighted." width="762" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow opens with its sticky-note README already on the canvas explaining what every node does.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add your Cloudinary credentials
&lt;/h3&gt;

&lt;p&gt;The templates contain &lt;strong&gt;no connection details&lt;/strong&gt; — they're yours to add.&lt;/p&gt;

&lt;p&gt;First, create the credential once: in the &lt;strong&gt;Credentials&lt;/strong&gt; sidebar → &lt;strong&gt;Add Credential&lt;/strong&gt; → &lt;strong&gt;Cloudinary API&lt;/strong&gt;, and paste your &lt;strong&gt;Cloud name&lt;/strong&gt;, &lt;strong&gt;API Key&lt;/strong&gt;, and &lt;strong&gt;API Secret&lt;/strong&gt; from the &lt;a href="https://console.cloudinary.com/settings/api-keys" rel="noopener noreferrer"&gt;Cloudinary Console → API Keys&lt;/a&gt;. Save it. Now it's offered in the dropdown on every Cloudinary node, in this template and every one you import later.&lt;/p&gt;

&lt;p&gt;Then, on the imported workflow, open the &lt;strong&gt;first&lt;/strong&gt; Cloudinary node and pick that credential from the &lt;strong&gt;Credential&lt;/strong&gt; dropdown. n8n back-fills the other nodes that share it — you'll see &lt;em&gt;"Added this credential to N other node(s)."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💾 &lt;strong&gt;Then save before you run — this is the important bit.&lt;/strong&gt; Press &lt;strong&gt;Cmd/Ctrl+S&lt;/strong&gt; right after picking the credential. The save persists the binding to every Cloudinary node at once, so a full &lt;strong&gt;Execute Workflow&lt;/strong&gt; run finds credentials everywhere.&lt;/p&gt;

&lt;p&gt;If you &lt;em&gt;don't&lt;/em&gt; save first, the full run can fail one node at a time with &lt;strong&gt;"Node does not have any credentials set"&lt;/strong&gt; — even though the credential clearly looks selected on the node. (Tell-tale sign: running a node on its own with &lt;strong&gt;Execute step&lt;/strong&gt; works, but &lt;strong&gt;Execute Workflow&lt;/strong&gt; fails.) A save fixes it in one shot.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Run it
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Test workflow&lt;/strong&gt;. The first few workflows upload a public Cloudinary demo asset into &lt;em&gt;your&lt;/em&gt; account so everything downstream resolves against your cloud — so they run out of the box, then become yours the moment you point them at your own media.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to take it next
&lt;/h2&gt;

&lt;p&gt;These six are starting points, not end points. Because every Transform op emits a reusable &lt;code&gt;transformation&lt;/code&gt; string and every node speaks plain n8n JSON, you can drop these media steps into &lt;em&gt;any&lt;/em&gt; pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger the social-variations flow from a &lt;strong&gt;webhook&lt;/strong&gt; when a new video lands in your CMS.&lt;/li&gt;
&lt;li&gt;Feed the e-commerce pipeline from a &lt;strong&gt;Google Sheet&lt;/strong&gt; or your &lt;strong&gt;PIM&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Schedule the governance audit to run nightly and post the report to &lt;strong&gt;Slack&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Chain the player + AI-captions flow onto the end of any video upload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the node, import a workflow, and let the media steps run themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which Cloudinary feature are you missing in the n8n integration?&lt;/strong&gt; We're actively expanding the node — tell us what you'd build next by &lt;a href="https://github.com/cloudinary/n8n-nodes-cloudinary/issues/new" rel="noopener noreferrer"&gt;opening an issue on GitHub&lt;/a&gt; or reaching out through &lt;a href="https://support.cloudinary.com/hc/en-us/requests/new" rel="noopener noreferrer"&gt;Cloudinary Support&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uqET9" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;👉 &lt;strong&gt;Get the node:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/n8n-nodes-cloudinary" rel="noopener noreferrer"&gt;&lt;code&gt;n8n-nodes-cloudinary&lt;/code&gt; on npm&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;Request a feature / report a bug:&lt;/strong&gt; &lt;a href="https://github.com/cloudinary/n8n-nodes-cloudinary/issues" rel="noopener noreferrer"&gt;GitHub issues&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;How to import a workflow:&lt;/strong&gt; &lt;a href="https://docs.n8n.io/workflows/export-import/" rel="noopener noreferrer"&gt;n8n's import &amp;amp; export guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>n8n</category>
      <category>video</category>
      <category>a11y</category>
    </item>
    <item>
      <title>What Makes An Agent Loop Useful?</title>
      <dc:creator>Ebony Louis</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:32:34 +0000</pubDate>
      <link>https://dev.to/cloudinary/what-makes-an-agent-loop-useful-d5e</link>
      <guid>https://dev.to/cloudinary/what-makes-an-agent-loop-useful-d5e</guid>
      <description>&lt;p&gt;Over the last few days, my feed has been filled with posts about agent loops.&lt;/p&gt;

&lt;p&gt;The message is usually some variation of the same idea: stop prompting agents one step at a time and start designing systems that can act, evaluate their work, and continue toward a goal on their own. The future isn't us prompting agents all day. It's us designing loops that can keep making progress while we're doing something else, or even while we're asleep.&lt;/p&gt;

&lt;p&gt;That's old news.&lt;/p&gt;

&lt;p&gt;What I didn't understand was which &lt;strong&gt;parts&lt;/strong&gt; of the loop actually mattered.&lt;/p&gt;

&lt;p&gt;Was it the &lt;em&gt;model&lt;/em&gt;? The &lt;em&gt;tools&lt;/em&gt;? The &lt;em&gt;memory&lt;/em&gt;? The &lt;em&gt;trigger&lt;/em&gt;? The fact that it could run unattended?&lt;/p&gt;

&lt;p&gt;Most of the examples I found explained the concept. Few explained how to think about designing one. And even fewer showed what happened when things went wrong.&lt;/p&gt;

&lt;p&gt;So instead of reading another post about loops, I decided to build a simple one.&lt;/p&gt;

&lt;p&gt;Using Claude Code, &lt;a href="https://link.cloudinary.com/uqCfY" rel="noopener noreferrer"&gt;Cloudinary Skills&lt;/a&gt;, and &lt;a href="https://link.cloudinary.com/uqCkv" rel="noopener noreferrer"&gt;Cloudinary MCP servers&lt;/a&gt;, I created a media optimization loop with a simple objective: reduce delivery weight for image assets by at least 20%.&lt;/p&gt;

&lt;p&gt;There was only one question left to answer:&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Loop Useful?
&lt;/h2&gt;

&lt;p&gt;Not every automation is a loop.&lt;/p&gt;

&lt;p&gt;A useful loop needs a few things working together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsw74q33yphlaub6xz0lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsw74q33yphlaub6xz0lm.png" alt="Useful loop" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If one of those pieces is missing, the loop will start to break down.&lt;/p&gt;

&lt;p&gt;Without a trigger, nothing starts. Without a goal, it doesn't know what success looks like. Without knowledge, the agent guesses. Without actions, it can only make recommendations. Without memory, every run starts from scratch. Without verification, it's very difficult to know whether the loop is actually making progress or simply producing output that looks reasonable.&lt;/p&gt;

&lt;p&gt;That last point ended up becoming the most important lesson of the entire project. Of the eight pieces in that circle, one short chain running through the middle of it is what actually determines whether the loop succeeds. We'll dive more into that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal Layer
&lt;/h2&gt;

&lt;p&gt;Everything started with a simple goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reduce image delivery weight by at least 20%&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That objective determined what success looked like and ultimately drove every decision the loop made.&lt;/p&gt;

&lt;p&gt;At first that sounds obvious. It wasn't. Later in the project I discovered that a surprisingly large amount of loop behavior comes down to how clearly you define the goal in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Knowledge Layer
&lt;/h2&gt;

&lt;p&gt;The agent needed both Cloudinary knowledge and knowledge about how the loop itself should operate.&lt;/p&gt;

&lt;p&gt;I didn't want it guessing transformation syntax, trying to remember optimization best practices from training data, or inventing its own workflow every time it ran.&lt;/p&gt;

&lt;p&gt;To solve that, I used &lt;a href="https://link.cloudinary.com/uqCfY" rel="noopener noreferrer"&gt;Cloudinary Skills&lt;/a&gt; alongside a custom loop skill.&lt;/p&gt;

&lt;p&gt;The Cloudinary Skills acted as the domain knowledge layer. In this case, the transformation focused skill provided guidance on optimization strategies, transformation syntax, and Cloudinary best practices so the agent could make informed decisions without relying on memory or assumptions.&lt;/p&gt;

&lt;p&gt;The custom loop skill defined the workflow itself. It told the agent how to read the goal, inspect memory, evaluate results, update state, and decide whether the loop should continue or stop.&lt;/p&gt;

&lt;p&gt;Instead of telling the agent exactly what to do at every step, I gave it both reusable Cloudinary knowledge and a reusable workflow. The loop could then reason about the problem, take action, evaluate the outcome, and decide what to do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Action Layer
&lt;/h2&gt;

&lt;p&gt;Knowledge alone isn't enough. If the loop is going to do real work, it needs the ability to interact with your environment. That's where &lt;a href="https://link.cloudinary.com/uqCkv" rel="noopener noreferrer"&gt;Cloudinary MCP servers&lt;/a&gt; came in.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Asset Management MCP&lt;/em&gt; server allowed the loop to discover image assets larger than 1 MB, inspect their metadata, and prioritize the biggest optimization opportunities first.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Environment Configuration MCP&lt;/em&gt; server allowed the loop to create reusable named transformations when it identified optimization patterns that could be applied across multiple assets. Rather than repeatedly generating the same transformation logic, the loop could register and reuse optimization strategies across future runs.&lt;/p&gt;

&lt;p&gt;Together, the Skills and MCP servers gave the loop both knowledge and action. The Skills helped the agent reason about what should happen. The MCP servers allowed it to actually do it.&lt;/p&gt;

&lt;p&gt;What struck me most wasn't any individual MCP call. It was watching the loop move from reasoning to action. It wasn't simply recommending optimizations anymore. It could inspect my Cloudinary account, identify opportunities, and make changes based on what it found.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Memory Layer
&lt;/h2&gt;

&lt;p&gt;The loop also needed a way to remember what it had already done. I used a simple memory file that tracked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assets already evaluated&lt;/li&gt;
&lt;li&gt;Previous run summaries&lt;/li&gt;
&lt;li&gt;Named transformations that had been created&lt;/li&gt;
&lt;li&gt;Current loop status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allowed future runs to pick up where earlier runs left off rather than starting from scratch every time. The agent could see what had already been processed and focus only on remaining work.&lt;/p&gt;

&lt;p&gt;With the goal, knowledge, action, and memory layers in place, the loop could do real work and remember what it had done. What it couldn't yet do was tell whether that work was any good.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evaluation Layer (Where I Got It Wrong)
&lt;/h2&gt;

&lt;p&gt;The first version of the loop looked successful.&lt;/p&gt;

&lt;p&gt;It analyzed assets, generated optimization strategies, and reported an average savings of nearly 68%. If I had stopped there, I probably would have considered the project finished.&lt;/p&gt;

&lt;p&gt;The problem was that those savings were estimates. The loop wasn't actually verifying its work. It was looking at dimensions, formats, and known optimization patterns and making educated guesses about how much optimization should be possible. The numbers looked reasonable.&lt;/p&gt;

&lt;p&gt;Here's what was actually going on. My original loop instructions told the agent to evaluate whether an optimization met the savings target, but never specified &lt;em&gt;how&lt;/em&gt;. So the agent did what any reasonable agent would do when given an ambiguous instruction: it estimated. It knew the typical savings associated with common optimization strategies and used that knowledge to produce a confident sounding number.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fix wasn't a smarter model. It was a more precise instruction. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I added one section to the loop command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Step 8 — Evaluate results&lt;/span&gt;

For each asset processed, determine:
&lt;span class="p"&gt;-&lt;/span&gt; Estimated or measured optimized size (bytes)
&lt;span class="p"&gt;-&lt;/span&gt; Percent savings = (original - optimized) / original × 100
&lt;span class="p"&gt;-&lt;/span&gt; Did this asset meet the target savings threshold?
&lt;span class="p"&gt;-&lt;/span&gt; Confidence level: high (measured), medium (estimated from known asset type),
  low (estimated with significant uncertainty)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. I didn't tell the agent how to measure anything. I just told it that "estimated" and "measured" were different things, and that it needed to label which one it was giving me.&lt;/p&gt;

&lt;p&gt;That single instruction changed the entire run. The agent started actually fetching the optimized URLs and comparing real byte sizes against the originals, because it now had to be honest about which category its number fell into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification Changed Everything
&lt;/h2&gt;

&lt;p&gt;Here's the same batch of assets, estimated savings versus measured savings, after I added the evaluation step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce52fwp5qjj1ocq2edf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce52fwp5qjj1ocq2edf2.png" alt="asset results" width="799" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost nothing landed where the estimate said it would.&lt;/p&gt;

&lt;p&gt;The kitten GIF is the headline number: predicted at 70%, measured at 0%. I dug into it and found the test request wasn't negotiating WebP correctly, which caused Cloudinary's automatic format selection to fall back to GIF delivery. The optimization strategy wasn't wrong. The measurement exposed a gap between what should happen and what actually happened on that request.&lt;/p&gt;

&lt;p&gt;But the GIF isn't the real story. Look at &lt;code&gt;food/spices&lt;/code&gt;: estimated at 30%, measured at 61.3%. Look at &lt;code&gt;breakfast&lt;/code&gt;: estimated at 53%, measured at 77.3%. These weren't close. The agent's estimates were off by 5 to 31 percentage points, in both directions, across almost every asset in the batch.&lt;/p&gt;

&lt;p&gt;That's the part that actually changed how I think about this. The estimates weren't &lt;em&gt;imprecise&lt;/em&gt;. They were &lt;em&gt;unreliable&lt;/em&gt;, and not in a way you could correct for with a fudge factor, because sometimes the real number was way better than predicted and sometimes it was worse. Without &lt;em&gt;verification&lt;/em&gt;, there was no way to know which assets fell into which bucket. The loop would have reported a single confident average and moved on, and I would have believed it.&lt;/p&gt;

&lt;p&gt;With the Step 8 instruction in place, the loop now generates optimized Cloudinary URLs, requests the transformed assets, measures the actual delivered byte sizes, and compares those against the originals, flagging low confidence results so I know where to look first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Loop Was Right. I Was Wrong.
&lt;/h2&gt;

&lt;p&gt;After the first run with real measurements, the loop reported that seven eligible assets still remained.&lt;/p&gt;

&lt;p&gt;My first reaction was, why didn't it keep going?&lt;/p&gt;

&lt;p&gt;I went back and looked at the goal. The goal wasn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Optimize every asset larger than 1MB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reduce delivery weight by 20%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those sound similar. They produce completely different behavior.&lt;/p&gt;

&lt;p&gt;By the time the loop stopped, the measured average savings across the assets it had processed was already well past 20%. The goal had been met. From the loop's perspective, the work was done. Processing the remaining seven assets wouldn't have made the result any more true.&lt;/p&gt;

&lt;p&gt;The loop wasn't confused. I was.&lt;/p&gt;

&lt;p&gt;I had written down one goal and was carrying around a different one in my head. The loop followed the one that was actually written down, the one it could check against a real number. It stopped exactly when it was supposed to.&lt;/p&gt;

&lt;p&gt;A loop will faithfully pursue the goal you define, not the goal you intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chain That Was Actually Doing the Work
&lt;/h2&gt;

&lt;p&gt;Looking back, the GIF that measured 0% and the seven assets the loop left alone are the same lesson told twice.&lt;/p&gt;

&lt;p&gt;Earlier I showed the eight pieces a useful loop needs: &lt;em&gt;trigger&lt;/em&gt;, &lt;em&gt;goal&lt;/em&gt;, &lt;em&gt;knowledge&lt;/em&gt;, &lt;em&gt;action&lt;/em&gt;, &lt;em&gt;evaluation&lt;/em&gt;, &lt;em&gt;memory&lt;/em&gt;, &lt;em&gt;decision&lt;/em&gt;, &lt;em&gt;repeat&lt;/em&gt;. That's the architecture, the parts you need to assemble. But it's not the part that actually drives progress toward the goal. That part is smaller:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5hy1vhdqr5vmtmzdauz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5hy1vhdqr5vmtmzdauz.png" alt="Verified progress" width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the eight pieces is the architecture, this is the engine. Everything else in this project, the trigger, the Skills, the MCP servers, the memory file, exists to support this chain. But this is the part that makes something a loop instead of a script that runs once and reports back.&lt;/p&gt;

&lt;p&gt;A script can do the Action step. Only a loop can do &lt;em&gt;Measure&lt;/em&gt; → &lt;em&gt;Verify&lt;/em&gt; → &lt;em&gt;Decide&lt;/em&gt;, and use the answer to decide whether to repeat. The measurement gave the loop a number in the same units as the goal. Verification was the comparison between that number and the target. Decision was what the loop did with the answer, stop, because the goal was met, even though work remained.&lt;/p&gt;

&lt;p&gt;Take away the measurement, and the loop has nothing to verify. Take away verification, and the decision is just a guess wearing a confident tone. Take away the decision, and you have an agent that acts but never knows when to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Loop, In Practice
&lt;/h2&gt;

&lt;p&gt;Stepping back, here's what each layer actually was in this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: a command I run manually in Claude Code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: a markdown file stating the target savings percentage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge&lt;/strong&gt;: Cloudinary Skills and a custom loop skill, so the agent didn't have to guess either Cloudinary best practices or the workflow itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt;: Cloudinary MCP servers, so the agent could inspect and modify a real account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: a JSON file tracking what's been done and what's left&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation&lt;/strong&gt;: a comparison between the original and optimized byte sizes, measured against the goal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision&lt;/strong&gt;: the agent comparing the evaluation result to the goal and deciding to stop, continue, or flag for review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these layers are exotic. A markdown file, a JSON file, and two categories of tooling Cloudinary already publishes. What made it a loop wasn't the sophistication of any single piece, it was that each layer fed the next one something real, and that the chain in the middle, &lt;em&gt;Measure&lt;/em&gt; → &lt;em&gt;Verify&lt;/em&gt; → &lt;em&gt;Decide&lt;/em&gt;, was actually wired up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Didn't Build (Yet)
&lt;/h2&gt;

&lt;p&gt;This is intentionally a small loop. It doesn't use subagents, worktrees, or orchestration frameworks.&lt;/p&gt;

&lt;p&gt;Those things help loops scale, run faster, or handle more complexity, but they aren't what make something a loop in the first place.&lt;/p&gt;

&lt;p&gt;The trigger is also still manual. I start the loop by running a command in Claude Code, and in a production version I'd likely replace that with a schedule or an event such as a new asset upload.&lt;/p&gt;

&lt;p&gt;In other words, this isn't yet the "run while I sleep" version of a loop that many people are talking about. It's a deliberately small implementation designed to answer a simpler question:&lt;/p&gt;

&lt;p&gt;What actually matters when you're designing a loop?&lt;/p&gt;

&lt;p&gt;If you'd like to explore the implementation yourself, I've shared the files used in this experiment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/EbonyLouis/68b3a8a42af2fbb0787d89abbfffa395" rel="noopener noreferrer"&gt;optimization-loop.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/EbonyLouis/2ae368b7ec32940850d2b9b3bf786bd7" rel="noopener noreferrer"&gt;goal.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/EbonyLouis/965493dd96fd32089d41df87f1e3c07f" rel="noopener noreferrer"&gt;loop-memory.json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Going into this project, I wanted to understand what actually matters when you're designing an agent loop.&lt;/p&gt;

&lt;p&gt;I thought the answer might be the model, the tools, the memory layer, or the fact that the loop could run unattended.&lt;/p&gt;

&lt;p&gt;What I learned was that those things matter, but mostly because they support something else.&lt;/p&gt;

&lt;p&gt;A useful loop needs a clear goal, a way to measure progress against that goal, and a way to decide what to do next based on the result.&lt;/p&gt;

&lt;p&gt;The GIF that measured 0% savings and the seven assets the loop left untouched both pointed to the same lesson: a loop is only as useful as its ability to determine whether it's actually making progress toward the objective it was given.&lt;/p&gt;

&lt;p&gt;Everything else, including tools, MCP servers, Skills, subagents, and orchestration, helps a loop scale. What makes it useful is its ability to measure progress against a goal and use that information to decide what to do next.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uqvzi" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>agentloops</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>5 Projects, 2 Weeks, 1 Goal: Building a More Sustainable Web</title>
      <dc:creator>eugene musebe</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:59:23 +0000</pubDate>
      <link>https://dev.to/cloudinary/5-projects-2-weeks-1-goal-building-a-more-sustainable-web-2gc8</link>
      <guid>https://dev.to/cloudinary/5-projects-2-weeks-1-goal-building-a-more-sustainable-web-2gc8</guid>
      <description>&lt;p&gt;Developers talk about sustainability all the time. They talk about it at conferences, in Twitter threads, and in GitHub issue comments. What they do less often is &lt;em&gt;build&lt;/em&gt; something about it.&lt;/p&gt;

&lt;p&gt;That's what we asked our Cloudinary Creators community to do.&lt;/p&gt;

&lt;p&gt;April is widely celebrated as Earth Month, so it felt like the right moment to point that energy at something developers actually have control over: the weight of the web. Digital weight is a real problem, and optimization is still too often treated as an afterthought. We launched the Mini-Hack: Build a Mindful Digital Future, a two-week challenge centered on one idea: the web is heavier than it needs to be, and smarter media delivery is part of the fix.&lt;/p&gt;

&lt;p&gt;We wanted to see how developers would use Cloudinary to make digital experiences faster, lighter, and more sustainable across real contexts. No grand prize. No months-long runway. Just two weeks, a focused build, and a Cloudinary swag pack for every valid submission.&lt;/p&gt;

&lt;p&gt;The response was better than we expected. In just two weeks, creators built five projects around one shared goal: making digital experiences faster, lighter, and more mindful. A wardrobe. A marketing pipeline. A GitHub Action. An exam-week side project. That range mattered, and it proved the point of the Mini-Hack: sustainability becomes more useful when it shows up as working code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Asked For
&lt;/h2&gt;

&lt;p&gt;The brief was deliberately tight. Pick one focused idea. Build it. Show us what you've got.&lt;/p&gt;

&lt;p&gt;Submissions could be a working build with a live demo or repo, a short walkthrough video, or a blog post featuring a specific Cloudinary use case. The theme areas were lighter delivery, asset reuse, sustainability, and storytelling around climate awareness. Earth Month, translated into things you can actually ship.&lt;/p&gt;

&lt;p&gt;Two weeks. Simple as that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What They Built
&lt;/h2&gt;

&lt;p&gt;Five projects came in. All of them different. All of them more thoughtful than a standard hackathon entry tends to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EcoLens&lt;/strong&gt; came from someone who was in the middle of exam week and still shipped. The idea started from something painfully relatable: vibe-coded apps full of unoptimized image.png files that make websites slow for no reason. EcoLens lets you paste a website URL or a GitHub repo link, and it scans for media assets, showing you original vs. optimized sizes, bandwidth savings, and CO₂ reduction estimates, all powered by Cloudinary. The really interesting part is the four ways it lets you act on those results: a one-script-tag SDK method, an npm package for local projects, a GitHub PR flow that opens a pull request with optimized changes, and an AI-guided flow that generates prompts for Cursor, Claude, or Copilot. Stack: Next.js, TypeScript, Supabase, Cloudinary AI transformations, and Vercel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc56ixqi8gxqfpdh82bv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc56ixqi8gxqfpdh82bv2.png" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ecolens-cloudinary.vercel.app/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/ecolens" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EcoCloset&lt;/strong&gt; tackled the problem from an entirely different angle: your wardrobe. The insight is straightforward but easy to miss: most fast-fashion waste happens because people forget what they already own. EcoCloset uses Cloudinary AI to strip backgrounds from clothing photos and build a clean digital lookbook of what's already in your closet. The idea being that if you can actually &lt;em&gt;see&lt;/em&gt; your wardrobe before you hit checkout, you buy less duplicate stuff. It's a sustainability tool that works by improving your own visibility into what you already have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jllu4g0f40l1r5m1iam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jllu4g0f40l1r5m1iam.png" width="799" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ecoclo.netlify.app/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Eco Image Optimizer&lt;/strong&gt; kept things intentionally simple. Upload an image, run Cloudinary's &lt;code&gt;f_auto&lt;/code&gt; and &lt;code&gt;q_auto&lt;/code&gt; optimizations, and see the difference: original file size versus optimized, bandwidth savings, and estimated CO₂ reduction, side by side. No complex setup, no steep learning curve. Just a clear, practical demonstration that smarter media delivery has a measurable environmental impact, and that it doesn't have to be complicated to matter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxubjch1z0w9u2jzi3u3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxubjch1z0w9u2jzi3u3x.png" width="799" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eco-image-optimizer.vercel.app/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Jmos-Mbugua/eco-image-optimizer" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Green-pipe&lt;/strong&gt; went in a completely different direction and built for the developer tooling layer. It's a CLI tool and GitHub Action that scans any repository for media assets, optimizes them through Cloudinary, and generates a pull request with the results alongside a bandwidth and carbon report. The builder tested it against multiple real repositories, including the Kubernetes repository, and the numbers made a point all on their own. The project doesn't have a UI. It doesn't need one. It lives where developers already are.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/green-pipe" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PromptForge AI&lt;/strong&gt; approached the sustainability problem from the marketing workflow side. The premise: most marketing teams store the same asset in a dozen different formats and sizes, generating storage waste and carbon emissions for no real reason. PromptForge generates a complete marketing kit from a single prompt, then uses Cloudinary to dynamically transform one source asset into every required format. One file. Every format you need. None of the duplication you don't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mt04vgwtzfu9ahdu1jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mt04vgwtzfu9ahdu1jz.png" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/joyston_ccd43d53e268ec635/building-promptforge-ai-smarter-lighter-more-sustainable-marketing-3ldh"&gt;Blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Joystonm/PromptForge" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://promptforgegreen.vercel.app" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Judging Session
&lt;/h2&gt;

&lt;p&gt;On May 15th, we took it to LinkedIn Live.&lt;/p&gt;

&lt;p&gt;Our Official Cloudinary Creators pitched their projects in a live 30-minute session, with &lt;strong&gt;Sanjay Sarathy, VP of Developer Experience &amp;amp; Self-Service at Cloudinary&lt;/strong&gt;, sitting in as guest judge. It wasn't a polished product demo event. It was builders talking through real decisions, real tradeoffs, and real results. Jen Looper and other community members tuned in to watch.&lt;/p&gt;

&lt;p&gt;We did not rank the projects or select a single winner. Each submission brought a different approach to the same challenge, and each one showed real thought, effort, and practical use of Cloudinary. For this Mini-Hack, recognition was less about choosing a first place and more about celebrating what every creator managed to build in just two weeks.&lt;/p&gt;

&lt;p&gt;You can catch the recording here → &lt;a href="https://www.linkedin.com/posts/join-us-on-friday-to-watch-some-of-our-official-ugcPost-7460697818618494976-B7aK/" rel="noopener noreferrer"&gt;Watch the judging session&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What These Two Weeks Proved
&lt;/h2&gt;

&lt;p&gt;A two-week mini-hack with a sustainability constraint produced a CLI tool that works on the Kubernetes repo, a wardrobe app that reframes consumption, a PR automation tool, a marketing asset optimizer, and an image audit platform with four distinct developer workflows.&lt;/p&gt;

&lt;p&gt;That's a lot of ground to cover for a two-week community event with a swag pack as the prize.&lt;/p&gt;

&lt;p&gt;The thread connecting all five projects is the same: digital weight is a real problem, optimization is often an afterthought, and Cloudinary is useful enough that people keep finding new contexts to apply it in. A wardrobe. A marketing pipeline. A GitHub Action. An exam-week side project. That range matters. And it's a fitting way to mark Earth Month: not with a pledge, but with working code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're doing it again in June.&lt;/p&gt;

&lt;p&gt;We're not ready to share all the details yet, but if these two weeks are any indication, it's going to be worth your time. Stay tuned, and if you want to make sure you don't miss the announcement, keep an eye on the community and our social channels.&lt;/p&gt;

&lt;p&gt;The web is still heavier than it needs to be. There's more to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Be a Creator?
&lt;/h2&gt;

&lt;p&gt;Every project you just read about came from someone in our community who decided to build instead of just talk. You can be next.&lt;/p&gt;

&lt;p&gt;Here's where to start. &lt;/p&gt;

&lt;p&gt;Learn more about the Cloudinary Creators program and what it offers at &lt;a href="https://cloudinary.com/developers/community" rel="noopener noreferrer"&gt;https://cloudinary.com/developers/community&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Join the conversation, get build feedback, and hear about the next Mini-Hack first in our Discord community at &lt;a href="https://discord.com/invite/D8ddQj6KnH" rel="noopener noreferrer"&gt;https://discord.com/invite/D8ddQj6KnH&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When you're ready to go deeper, work through the Cloudinary Creators curriculum at &lt;a href="https://training.cloudinary.com/pages/c2c" rel="noopener noreferrer"&gt;https://training.cloudinary.com/pages/c2c&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two weeks was all it took last time. See what you can build with yours.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uqnvw" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>sustainability</category>
      <category>cloudinary</category>
      <category>community</category>
    </item>
    <item>
      <title>Your AI Agent Knows Cloudinary. Sort Of.</title>
      <dc:creator>Ebony Louis</dc:creator>
      <pubDate>Fri, 15 May 2026 17:01:40 +0000</pubDate>
      <link>https://dev.to/cloudinary/your-ai-agent-knows-cloudinary-sort-of-5cog</link>
      <guid>https://dev.to/cloudinary/your-ai-agent-knows-cloudinary-sort-of-5cog</guid>
      <description>&lt;p&gt;AI agents are incredible right up until they confidently generate something completely wrong and move on like nothing happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Ran Into
&lt;/h2&gt;

&lt;p&gt;Over the past year, I've spent a huge amount of time working with tools like Claude Code, Cursor, and Goose, and one pattern keeps showing up: the code &lt;em&gt;looks&lt;/em&gt; correct until you actually run it.&lt;/p&gt;

&lt;p&gt;That was one of the first things I ran into after joining &lt;a href="https://cloudinary.com" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I asked my agent to help me set up a simple Cloudinary integration in an application I was testing. The prompt itself was straightforward: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;generate a transformation URL, optimize an image, resize it to 800px wide, and apply automatic quality and format optimization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The output looked convincing enough that I almost trusted it. Which, honestly, was the problem. The transformation syntax wasn't following best practices, the SDK imports were outdated, and some delivery URL parameters would have behaved unpredictably in production. It didn't fail loudly. It failed quietly, which is worse.&lt;/p&gt;

&lt;p&gt;That’s exactly why we built the &lt;a href="https://link.cloudinary.com/uqCf0" rel="noopener noreferrer"&gt;Cloudinary Skills Pack&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Cloudinary Skills Pack Does
&lt;/h2&gt;

&lt;p&gt;The Cloudinary Skills Pack turns your AI agent into a Cloudinary expert with up to date workflows, documentation context, and implementation patterns built in.&lt;/p&gt;

&lt;p&gt;Instead of relying entirely on scattered examples pulled from docs, forums, blog posts, and old repositories, the agent can use skills that understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudinary transformation syntax&lt;/li&gt;
&lt;li&gt;current SDK usage patterns&lt;/li&gt;
&lt;li&gt;delivery URL workflows&lt;/li&gt;
&lt;li&gt;media optimization best practices&lt;/li&gt;
&lt;li&gt;documentation-aware implementation guidance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current Skills Pack includes three skills:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cloudinary-docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pulls relevant documentation context directly from the latest Cloudinary docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cloudinary-transformations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Converts natural language requests into valid Cloudinary image and video transformation URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cloudinary-react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provides modern React SDK integration patterns and troubleshooting guidance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 The cloudinary-docs and cloudinary-transformations skills are framework and language agnostic. Whether you're working with React, Next.js, or Vue on the frontend, or Node.js, Python, PHP, Android, iOS, or React Native on the backend, these skills work across your entire stack. The React skill exists specifically for teams using the Cloudinary React SDK.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Changed When I Tested It
&lt;/h2&gt;

&lt;p&gt;After installing the &lt;a href="https://link.cloudinary.com/uqCf0" rel="noopener noreferrer"&gt;Skills Pack&lt;/a&gt;, I reran the exact same prompts.&lt;/p&gt;

&lt;p&gt;The difference was immediate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Answers Became More Reliable
&lt;/h3&gt;

&lt;p&gt;One of the biggest improvements was documentation accuracy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; "How do I sign a Cloudinary delivery URL to make it secure?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Without the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhon54r4r1niwehuattvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhon54r4r1niwehuattvy.png" alt="Before: Goose explaining URL signing concept without cloudinary-docs skill" width="800" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jwk19tq5zkgukpak1lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jwk19tq5zkgukpak1lj.png" alt="After: Goose returning working Node.js implementation with cloudinary-docs skill installed" width="800" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; the skill, my agent explained the concept of signing but missed the actual implementation. &lt;strong&gt;With&lt;/strong&gt; the &lt;code&gt;cloudinary-docs&lt;/code&gt; skill installed, it pulled context directly from the latest Cloudinary documentation and returned working Node.js code and a CLI command I could run immediately.&lt;/p&gt;

&lt;p&gt;That distinction matters more than it sounds. Incorrect answers around delivery URLs and signing workflows often don't fail immediately. They become production issues later when security constraints start to matter.&lt;/p&gt;




&lt;h3&gt;
  
  
  Transformation URLs Became Production Ready
&lt;/h3&gt;

&lt;p&gt;The clearest improvement showed up in transformation generation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; "Write me a Cloudinary transformation URL that resizes an image to 800px wide, uses face aware cropping, and applies automatic format and quality optimization."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Without the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m45u53vtqc2r33ras1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m45u53vtqc2r33ras1a.png" alt="Before: Goose generating transformation URL with c_thumb without cloudinary-transformations skill" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvghcfsll94jup904h7tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvghcfsll94jup904h7tm.png" alt="After: Goose generating correct transformation URL with c_fill and g_face with cloudinary-transformations skill installed" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; the skill, my agent used &lt;code&gt;c_thumb&lt;/code&gt; instead of &lt;code&gt;c_fill&lt;/code&gt; for the crop mode. Technically the URL worked. Realistically it was one "why is this image cropped like that?" debugging session away from ruining my afternoon. &lt;strong&gt;With&lt;/strong&gt; the &lt;code&gt;cloudinary-transformations&lt;/code&gt; skill installed, it returned the correct crop mode, face aware gravity, and proper optimization parameters on the first attempt.&lt;/p&gt;

&lt;p&gt;This is also where the framework agnostic nature of the Skills Pack becomes important. The transformation skill works independently of React and improves Cloudinary workflows no matter what frontend framework or backend stack you are using.&lt;/p&gt;




&lt;h3&gt;
  
  
  The React SDK Imports Were Correct
&lt;/h3&gt;

&lt;p&gt;One of the workflows I tested used the Cloudinary React SDK, which is where the &lt;code&gt;cloudinary-react&lt;/code&gt; skill came in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; "Show me how to set up Cloudinary in a React app and display an optimized image using the Cloudinary React SDK."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Without the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvhbxphwtrodx3ghxz9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvhbxphwtrodx3ghxz9y.png" alt="before" width="799" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With the skill:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn731nxszohcgngms2ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn731nxszohcgngms2ac.png" alt="after" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; the skill, my agent recommended the deprecated &lt;code&gt;cloudinary-react&lt;/code&gt; package. The code looked believable enough that I probably would have copied it directly into my project if my terminal hadn't immediately humbled me. &lt;strong&gt;With&lt;/strong&gt; the skill installed, it recommended the correct &lt;code&gt;@cloudinary/react&lt;/code&gt; and &lt;code&gt;@cloudinary/url-gen&lt;/code&gt; packages and set up the right environment variables for Vite.&lt;/p&gt;

&lt;p&gt;Instead of debugging the generated integration, I could immediately continue building.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP Servers and Skills Solve Different Problems
&lt;/h2&gt;

&lt;p&gt;One thing became very clear while testing this.&lt;/p&gt;

&lt;p&gt;There's a growing conversation around whether skills are necessary if you already have MCP servers configured for your coding agent. After spending the last year heavily working with agents and skills, I don't think these are competing ideas at all.&lt;/p&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCP Servers&lt;/th&gt;
&lt;th&gt;Skills&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Give agents tools and actions&lt;/td&gt;
&lt;td&gt;Give agents workflows and implementation knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execute operations&lt;/td&gt;
&lt;td&gt;Teach best practices and platform specific patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Help agents do something&lt;/td&gt;
&lt;td&gt;Help agents do it correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Cloudinary MCP server can help an agent create a named transformation in your account.&lt;/p&gt;

&lt;p&gt;The skill helps the agent understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when named transformations are the better architectural choice&lt;/li&gt;
&lt;li&gt;how transformation syntax should be structured&lt;/li&gt;
&lt;li&gt;which optimization patterns are recommended&lt;/li&gt;
&lt;li&gt;how to avoid common implementation mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two approaches complement each other. MCP servers extend what an agent can do, while skills improve how accurately the agent understands a platform.&lt;/p&gt;

&lt;p&gt;That distinction clicked for me very quickly because I spent the last few years working on Goose, where skills are a core part of how agents learn reusable workflows. Once I started testing Cloudinary integrations through that same lens, the value became obvious almost immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Agents Need Platform Context
&lt;/h2&gt;

&lt;p&gt;The biggest shift happening right now is that AI agents are quickly becoming the interface developers use to interact with platforms.&lt;/p&gt;

&lt;p&gt;That changes the responsibility for developer platforms too.&lt;/p&gt;

&lt;p&gt;It's no longer enough to expose APIs and publish documentation. Platforms now need ways to teach agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;platform specific syntax&lt;/li&gt;
&lt;li&gt;implementation workflows&lt;/li&gt;
&lt;li&gt;SDK patterns&lt;/li&gt;
&lt;li&gt;optimization best practices&lt;/li&gt;
&lt;li&gt;architectural recommendations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's what skills enable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run in your terminal&lt;/span&gt;

npx skills add cloudinary-devs/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full installation options, marketplace plugins, and setup instructions are available in the &lt;a href="https://cloudinary.com/documentation/cloudinary_llm_mcp#skills_installation" rel="noopener noreferrer"&gt;Cloudinary Skills Guide →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're continuing to expand the Cloudinary Skills Pack with new skills and capabilities over time, so stay tuned for more. And if you have feedback or ideas you'd like to see supported, we'd love to hear from you in the &lt;a href="https://github.com/cloudinary-devs/skills/issues" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uo8oM" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Say It With Flowers: BouqAIs with the Tussie-Mussie Generator</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Sat, 09 May 2026 17:19:01 +0000</pubDate>
      <link>https://dev.to/cloudinary/say-it-with-flowers-bouqais-with-the-tussie-mussie-generator-3do0</link>
      <guid>https://dev.to/cloudinary/say-it-with-flowers-bouqais-with-the-tussie-mussie-generator-3do0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://tussie-mussies.netlify.app" rel="noopener noreferrer"&gt;Tussie-Mussie Generator&lt;/a&gt;&lt;/strong&gt; is a web app for anyone who wants to send a meaningful floral e-card with an AI-generated "tussie mussie", a type of small nosegay. It uses the Victorian language of flowers to compose a bouquet, generates an image with Gemini, and sends it via Resend. This post covers &lt;br&gt;
a Mother's Day relaunch: fixing a broken email integration and swapping the AI image model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What changed in this relaunch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Migrated email sending from Mailgun to Resend, including a Cloudflare serverless function&lt;/li&gt;
&lt;li&gt;Fixed DNS configuration across Porkbun, Netlify, and Resend for a custom subdomain&lt;/li&gt;
&lt;li&gt;Replaced the previous image generator with Gemini for accurate tussie mussie generation&lt;/li&gt;
&lt;li&gt;Refactored the UI with Astro + Vue and impeccable.style for AI-assisted critique&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you ever wanted to build an e-card system that would allow you to send little ai-generated bouquets to your friends? No? Yes? Read on, gentle reader!&lt;/p&gt;

&lt;p&gt;Last year, I onboarded to my new job by building this sample app that lets you use the Victorian language of flowers to compose a bouquet. An image-heavy app like this is a great candidate for hosting and optimizing via Cloudinary, so all the flower images are stored there. An image of the composed bouquet is generated by Gemini, and then sent via Resend to the recipient of your choice. The app looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3w0zh47qr8wxul07kfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3w0zh47qr8wxul07kfk.png" alt="Tussie Mussie Generator" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the generated e-card looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hl2hu7bcummpftq8k7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hl2hu7bcummpftq8k7x.png" alt="demo" width="800" height="1245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I launched this app a year ago, and it's had an interesting lifespan. I wanted to relaunch it for Mother's Day and realized there was some refactoring to do! &lt;/p&gt;

&lt;p&gt;Here are a few of the gotchas I dealt with:&lt;/p&gt;

&lt;p&gt;Keeping the core: The basis of this app is its dataset, which I created a while ago on &lt;a href="https://www.kaggle.com/datasets/jenlooper/language-of-flowers" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt; as a useful version of the Victorian "Language of Flowers", scraped from the &lt;a href="https://www.almanac.com/flower-meanings-language-flowers#flower-meanings" rel="noopener noreferrer"&gt;Farmer's Almanac&lt;/a&gt;. Here's an example of what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5l8fjmwlta0yy1trhoa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5l8fjmwlta0yy1trhoa.jpg" alt="Almanac version of florilege" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your idea is centered around a solid dataset, you can't go wrong. Once I had the list, it took me a while to scrape images of the flowers in question and store them, optimized, on Cloudinary. But that work was done a year ago. So two things remained for the relaunch: refactoring the UI and fixing the bits that stopped working, namely the e-card generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring the UI
&lt;/h2&gt;

&lt;p&gt;This app is built using my preferred stack, namely Astro + Vue. After a year, it was definitely time to upgrade, including fixing the tailwind integration which relied on an outdated package. Cursor made short work of that - it's one of the best uses of AI software agents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: periodically ask your AI to plan the removal of any redundant files or code while you complete refactors. There are usually vestiges that can be deleted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used a wildly useful AI skill to refactor my admittedly basic UI: &lt;a href="https://impeccable.style/" rel="noopener noreferrer"&gt;impeccable.style&lt;/a&gt;. Install these skills on your machine and as you work with any AI generated UI suggestions, run &lt;code&gt;/impeccable critique&lt;/code&gt; to reality-check your work for taste and style. I'm now using impeccable on all my UIs - it also helped me tidy &lt;a href="https://jenlooper.com" rel="noopener noreferrer"&gt;my portfolio&lt;/a&gt;. The UI went from this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6luq0346nkg7qkrvbv2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6luq0346nkg7qkrvbv2.webp" alt="old UI" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...to the UI shown above. I like it a lot. &lt;/p&gt;




&lt;h2&gt;
  
  
  Fixing the E-Card functionality
&lt;/h2&gt;

&lt;p&gt;This app caused me a horrible panic after launch last year, ruining an anniversary dinner, due to a kerfuffle with Mailgun, the original e-card sending mechanism. I was shocked by an enormous overage charge due to hacking of their systems, and I shut the entire thing down as fast as I could. To relaunch, I needed to find a new service to send email via API. Once again, Cursor to the rescue. &lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, a new email service that is much more solid than my previous choice. Let us hope that we have smooth sailing from now on! The integration includes a serverless function that you can host on Cloudflare, and works well as soon as you verify a domain. That last part took me the longest time - the gotcha is that since I bought the domain on Porkbun, I had to edit DNS records there to serve emails from a subdomain. The site is hosted on Netlify, so I had to build the subdomain there, and then the DNS verified on Resend. It's the kind of task that you don't do often, so it's easy to miss a step. But all is set now, and the emails are sending!&lt;/p&gt;

&lt;p&gt;I also had to reconsider how to build the AI generated bouquets. I'm out of credits on the service I previously chose to generate the bouquets, and they were never quite right anyway (generated as standard bouquets rather than true tussie mussies which have a silver filigree holder). It's crazy to see how AI has evolved for this esoteric imagery over the past year - now Gemini can accurately compose a tussie mussie in its holder, given a specific prompt.&lt;/p&gt;

&lt;p&gt;Enjoy these little bouquets! &lt;a href="https://tussie-mussies.netlify.app/" rel="noopener noreferrer"&gt;Send one to your Mom today&lt;/a&gt;!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uoTiK" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>astro</category>
      <category>vue</category>
    </item>
    <item>
      <title>How to Win a Hackathon</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Sun, 03 May 2026 03:43:08 +0000</pubDate>
      <link>https://dev.to/cloudinary/how-to-win-a-hackathon-1377</link>
      <guid>https://dev.to/cloudinary/how-to-win-a-hackathon-1377</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A sponsor’s perspective&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blogpost is written on the heels of a few amazing weekends spent knee-deep in students in Toronto and Los Angeles at Hack Canada and LA Hacks, engaging almost 2000 students in person in total. Managing a sponsor track, I have some thoughts on good ways to present yourselves when angling your project towards a sponsor prize.&lt;/p&gt;

&lt;p&gt;For those not as familiar with how student hackathons work, they are usually comprised of an organized weekend populated by college undergrads (or older, or younger! high schoolers are increasingly getting into the mix). Over about 36 hours, students form teams and try to build software that meets a need, solves a problem, or proves a point. Some hackathons focus on themes around social good, like Hack Canada which challenged students to build something of special interest to Canada. Others have several themes and a full roster of sponsored options. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hackathons are a bastion of the cozy web! It’s a delight to see students embracing the freedom to create weird, useless, and wildly creative apps like AeroMaxx, a way to gauge influencers’ aerodynamic-maxxing (Taylor Swift is more aerodynamic than Clavicular, did you know?). LA Hacks even had a track embracing the “most questionable” hack, won by “Yes? Or Yes!”, a hack that had literal goldfish helping make decisions and trigger AI agents to do things like break up with a girl friend, quit a job, and post to social media. Great projects. Please keep building this kind of weird stuff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn9ghxj1wt5pctpuhve0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn9ghxj1wt5pctpuhve0.png" alt="MagByte" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a cozy interface for roommates to manage their shopping lists: &lt;a href="https://devpost.com/software/magbite?_gl=1*1nkfo64*_gcl_au*MjA0ODI2ODI1OC4xNzcyOTAyNTc4*_ga*Mzk1NTQ4NjAwLjE3NzI5MDI1Nzk.*_ga_0YHJK3Y10M*czE3Nzc3Nzc1MDMkbzM4JGcxJHQxNzc3Nzc3NjM5JGo0OCRsMCRoMA.." rel="noopener noreferrer"&gt;MagByte&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s a great moment to let your creativity run wild:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi55ce5tfipfpjajllj59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi55ce5tfipfpjajllj59.png" alt="goldfish q&amp;amp;a" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://devpost.com/software/yes-or-yes?_gl=1*nv0asj*_gcl_au*MjA0ODI2ODI1OC4xNzcyOTAyNTc4*_ga*Mzk1NTQ4NjAwLjE3NzI5MDI1Nzk.*_ga_0YHJK3Y10M*czE3Nzc3Nzc1MDMkbzM4JGcxJHQxNzc3Nzc4MTA2JGo1NiRsMCRoMA.." rel="noopener noreferrer"&gt;Yes? Or Yes!&lt;/a&gt; LA Hacks most unhinged hack winner - goldfish help you make decisions and then trigger agentic workflows. Good luck!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been working the hackathon circuit as a developer advocate for at least ten years, including the mess during COVID, and the major difference nowadays between the earlier events is, unsurprisingly, the heavy use of AI tooling that both speeds up delivery, polishes the final product, and gets more software shipped, way faster. Instead of watching demo videos, judges can actively interact with an almost production-ready app, shockingly quickly.&lt;/p&gt;

&lt;p&gt;Still, there are some tips and tricks that I’d like to share based on a few recent observations of these very fun events. Here are five things to keep in mind as you go for a win!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who’s it for?
&lt;/h2&gt;

&lt;p&gt;A lot of folks are attracted to social-good type projects, and that’s very laudable. I love them, personally, and tend to mark them highly in my judging rubric. I just want to caution hackathon participants against going after this vector with a “tech savior complex”. It’s easy to think that tech can solve all the things. And tech has made great strides on devices such as most smart phones to help people with various accessibility issues such as those who are blind, color blind, experience hearing loss, or are mobility-challenged. &lt;/p&gt;

&lt;p&gt;But if you are going to create a project for a given sector of users, it’s critical to come at these projects with an open mindset and humility, and to work with people who have these actual challenges. Working on an app for older people? Call your Grandma or Grandpa before you start building. It’s likely they can guide you to refining your app to make it much more useful.&lt;/p&gt;

&lt;p&gt;At Hack Canada, teams succeeded who spent time talking to local security guards as they built, to ensure that their safety-oriented hack would be useful. It’s all about finding your customer and/or user, and building backwards from that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Power to the People
&lt;/h2&gt;

&lt;p&gt;Sponsors and mentors are often available to help you all the way through the event, so make use of them! Take some time before you start working to talk to sponsors and see if they can help you start to ideate. Learn about the various sponsors and what they are looking for. Some are looking for product feedback, some are hiring, others may have another agenda. &lt;/p&gt;

&lt;p&gt;Maybe you’ll find a product that you never heard of and will make a nice professional connection. Get your LinkedIn app ready to scan codes! This is a great moment to network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82424uizvh1gzcbpfr3z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82424uizvh1gzcbpfr3z.jpg" alt="Jen and Raya" width="582" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At your service at the booth!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Skill Up
&lt;/h2&gt;

&lt;p&gt;The use of AI tools has accelerated the build phase of a hackathon almost beyond recognition. When I was judging hackathons around 2016 or so, the projects produced were super shaky - often the hacker created a video at the moment that the app worked, and showed that to a judge as it was highly unlikely they could ever get it working again! This has all changed when AI has become such an important build partner.&lt;/p&gt;

&lt;p&gt;The use of AI, however, shifts the focus of a hackathon away from the software engineering tasks that now can be offloaded to agents (with supervision, of course!). Now, you need to spend a good amount of time getting set up to make your AI as successful and efficient as you can. Many vendors have created tools to help; we at Cloudinary produced a Skills Pack for this purpose: &lt;a href="https://github.com/cloudinary-devs/skills" rel="noopener noreferrer"&gt;https://github.com/cloudinary-devs/skills&lt;/a&gt;. Using tools such as these will effectively give official context to your app, preventing hallucinations and other wasting of time. &lt;/p&gt;

&lt;h2&gt;
  
  
  Sponsormaxxing and platformmaxxing
&lt;/h2&gt;

&lt;p&gt;There’s a trend towards “sponsormaxxing” - LA Hacks even had a track for this. The term refers to the incorporation of as many sponsors’ platforms as possible in a given hack. I think it’s a fun challenge, but there’s a risk: if you want to win a sponsor track, you’ll likely be judged on deep integration with their platform. &lt;/p&gt;

&lt;p&gt;In our case at Cloudinary, I’m looking for not just the use of the platform as a place to dump images and video, but a deep use of the APIs, for example optimizing and transforming images in an AI pipeline with the goal of constructing videos (as you can do with our winning hack from LA Hacks, StudyO!). Going for a sponsor track prize? Focus on deep integrations.&lt;/p&gt;

&lt;p&gt;Going deep, however, can also be risky! There’s the risk of “platformmaxxing” - using every bell and whistle of a platform to try it out. You may need to be a bit strategic here, as you might max out your credits by piling on as many platform aspects as you can. Watch out for signs of maxing out your credits and plan accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folgg9609kdf96demfwx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folgg9609kdf96demfwx8.png" alt="AeroMaxx" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Speaking of maxxing: &lt;a href="https://devpost.com/software/aeromaxx?_gl=1*vb7ib8*_gcl_au*MjA0ODI2ODI1OC4xNzcyOTAyNTc4*_ga*Mzk1NTQ4NjAwLjE3NzI5MDI1Nzk.*_ga_0YHJK3Y10M*czE3Nzc3Nzc1MDMkbzM4JGcxJHQxNzc3Nzc3OTE0JGoxNiRsMCRoMA.." rel="noopener noreferrer"&gt;AeroMaxx&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Perfect the Pitch
&lt;/h2&gt;

&lt;p&gt;Judges have only a few minutes per team so you need to make your best impression right away. Come ready to demo with your storytelling skills well in hand. Do some trial runs prior, and get feedback on your presentation skills. Lead with the use case, show the demo, and field questions. A good strategy for a 2-3 minute hackathon pitch is to focus on the problem you see, the solution you propose, and the impact you predict. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hook (15 seconds): Introduce yourself, state the problem you are solving, and share a relatable scenario or story.&lt;/li&gt;
&lt;li&gt;Demo (60-90 seconds): Walk the audience through your core feature&lt;/li&gt;
&lt;li&gt;Tech (30 seconds): Briefly explain the most impressive technical hurdle you overcame or the tech stack that powers the hack.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: Consider recording a video of your demo just in case there’s a horrible glitch, and practice getting over a glitchy demo. You can do it!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfusg6w07felvou9vje9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfusg6w07felvou9vje9.jpg" alt="pitch me" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pitching can be intense!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Winning a hackathon is a great goal, but don’t forget that being accepted into some of these selective hackathons, participating, and building a piece of software that you can be proud of is very laudable as well. Wishing you the best of luck! And maybe we will see you there.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/uoBac" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>career</category>
      <category>community</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>6 Free Cloudinary Courses That Teach You to Build Media-Optimized Apps</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Thu, 30 Apr 2026 18:33:51 +0000</pubDate>
      <link>https://dev.to/cloudinary/6-free-cloudinary-courses-that-teach-you-to-build-media-optimized-apps-51m2</link>
      <guid>https://dev.to/cloudinary/6-free-cloudinary-courses-that-teach-you-to-build-media-optimized-apps-51m2</guid>
      <description>&lt;p&gt;The Cloud to Crowd (C2C) curriculum is Cloudinary's free 6-course learning suite for developers who want to build and ship applications with production-quality image and video handling. It covers everything from Cloudinary basics to AI-powered transformations paired with hands-on projects at every step and a free certificate when you complete each course.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fun fact: community member Jerome Hardaway of Vets Who Code, one of our nonprofit partners in the Creators Community program, coined the "Cloud to Crowd" term!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Be Able to Build After This Curriculum
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upload, manage, and transform media assets using the Cloudinary API&lt;/li&gt;
&lt;li&gt;Optimize images and video for performance in a Next.js app&lt;/li&gt;
&lt;li&gt;Apply AI-powered features: background removal, generative fill, auto-tagging&lt;/li&gt;
&lt;li&gt;Use the Python SDK to automate media workflows on the backend&lt;/li&gt;
&lt;li&gt;Deliver video efficiently with format and quality auto-selection&lt;/li&gt;
&lt;li&gt;Ship a real portfolio project that demonstrates all of the above&lt;/li&gt;
&lt;li&gt;Earn a Cloudinary certificate and qualify for the Creators Community&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevy6pbfnmkb347ezgdgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevy6pbfnmkb347ezgdgs.png" alt="Cloudinary Academy Courseware" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Built This
&lt;/h2&gt;

&lt;p&gt;Getting images and video right in a production app is harder than most tutorials suggest. Resize, format, optimize, deliver — each step has tradeoffs, and most learning resources skip the messy middle. The C2C courses are designed to fill that gap: structured, project-driven, and free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6 Free Courses
&lt;/h2&gt;

&lt;p&gt;All courses are self-paced and free at &lt;a href="https://training.cloudinary.com/pages/c2c" rel="noopener noreferrer"&gt;https://training.cloudinary.com/pages/c2c&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Course 1: Media IQ for Developers
&lt;/h3&gt;

&lt;p&gt;Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-intro" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-intro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The starting point for the first three courses in this suite. This course covers the core Cloudinary concepts: cloud name, API keys, the Media Library, and your first upload and transformation. No prior Cloudinary experience needed. By the end, you'll have a free account set up and understand how assets move through the Cloudinary pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose the right image formats for your use case, understanding when to use JPEG, PNG, WebP, AVIF, or SVG based on content type, browser support, and performance trade-offs&lt;/li&gt;
&lt;li&gt;Apply strategic optimization techniques including responsive images with automatic format/quality selection and intelligent cropping with gravity detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry0mgv63008yznpxadxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry0mgv63008yznpxadxa.png" alt="Heavy Watch image" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Course 2: Media IQ for Developers with Next.js
&lt;/h3&gt;

&lt;p&gt;Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-next" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-next&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flagship course of the C2C curriculum. You'll build a storefront application (the "CapZone" e-commerce store) using Next.js and the Cloudinary Next.js SDK, applying real-world image optimization patterns throughout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build an image-optimized web application using Cloudinary to upload, store, transform, and deliver images on-the-fly through URL-based transformations and CDN delivery&lt;/li&gt;
&lt;li&gt;Apply strategic optimization techniques including responsive images with automatic format/quality selection and intelligent cropping with gravity detection&lt;/li&gt;
&lt;li&gt;Create Next.js components that handle image uploading, display optimized images with overlays and watermarks, and serve different sizes for different contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhpe6j2tmei2fq4ao40v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhpe6j2tmei2fq4ao40v.png" alt="CapZone store" width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The kind of component you'll be writing by the end of this course:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CldImage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-cloudinary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CldImage&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fill"&lt;/span&gt;
      &lt;span class="na"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;
      &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;
      &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Course 3: Media IQ for Developers with AI
&lt;/h3&gt;

&lt;p&gt;Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-ai" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloudinary's AI-powered transformation features are some of its most practical and useful. This course covers the tools that save the most time in real projects: background removal, generative fill, object-aware cropping, and auto-tagging. This course follows the Next.js course, building AI into the CapZone store by adding a color picker to colorize the hats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove or replace backgrounds using e_background_removal&lt;/li&gt;
&lt;li&gt;Use generative fill to extend or recompose images&lt;/li&gt;
&lt;li&gt;Apply smart cropping with gravity: "auto" for face- and object-aware results&lt;/li&gt;
&lt;li&gt;Auto-tag assets using AI analysis to make large libraries searchable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4ryvwuokux6f54ai35k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4ryvwuokux6f54ai35k.png" alt="AI CapZone" width="800" height="811"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Course 4: Media IQ for Developers with Video
&lt;/h3&gt;

&lt;p&gt;Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-video" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Video is a different beast from images; you manage larger files, adaptive bitrates, more delivery complexity. This course covers the Cloudinary video pipeline from upload to playback. It's the third and final part of the progressive enhancement of the CapZone e-commerce web app that you started prior. In this course you'll add a video to one of the products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload and transcode video using the Cloudinary API&lt;/li&gt;
&lt;li&gt;Apply video transformations: trim, resize, overlay, add subtitles&lt;/li&gt;
&lt;li&gt;Build playlists and seekbars with subtitles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xo1znkfbl48ywp9zu6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xo1znkfbl48ywp9zu6d.png" alt="Video with playlist on CapZone" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Course 5: Media IQ for Developers with Python
&lt;/h2&gt;

&lt;p&gt;For less JavaScript-focused developers, this course covers Cloudinary's Python SDK: uploading, managing, and transforming assets from any Python environment.&lt;br&gt;
Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-python" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate and configure the Python SDK&lt;/li&gt;
&lt;li&gt;Upload and tag assets for organization&lt;/li&gt;
&lt;li&gt;Build a small pet store app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1f7brwa80h9bjk2p7yy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1f7brwa80h9bjk2p7yy.png" alt="pet store app" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Course 6: Building a Developer Portfolio with Cloudinary and Next.js
&lt;/h2&gt;

&lt;p&gt;Enroll: &lt;a href="https://training.cloudinary.com/learn/course/devrel-c2c-portfolio" rel="noopener noreferrer"&gt;https://training.cloudinary.com/learn/course/devrel-c2c-portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The capstone course. You'll apply everything from the earlier modules: upload, transformation, optimization, and AI features — to build a complete portfolio site using React and TypeScript that demonstrates your Cloudinary skills to employers. Focus on your own skills and show them off to the world in a media-rich website.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architect a media-rich portfolio app from scratch&lt;/li&gt;
&lt;li&gt;Combine multiple Cloudinary transformations into a polished UI&lt;/li&gt;
&lt;li&gt;Optimize for performance: lazy loading, responsive breakpoints, format auto-selection&lt;/li&gt;
&lt;li&gt;Deploy a production-ready app you can link from your resume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4891ntoh6m30xoqdmsvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4891ntoh6m30xoqdmsvh.png" alt="portfolio" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who's this for?
&lt;/h2&gt;

&lt;p&gt;The C2C curriculum works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early-career developers who want structured, project-based learning beyond "hello world" tutorials&lt;/li&gt;
&lt;li&gt;Career changers building a portfolio they can point to during job interviews&lt;/li&gt;
&lt;li&gt;Frontend developers who've used Cloudinary but want to understand the optimization and AI layers&lt;/li&gt;
&lt;li&gt;Backend developers looking to add media handling to their Python projects&lt;/li&gt;
&lt;li&gt;Bootcamp graduates who want to go from tutorial-follower to someone who can build production media pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Enroll for Free
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to training.cloudinary.com/pages/c2c&lt;/li&gt;
&lt;li&gt;Create a free Cloudinary Academy account — you just need an email address&lt;/li&gt;
&lt;li&gt;Start with Course 1 or jump to the course that matches your current skill level&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All 6 courses are free. Each comes with a completion certificate. Finishing any course and passing its final assessment at 80%+ earns you a credential that qualifies you to apply to the Cloudinary Creators Community, a network with mentorship, Discord access, mini-hack events, and connections to developers worldwide.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Cloudinary Creators Community
&lt;/h2&gt;

&lt;p&gt;The Cloudinary Creators Community (CCC) is a selective, cohort-based program for developers who've completed the C2C curriculum. &lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to private Discord channels with Cloudinary's Developer Relations team&lt;/li&gt;
&lt;li&gt;Monthly mini-hack challenges to apply and extend your skills&lt;/li&gt;
&lt;li&gt;Peer review and mentorship from a global network of developers&lt;/li&gt;
&lt;li&gt;Badges, certificates, and holopins with social media shout-outs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can join the Discord server now and participate in community activities even while you're working through the curriculum.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ready to start? → &lt;a href="//training.cloudinary.com/pages/c2c"&gt;training.cloudinary.com/pages/c2c&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blogpost was crafted with the help of Claude. Banner image by Nano Banana.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/un3am" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ship Your Next Great Web App in Record Time with create-cloudinary-react</title>
      <dc:creator>Eric Portis</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:53:07 +0000</pubDate>
      <link>https://dev.to/cloudinary/ship-your-next-great-web-app-in-record-time-with-create-cloudinary-react-2coa</link>
      <guid>https://dev.to/cloudinary/ship-your-next-great-web-app-in-record-time-with-create-cloudinary-react-2coa</guid>
      <description>&lt;p&gt;Over the past year or two, we've been having a lot of conversations about the future of Cloudinary's &lt;a href="https://www.cloudinary.dev" rel="noopener noreferrer"&gt;Libraries&lt;/a&gt; and &lt;a href="https://cloudinary.com/documentation/cloudinary_sdks" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt;. The conversations have been wide-ranging: the future of front-end frameworks, coding, and AI. It's all been a bit dizzying, and hard to wrap our heads around.&lt;/p&gt;

&lt;p&gt;Late last year, my colleague Raya Straus brought us back down to earth with some good old-fashioned user research. By engaging users in conversations about their current day-to-day experiences, Raya got us to stop worrying about the future, and start thinking about ways we could make developers more successful with Cloudinary &lt;em&gt;right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We released the first fruit of Raya's research last month: &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react" rel="noopener noreferrer"&gt;Cloudinary's React Starter Kit&lt;/a&gt;. If you're building a green-field, media-focused React app, and you're using LLM-powered development tools, we think &lt;code&gt;npx create-cloudinary-react&lt;/code&gt; is the best way to get started.&lt;/p&gt;

&lt;p&gt;Before we get into what it is, let's talk about how and why we built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Learned From React SDK User Research
&lt;/h2&gt;

&lt;p&gt;We limited our research scope to two of our most popular SDKs: &lt;a href="https://cloudinary.com/documentation/react_integration" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://next.cloudinary.dev" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;. We looked at every piece of user feedback for those two SDKs that we could find: support tickets, GitHub issues, chat transcripts, survey responses, and even a number of individual conversations held over email and Zoom, after we reached out to top SDK users.&lt;/p&gt;

&lt;p&gt;Once we'd collected and digested it all, three things stuck out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Many developers struggle to &lt;em&gt;get started&lt;/em&gt;. The least fun part of any development project is setting up a working development environment; a handful of common config problems are tripping up many developers before they can take their first steps with Cloudinary.&lt;/li&gt;
&lt;li&gt;Once set up, features that solve common use cases were straightforward and people were quickly successful. Cloudinary and our SDKs do a great job of solving common use cases with a minimum of fuss. Great!&lt;/li&gt;
&lt;li&gt;But once use cases get more advanced, developers start to struggle again. Cloudinary is a mature product, and &lt;em&gt;can&lt;/em&gt; do &lt;em&gt;all sorts of things&lt;/em&gt;, but advanced features are more complicated to use. Niche functionality often has fiddlier syntax, less clear error messages, and trickier-to-find documentation. So even though the features are there, developers run into walls when trying to use them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyr2nrznnt15tjvhphsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyr2nrznnt15tjvhphsy.png" alt="A line chart. The x axis is labelled, " width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the React Starter Kit Works
&lt;/h2&gt;

&lt;p&gt;So, the question we ended up with, was: How can we help developers who are just getting started with Cloudinary, &lt;em&gt;and&lt;/em&gt; folks with complex use cases?&lt;/p&gt;

&lt;p&gt;We think we can address both with one new tool: &lt;code&gt;npx create-cloudinary-react&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Type that into a terminal near you and you'll be led through a wizard, which will ask you a few questions about your environment. Then, &lt;em&gt;it&lt;/em&gt; will spin up a working React project for you, using Vite as a build tool, and handle all of the configuration that people who are just starting to use Cloudinary with React so often get stuck on. The resulting project starts with a simple, one-page website that accepts and displays user-generated image uploads.&lt;/p&gt;

&lt;p&gt;In addition to doing basic configuration, the wizard &lt;em&gt;also&lt;/em&gt; installs &lt;a href="https://cloudinary.com/documentation/cloudinary_llm_mcp" rel="noopener noreferrer"&gt;Cloudinary's LLM-specific tools&lt;/a&gt;, and puts &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react/blob/main/templates/.cursorrules.template" rel="noopener noreferrer"&gt;a bunch of rules and troubleshooting guidance to help LLMs with Cloudinary's APIs&lt;/a&gt; wherever your particular LLM-powered coding environment looks for context.&lt;/p&gt;

&lt;p&gt;We derived these rules from our user research, by feeding all of the feedback we received about using Cloudinary's React SDK into an LLM and asking it to write rules to address that feedback. From there, we refined, added, and subtracted rules based on our experiences of using and helping other folks use Cloudinary.&lt;/p&gt;

&lt;p&gt;Those rules, in addition to our other LLM-specific tools, significantly improve LLMs' results when prompted with complex tasks.  To show off those capabilities, the initial built project offers a handful of example prompts which you can copy, paste into your agent, and hit the ground running.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;create-cloudinary-react&lt;/code&gt;, we believe we've built something that helps &lt;em&gt;everyone&lt;/em&gt; get started, and &lt;em&gt;also&lt;/em&gt; helps folks who have gone beyond common use cases use all of the nooks and crannies of Cloudinary's extensive feature set. &lt;/p&gt;

&lt;h2&gt;
  
  
  See the React Starter Kit in Action
&lt;/h2&gt;

&lt;p&gt;Here's &lt;code&gt;create-cloudinary-react&lt;/code&gt; in action:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/gmzYabZFUHo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How the React Starter Kit performed at Hack Canada 2026
&lt;/h2&gt;

&lt;p&gt;After a few rounds of internal development and testing, we soft-launched the React Starter Kit a few weeks ago -- just before we participated as a sponsor in this year's &lt;a href="https://hackcanada.org/" rel="noopener noreferrer"&gt;Hack Canada&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A hackathon is a perfect environment for this kind of tool, and the results blew us away. Participants leveraged the Starter Kit to produce some &lt;a href="https://hack-canada-2026.devpost.com/submissions/search?utf8=%E2%9C%93&amp;amp;prize_filter%5Bprizes%5D%5B%5D=97781" rel="noopener noreferrer"&gt;truly incredible projects&lt;/a&gt;, and cited &lt;code&gt;create-cloudinary-react&lt;/code&gt; as part of their success.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ERRFRz6LQZs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pJzrU2F_3r8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/A5McD0WwqzM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That was all the validation we needed to add a &lt;a href="https://cloudinary.com/documentation/react_starter_kit" rel="noopener noreferrer"&gt;React Starter Kit walk through to our documentation&lt;/a&gt; and announce it in this very blog post.&lt;/p&gt;

&lt;p&gt;So, go! &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react" rel="noopener noreferrer"&gt;Try it out!&lt;/a&gt; And tell us what you think in the comments.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/un3aq" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>react</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Upload and Deliver Media in Flask with Cloudinary: Presets, Widget, and Search</title>
      <dc:creator>Sharon Yelenik</dc:creator>
      <pubDate>Mon, 23 Mar 2026 19:07:00 +0000</pubDate>
      <link>https://dev.to/cloudinary/managing-media-files-in-flask-images-videos-and-audio-kdk</link>
      <guid>https://dev.to/cloudinary/managing-media-files-in-flask-images-videos-and-audio-kdk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Excerpt:&lt;/strong&gt; Cloudinary is a media API for Python/Flask developers that uploads, transforms, and searches images and video without bloating your app or database. It cuts custom upload code and speeds delivery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post shows how to handle user uploads and delivery in a Flask app using Cloudinary’s Python SDK, upload presets, the Upload Widget, and search—without storing binaries in your database. The first sections cover setup; then we add client-side and server-side uploads.&lt;/p&gt;

&lt;p&gt;If you need a moderate amount of UGC (portfolios, catalogs), you want uploads off your server, consistent transforms, and searchable metadata.&lt;/p&gt;

&lt;p&gt;You’re building an app for a small business or website, maybe a portfolio site or boutique store, and you know high-quality visuals are key for attracting buyers and showcasing your work. But without a designer on the team, you’re responsible for handling all the images and videos yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you’ll do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install and configure Cloudinary in Flask&lt;/li&gt;
&lt;li&gt;Create an upload preset (transforms + auto-tagging)&lt;/li&gt;
&lt;li&gt;Add the Upload Widget and a server-side upload route&lt;/li&gt;
&lt;li&gt;Query assets by tag with the Search API&lt;/li&gt;
&lt;li&gt;Deliver with transformations in templates (e.g. &lt;code&gt;cloudinary_url&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits of Streamlined Media Management
&lt;/h2&gt;

&lt;p&gt;Before we get into the practical steps, here are some of the advantages of establishing a solid media management approach in your Flask application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less custom code and reduced storage complexity.&lt;/strong&gt; Offloading large media files from your database or filesystem keeps your application simpler and avoids managing bulky binary data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable uploads that scale.&lt;/strong&gt; A predictable upload flow prevents issues with file size limits, timeouts, or custom route handling, especially when users submit large images or videos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent, high-quality digital media.&lt;/strong&gt; Automated tasks like resizing, compression, tagging, and background removal help keep your images and videos optimized without manual editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized, searchable delivery.&lt;/strong&gt; Well-structured metadata and scalable search make assets easier to find, while delivering the right size and format improves performance across devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By &lt;a href="https://link.cloudinary.com/umWsT" rel="noopener noreferrer"&gt;storing your digital media in Cloudinary&lt;/a&gt; instead of on your server or in your database, you can take advantage of these benefits immediately, while keeping your Flask codebase lightweight and focused on application logic.&lt;/p&gt;

&lt;p&gt;Managing media files in Flask becomes much simpler with the steps we cover next (tap the icons to navigate to the related section):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1698223367%2Fblog%2Fdjango_media_management.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1698223367%2Fblog%2Fdjango_media_management.png" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup and Configuration
&lt;/h2&gt;

&lt;p&gt;Before we dive into the specifics of media management with Python and Flask, you'll need to sign up for Cloudinary. It's quick and straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing and Configuring Cloudinary in Your Python/Flask App
&lt;/h3&gt;

&lt;p&gt;To get started with Cloudinary in your Python/Flask app, follow these steps:&lt;/p&gt;

&lt;p&gt;In your project virtualenv:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the Cloudinary Python library using &lt;code&gt;pip install cloudinary&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Cloudinary in your Flask application, typically inside app.py or a separate &lt;code&gt;config.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.py or app.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.uploader&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.api&lt;/span&gt;

&lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cloud_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_cloud_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Replace &lt;code&gt;your_cloud_name&lt;/code&gt;, &lt;code&gt;your_api_key&lt;/code&gt;, and &lt;code&gt;your_api_secret&lt;/code&gt; with your actual Cloudinary credentials, which you can find on the &lt;a href="https://console.cloudinary.com/app/settings/api-keys" rel="noopener noreferrer"&gt;API keys&lt;/a&gt; page of the Console Settings.&lt;/p&gt;

&lt;ol start="3"&gt;&lt;li&gt;If you're using a &lt;code&gt;config.py&lt;/code&gt;, you'd load it in your Flask app like this:&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;  &lt;span class="c1"&gt;# or your preferred config structure
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these steps, you've successfully integrated Cloudinary into your Flask project, and you're ready to use its media management capabilities (upload, transform, search, deliver).&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced Media Processing With Upload Presets
&lt;/h2&gt;

&lt;p&gt;Upload presets in Cloudinary help you automate your media workflow by applying transformations, metadata rules, and upload behaviors the moment a file arrives. This means less processing in your Flask routes and far more consistent results across all your media.&lt;/p&gt;

&lt;p&gt;In this article, we’ll focus on two powerful capabilities you can unlock through presets: &lt;strong&gt;automated transformations&lt;/strong&gt; and &lt;strong&gt;auto-tagging&lt;/strong&gt;. You can explore many other preset options in Cloudinary’s &lt;a href="https://link.cloudinary.com/umWsU" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, but these two alone can dramatically simplify your media pipeline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Transformation Settings
&lt;/h2&gt;

&lt;p&gt;Upload presets allow you to automatically apply transformations such as resizing, cropping, recoloring, background removal, and more, without writing additional code in Flask. For example, you can create presets that remove backgrounds from uploaded logos, ensure all portfolio images are a consistent size, or sharpen older or lower-quality photos. &lt;/p&gt;

&lt;p&gt;Each preset applies its rules automatically when the file is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; By applying the &lt;a href="https://link.cloudinary.com/umWsW" rel="noopener noreferrer"&gt;g_auto&lt;/a&gt; AI-powered transformation, you can ensure that crops preserve the important parts of your images and keep the main subject of your videos in focus.&lt;/p&gt;

&lt;p&gt;By defining these settings once, every uploaded media file stays consistent and optimized with no manual editing or external image processing tools required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Fw_500%2Ch_303%2Cc_scale%2Ff_auto%2Cq_auto%2Fv1698140107%2FWeb_Assets%2Fblog%2Frecolor%2Frecolor.gif%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Fw_500%2Ch_303%2Cc_scale%2Ff_auto%2Cq_auto%2Fv1698140107%2FWeb_Assets%2Fblog%2Frecolor%2Frecolor.gif%3F_i%3DAA" width="500" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging Auto-Tagging
&lt;/h3&gt;

&lt;p&gt;If you enable auto-tagging through Cloudinary’s categorization engines, images are automatically analyzed and tagged based on their content. These tags can support a variety of downstream tasks, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;organizing assets&lt;/li&gt;
&lt;li&gt;improving searchability&lt;/li&gt;
&lt;li&gt;enabling content discovery&lt;/li&gt;
&lt;li&gt;powering filters in your UI or internal tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an image of a woman on a city street using a smartphone and carrying a bag might receive tags such as “woman,” “bag,” “mobile phone,” “purse,” or “car,” depending on the AI model you use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; To use auto-tagging, you’ll need to subscribe to one of Cloudinary’s &lt;a href="https://cloudinary.com/documentation/cloudinary_add_ons#auto_tagging" rel="noopener noreferrer"&gt;auto-tagging add-ons&lt;/a&gt;. The examples in this blog use the &lt;a href="https://link.cloudinary.com/umWsX" rel="noopener noreferrer"&gt;Rekognition Auto Tagging&lt;/a&gt; add-on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764259730%2FWeb_Assets%2Fblog%2Fwoman-business-suit%2Fwoman-business-suit.jpg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764259730%2FWeb_Assets%2Fblog%2Fwoman-business-suit%2Fwoman-business-suit.jpg%3F_i%3DAA" width="760" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Red-haired woman in stylish outfit chatting on phone&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Check out the &lt;a href="https://link.cloudinary.com/umWsY" rel="noopener noreferrer"&gt;Computer Vision Image Analysis for Your E-commerce Website&lt;/a&gt; demo to see Cloudinary in action, returning information about the content it identifies in your images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Upload Preset
&lt;/h2&gt;

&lt;p&gt;You can create an upload preset using the Cloudinary Admin API right from your Flask application or a setup script. Here’s an example using the Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Define the upload preset details
&lt;/span&gt;&lt;span class="n"&gt;upload_preset_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;upload_preset_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsigned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_folder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transformation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;effect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vignette&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;categorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_rek_tagging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto_tagging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create the upload preset using the SDK
&lt;/span&gt;&lt;span class="n"&gt;upload_preset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_upload_preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_preset_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_preset_options&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Check if the upload preset was created successfully
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;upload_preset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;upload_preset_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Upload preset created successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to create upload preset.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, simply reference the preset name during upload, and all rules will apply automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Media With Python and Flask
&lt;/h2&gt;

&lt;p&gt;Uploading files is central to your Flask workflow, and Cloudinary provides two flexible methods: the Upload Widget for client-side uploads and server-side uploads from your Flask routes.&lt;/p&gt;

&lt;p&gt;Let’s explore both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Widget
&lt;/h3&gt;

&lt;p&gt;If your app accepts user-generated content (UGC), such as portfolio images from creators or product photos submitted by small business owners, the Cloudinary Upload Widget provides a smooth, reliable upload experience directly from the browser. You can attach any upload presets you’ve created so transformations, optimization, tagging, and other processing happen automatically on upload, without additional Flask code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can also create a preset that automatically &lt;a href="https://cloudinary.com/documentation/user_generated_content#moderate" rel="noopener noreferrer"&gt;moderates&lt;/a&gt; user-uploaded images and videos to help ensure that the content appearing on your site is appropriate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764260271%2FWeb_Assets%2Fblog%2Fupload_widget_accessible-1_3948189bcb%2Fupload_widget_accessible-1_3948189bcb.png%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764260271%2FWeb_Assets%2Fblog%2Fupload_widget_accessible-1_3948189bcb%2Fupload_widget_accessible-1_3948189bcb.png%3F_i%3DAA" width="1580" height="1220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: portfolio or catalog uploads
&lt;/h3&gt;

&lt;p&gt;A portfolio gallery or a simple shop catalog where users can upload photos or videos is an ideal scenario for the Upload Widget. It handles the entire client-side upload flow, while your presets manage all media processing in the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_scale%2Cw_500%2Fl_moon_layer%2Fc_scale%2Cw_150%2Ffl_layer_apply%2Cg_north_east%2Fl_text%3Aroboto_20_bold%3AMoonlight%2Ffl_layer_apply%2Cg_north_east%2Cx_30%2Cy_65%2Fcity_night_time.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_scale%2Cw_500%2Fl_moon_layer%2Fc_scale%2Cw_150%2Ffl_layer_apply%2Cg_north_east%2Fl_text%3Aroboto_20_bold%3AMoonlight%2Ffl_layer_apply%2Cg_north_east%2Cx_30%2Cy_65%2Fcity_night_time.jpg" width="500" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdemo-res.cloudinary.com%2Fw_500%2Fl_docs%3Awedding.jpg%2Cc_pad%2Cw_250%2Ch_250%2Fl_radialize%2Ffl_layer_apply%2Ce_displace%2Cy_-8%2Ffl_layer_apply%2Cx_10%2Cb_transparent%2Fleft_mug" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdemo-res.cloudinary.com%2Fw_500%2Fl_docs%3Awedding.jpg%2Cc_pad%2Cw_250%2Ch_250%2Fl_radialize%2Ffl_layer_apply%2Ce_displace%2Cy_-8%2Ffl_layer_apply%2Cx_10%2Cb_transparent%2Fleft_mug" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fv1489074100%2Fgirl_camera.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fv1489074100%2Fgirl_camera.jpg" width="800" height="1195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Implement the Upload Widget in Flask
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a route that renders the template:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload-media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_media&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_media.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="2"&gt;&lt;li&gt; Create the template (templates/upload_media.html):
&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Media&lt;/span&gt; &lt;span class="n"&gt;Upload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_widget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloudinary-button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Upload&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://upload-widget.cloudinary.com/global/all.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/javascript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/javascript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;myWidget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUploadWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="n"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_cloud_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;uploadPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_preset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secure_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done! Here is the file info: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;
                &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/save-media&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_widget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;click&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;myWidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="3"&gt;&lt;li&gt; Add the URL rule (if not using decorators):
&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_url_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload-media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="4"&gt;&lt;li&gt; Test it by visiting:
&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;http://localhost:5000/upload-media&lt;/code&gt; (with your dev server running)&lt;/p&gt;

&lt;p&gt;You now have a complete client-side upload flow, with Cloudinary handling media processing automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-Side Upload
&lt;/h2&gt;

&lt;p&gt;Server-side uploads are perfect when you need automation or want to process files uploaded through a traditional &lt;/p&gt;.
&lt;h3&gt;
  
  
  Example Use Case
&lt;/h3&gt;

&lt;p&gt;News aggregators, podcast platforms, or content curation apps often automate large-scale ingestion:&lt;/p&gt;
&lt;h1&gt;
  
  
  analyze images
&lt;/h1&gt;
&lt;h1&gt;
  
  
  apply tags
&lt;/h1&gt;
&lt;h1&gt;
  
  
  create captions
&lt;/h1&gt;
&lt;h1&gt;
  
  
  run moderation
&lt;/h1&gt;
&lt;h1&gt;
  
  
  convert formats
&lt;/h1&gt;

&lt;p&gt;Cloudinary can handle these tasks server-side with very little code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uploading From Flask
&lt;/h3&gt;


&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.uploader&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;file_to_upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;file_to_upload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;upload_preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_upload_preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secure_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And that’s it. Your media is uploaded, transformed, stored, and ready to deliver.&lt;/p&gt;
&lt;h2&gt;
  
  
  Searching Made Simple
&lt;/h2&gt;

&lt;p&gt;Once your assets are stored in Cloudinary, especially with auto-tagging enabled, searching becomes incredibly fast.&lt;/p&gt;

&lt;p&gt;Instead of digging through folders or writing custom queries, you can retrieve matching media with a single expression:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_media_by_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Example queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tags:portrait&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags:handmade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource_type:video&lt;/code&gt; AND &lt;code&gt;tags:nature&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use search to build quick admin dashboards, galleries, filters, or automated content workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivering Your Media
&lt;/h2&gt;

&lt;p&gt;With Cloudinary managing your media, delivery becomes as powerful as upload. You can apply on-the-fly transformations directly in the image or video URL with no processing required in Flask.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Dynamic Image Sizing in a Flask Template
&lt;/h3&gt;



&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; 
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{ cloudinary_url(public_id, width=width, height=height, crop=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;) }}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your Image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Or, using the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cloudinary.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary_url&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cloudinary_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;public_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Why this beats DIY storage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You don’t store multiple versions of each image&lt;/li&gt;
&lt;li&gt;Users automatically receive optimized formats (WebP, AVIF, MP4, etc.)&lt;/li&gt;
&lt;li&gt;Dynamic resizing keeps your site lightweight and fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For responsive layouts, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloudinary.com/documentation/responsive_html" rel="noopener noreferrer"&gt;Responsive images using HTML and dynamic image transformations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudinary.com/documentation/video_overview" rel="noopener noreferrer"&gt;Managing and delivering videos at scale.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149581%2FWeb_Assets%2Fblog%2Fguitar-man-1%2Fguitar-man-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149581%2FWeb_Assets%2Fblog%2Fguitar-man-1%2Fguitar-man-1.jpeg%3F_i%3DAA" width="1600" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149585%2FWeb_Assets%2Fblog%2Fguitar-man2-1%2Fguitar-man2-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149585%2FWeb_Assets%2Fblog%2Fguitar-man2-1%2Fguitar-man2-1.jpeg%3F_i%3DAA" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149587%2FWeb_Assets%2Fblog%2Fguitar-man3-1%2Fguitar-man3-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149587%2FWeb_Assets%2Fblog%2Fguitar-man3-1%2Fguitar-man3-1.jpeg%3F_i%3DAA" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap: Presets, Uploads, Search, and Delivery
&lt;/h2&gt;

&lt;p&gt;In short, the features we’ve covered include upload presets, smart uploads, AI-powered search, and dynamic delivery. Apply these features to take managing media files in Flask from “painful but necessary” to “effortless and scalable.”&lt;/p&gt;

&lt;p&gt;Instead of writing boilerplate logic for uploads, storage, and optimization, you can focus on building features your users care about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search &amp;amp; discovery (keywords):&lt;/strong&gt; Flask file upload Cloudinary; Cloudinary upload preset Python; Cloudinary Search API tags; Cloudinary Upload Widget Flask; Flask image delivery and &lt;code&gt;cloudinary_url&lt;/code&gt; transformations; Python SDK, user uploads, UGC.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/umWs0" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>flask</category>
      <category>python</category>
      <category>media</category>
      <category>upload</category>
    </item>
  </channel>
</rss>
