skills-il/developer-tools/cloudinary-assets

Claude Code and agent skill by skills-il/developer-tools

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Manage media assets through Cloudinary's REST API -- upload, transform, optimize, and deliver images and videos. Use when user asks about image upload, media optimization, image transformations, responsive images, video management, CDN delivery, or mentions Cloudinary specifically. Covers Upload API, Admin API, URL-based transformations, AI-powered effects (gen_remove, gen_replace, background removal), and delivery optimization. Israeli-founded (2012) with R&D in Petah Tikva; global HQ in San Jose, California. Do NOT use for non-Cloudinary media hosting or local image processing without cloud upload.

Install

npx vskill@latest install skills-il/developer-tools/cloudinary-assets

SKILL.md

Cloudinary Assets

Instructions

Step 1: Verify Cloudinary Configuration

Check for Cloudinary credentials:

python
import os
def get_cloudinary_config():
"""Get Cloudinary config from environment."""
# Option 1: CLOUDINARY_URL (preferred)
cloudinary_url = os.environ.get('CLOUDINARY_URL')
if cloudinary_url:
return {"url": cloudinary_url}
# Option 2: Individual variables
cloud_name = os.environ.get('CLOUDINARY_CLOUD_NAME')
api_key = os.environ.get('CLOUDINARY_API_KEY')
api_secret = os.environ.get('CLOUDINARY_API_SECRET')
if all([cloud_name, api_key, api_secret]):
return {"cloud_name": cloud_name, "api_key": api_key, "api_secret": api_secret}
return None # Credentials not configured

If not configured, guide the user:

  1. Sign up at https://cloudinary.com (free tier: 25 credits per month)
  2. Find the cloud name, API key and secret on the API Keys page of the Console Settings
  3. Set CLOUDINARY_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME
Step 2: Choose Operation
OperationAPIMethodWhen
Upload imageUpload APIPOST /image/uploadNew image to store
Upload videoUpload APIPOST /video/uploadNew video to store
Transform imageURL-basedGET (URL)Resize, crop, effects
Optimize deliveryURL-basedGET (URL)Performance improvement
List assetsAdmin APIGET /resourcesBrowse media library
Delete assetUpload APIPOST /{resource_type}/destroyRemove media (image, video or raw)
Get asset detailsAdmin APIGET /resources/{id}Check metadata
Step 3: Upload Media

Upload an image:

python
import requests
import hashlib
import time
def upload_image(file_path, cloud_name, api_key, api_secret,
folder="", asset_folder="", tags=None):
"""Upload image to Cloudinary."""
timestamp = str(int(time.time()))
# EVERY upload parameter except file, cloud_name, api_key, resource_type
# and signature itself must be in the signed string, sorted alphabetically.
# Signing only a subset (e.g. omitting tags) yields "Invalid Signature".
params = {"timestamp": timestamp}
if folder:
params["folder"] = folder # fixed folder mode
if asset_folder:
params["asset_folder"] = asset_folder # dynamic folder mode (new accounts)
if tags:
params["tags"] = ",".join(tags)
params_to_sign = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
signature = hashlib.sha1(
f"{params_to_sign}{api_secret}".encode()
).hexdigest()
url = f"https://api.cloudinary.com/v1_1/{cloud_name}/image/upload"
data = {"api_key": api_key, "signature": signature, **params}
with open(file_path, "rb") as f:
response = requests.post(url, data=data, files={"file": f}, timeout=(10, 180))
response.raise_for_status()
return response.json()

Files over 100 MB (most real videos) cannot go through this single POST. Cloudinary requires chunked upload for them: use the server SDK's upload_large (cloudinary.uploader.upload_large in Python, cloudinary.v2.uploader.upload_large in Node) and ALWAYS pass resource_type="video" for a video. Do not rely on the SDK default: in the current SDK source upload_large falls back to raw, and a raw asset gets no video transformations, so every /video/upload/ URL for it fails. Chunking does not lift your plan's size cap (see Troubleshooting). For video eager transformations also pass eager_async=true so encoding runs in the background instead of blocking the upload, and eager_notification_url to be told when it finishes.

Uploads from a browser or Next.js app. Never ship api_secret to the client. There are two documented routes, and either one is also the fix for a "blocked by CORS policy" error on a direct browser upload:

  1. Signed (preferred for user uploads). The browser asks your server for a signature, then uploads

directly. With next-cloudinary, give CldUploadWidget a signatureEndpoint: ``ts // app/api/sign-cloudinary-params/route.ts (server only) import { v2 as cloudinary } from "cloudinary"; export async function POST(request: Request) { // Authenticate the caller first: this route signs whatever it is sent. const { paramsToSign } = await request.json(); const signature = cloudinary.utils.api_sign_request(paramsToSign, process.env.CLOUDINARY_API_SECRET!); return Response.json({ signature }); } ` `tsx import { CldUploadWidget } from "next-cloudinary"; <CldUploadWidget signatureEndpoint="/api/sign-cloudinary-params"> {({ open }) => <button onClick={() => open()}>Upload</button>} </CldUploadWidget> ` Env: NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, NEXT_PUBLIC_CLOUDINARY_API_KEY (the key may be public), CLOUDINARY_API_SECRET (server only). next-cloudinary also ships CldImage and CldVideoPlayer` for delivery.

  1. Unsigned. An unsigned upload_preset named in the request. Anyone who sees the preset name can

upload with it, so harden it (see Gotchas).

Step 4: Transform Images via URL

Build transformation URLs using this pattern:

https://res.cloudinary.com/{cloud_name}/image/upload/{transformations}/{public_id}.{format}

Common transformation recipes:

GoalTransformationExample
Thumbnailw_150,h_150,c_fill,g_faceFace-aware 150x150 thumbnail
Hero imagew_1200,h_600,c_fill,q_auto,f_autoOptimized hero banner
Profile avatarw_200,h_200,c_thumb,g_face,r_maxCircular face crop
Product imagew_800,h_800,c_pad,b_whitePadded on white background
Social sharew_1200,h_630,c_fillOpenGraph image size
Watermarkedl_{your_logo_public_id},w_200,o_50/fl_layer_apply,g_south_eastSemi-transparent watermark. Replace the placeholder with a real public ID: l_watermark on an account without that asset returns 400 Resource not found - watermark

Folder mode matters. Cloudinary has two modes and new product environments are created in dynamic folder mode. In dynamic folder mode the parameter that places an asset in the folder tree is asset_folder, and it does NOT affect the public ID. The older folder parameter is the fixed-folder-mode parameter, where the folder becomes part of the public ID. Passing folder on a dynamic-folder account therefore does not do what a fixed-folder tutorial implies. Check your product environment's folder mode in Settings before scripting bulk uploads, and pass asset_folder when you are in dynamic mode.

Step 4b: AI-Powered Transformations

Cloudinary's generative AI effects (gen_remove, gen_replace, gen_background_replace, gen_recolor, gen_restore) are available as e_gen_* URL params. Generative fill is the exception: it is a background qualifier b_gen_fill:prompt_<text> used with a padding crop in the SAME component, e.g. c_pad,w_1600,h_900,b_gen_fill:prompt_beach (200). Two traps: the prompt is unparenthesized like e_gen_background_replace (b_gen_fill:prompt_(beach) returns HTTP 500 General Error), and without a padding crop it returns HTTP 400 gen_fill only available for padding crop modes that fills a padded area, NOT an e_gen_fill effect (constructing e_gen_fill:... returns a 400). Some variants may still be flagged as Beta on the docs page, so check the current status before relying on a specific effect in production:

ParamWhat it does
e_gen_remove:prompt_(person)AI removes the matched object from the image
e_gen_replace:from_car;to_bicycleAI replaces one object with another (documented form is unparenthesized). A 400 Invalid input for gen_replace usually means the from object was not found in the image, not that the syntax is wrong
e_gen_background_replace:prompt_beach%20at%20sunsetGenerative background swap. Note the prompt is NOT parenthesized for this effect: prompt_(beach) returns HTTP 500 General Error, while prompt_beach returns 200. The parenthesized form is correct for e_gen_remove but not here
e_background_removalBackground removal, a built-in transformation (no separate add-on subscription; the legacy add-on is closed to new accounts from Feb 1 2026). It is NOT free, it bills via special transformation counting.
e_gen_restoreAI restoration for old, blurry, or damaged photos
auto_tagging:0.7Auto-tag uploads via AI (confidence threshold 0.0-1.0); pass at upload time. Unlike the e_gen_* effects this is NOT built in: it requires registering a tagging add-on (Google Auto Tagging, AWS Rekognition, Imagga) on the Add-ons page first, otherwise the upload returns an error instead of tags

Example: remove a person from the background, then replace background:

https://res.cloudinary.com/{cloud_name}/image/upload/e_gen_remove:prompt_(person)/e_gen_background_replace:prompt_modern%20office/{public_id}

Auto-tagging at upload time:

python
data = {
"api_key": api_key, "timestamp": timestamp, "signature": signature,
"categorization": "google_tagging",
"auto_tagging": 0.7, # accept tags with >=70% confidence
}
# categorization and auto_tagging are upload parameters, so both must be in the
# signed string too, or the upload fails with Invalid Signature.
Step 5: Optimize for Performance

Apply automatic optimization:

# Add f_auto (format) and q_auto (quality) to any URL
https://res.cloudinary.com/{cloud_name}/image/upload/f_auto,q_auto/{public_id}

Generate responsive breakpoints:

python
def get_responsive_urls(cloud_name, public_id, widths=None):
"""Generate responsive image URLs."""
if widths is None:
widths = [320, 640, 960, 1280, 1920]
base = f"https://res.cloudinary.com/{cloud_name}/image/upload"
urls = {}
for w in widths:
# c_limit: never upscale. Without it, w_1920 on an 864px original
# delivers a 1920px image.
urls[w] = f"{base}/w_{w},c_limit,q_auto,f_auto/{public_id}"
srcset = ", ".join(f"{url} {w}w" for w, url in urls.items())
return urls, srcset

HTML responsive image tag:

html
<img
src="https://res.cloudinary.com/{cloud_name}/image/upload/w_960,c_limit,q_auto,f_auto/{public_id}"
srcset="{generated_srcset}"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
alt="Description"
loading="lazy"
/>

Video delivery. Deliver from /video/upload/, not /image/upload/ (the same public ID under the image path is a different asset, usually a 404):

GoalURL
Optimized progressive MP4/WebM.../video/upload/q_auto,f_auto:video/{public_id} (use f_auto:video when the URL has no extension: with plain f_auto, a request whose Accept header lists image types, as a browser navigation does, gets an AVIF image instead of the video)
Adaptive bitrate streaming (HLS).../video/upload/sp_auto/{public_id}.m3u8 (.mpd for DASH). Play it with an HLS/DASH-capable player such as the Cloudinary Video Player
Poster frame.../video/upload/so_5,w_800,h_450,c_fill,q_auto,f_jpg/{public_id}

q_auto on an .m3u8 is NOT adaptive streaming: it returns a single-rendition playlist. Only sp_auto (or a named streaming profile) produces the multi-rendition master playlist. Restrict an image URL to image candidates with f_auto:image.

Step 6: Manage Assets

List all assets:

python
def list_assets(cloud_name, api_key, api_secret, resource_type="image",
max_results=30, all_pages=False):
"""List assets in Cloudinary media library.

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
1
Repository forks
1
7-day trend score
3

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 20 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/skills-il/developer-tools/cloudinary-assets/badge)](https://verified-skill.com/skills/skills-il/developer-tools/cloudinary-assets)

Something wrong with this skill or its page? Tell us.