Bulk asset ingestion from a spreadsheet
A published CSV upload flow turns a one-off media migration into a spreadsheet job with no code to deploy. The work that decides whether it succeeds is row-level failure tracking, pacing against your plan's API allowance, and supplying metadata at creation time rather than in a second pass.
Before you start
Three things need to be true before the first row runs.
A product environment you can write to. The Free plan is usable in production and needs no credit card, so a migration does not require an upgrade first. It does have hard ceilings worth checking against your source files: images over 10 MB or above 25 megapixels are rejected, video over 100 MB is rejected, and the Admin API is capped at 500 requests per hour — figures from Cloudinary’s public pricing page, checked 2026-08-18.
Source files the flow can fetch. The upload endpoint takes a remote URL in place of a file body, which is the property that makes a spreadsheet viable at all: each row carries a URL, and Cloudinary pulls the bytes itself. If the originals only exist in a folder on a laptop, put them somewhere fetchable first — otherwise you are writing a script after all.
A copy of the sheet you can edit while the run is in progress. The flow will be writing back to it.
Steps
-
Start from the published CSV upload flow rather than building one. MediaFlows ships a CSV upload PowerFlow that reads rows and creates an asset from each one. For a one-off migration this is the whole point: there is no service to write, no deploy, no credentials to hand to CI, and no code to maintain for a job that runs once. You supply a spreadsheet and the flow does the rest. The prebuilt flow is also the reason not to reach for the canvas immediately — see the difference between the low-code canvas and the natural-language builder before you start editing blocks. If your ingestion is continuous rather than one-off, and driven by events from your own systems, a hand-written webhook handler is the better shape and this page is the wrong approach.
-
Validate the sheet before it touches the flow. A spreadsheet is a poor system of record and an excellent transfer format. It has no constraints, no types, no uniqueness guarantees, and whoever exported it may have re-sorted a column independently of the others. Treat it as input to validate, not as truth. Before the run, add formula columns that check: the URL is well-formed and resolves, the file size is under your plan’s ceiling, the
public_idis unique within the sheet, and every required metadata field is non-empty. A bad row caught in the sheet costs a cell edit. The same row caught at position 4,000 costs the remainder of the run. After the migration, invert the relationship: Cloudinary is the record and the sheet is a snapshot of one afternoon, so do not go back and edit it expecting anything downstream to notice. -
Put every field you will ever want into the sheet as a column. Metadata supplied at creation time is far cheaper than metadata applied afterwards, because the second form re-reads and re-writes every asset — a full extra pass over 10,000 assets, against the same API allowance, to set values you already had in your hand at upload. Tags, structured metadata field values, folder,
public_idand context all belong in the upload call. The exception is metadata that cannot exist before the asset does, such as alt text a model generates from the image itself; that genuinely requires a second pass, so plan and budget it as its own run rather than assuming it rides along free. -
Size the run against your plan’s allowance and decide how it paces. Ingest rate is bounded by the plan’s API allowance, not by the flow. The flow will submit as fast as it can read rows, so a large import either paces itself or spends its first minutes generating rate-limit errors and its next hour being retried. Two numbers govern this. The first is request rate — on Free, 500 Admin API requests per hour is the documented ceiling, which a 10,000-row import cannot clear inside a day if it issues an Admin call per row. The second is credits. One credit is 1 GB of managed storage, 1 GB of delivered bandwidth, or 1,000 transformations, per Cloudinary’s credits FAQ, checked 2026-08-18. Storage alone decides whether a migration fits:
Import Stored bytes Credits for storage Against Free (25/mo) Against Plus (225/mo) 10,000 × 500 KB ~5 GB 5 fits, 20 left for everything else comfortable 10,000 × 2 MB ~20 GB 20 80% of the plan before a single transformation comfortable 10,000 × 8 MB ~80 GB 80 over the plan fits, 145 left Advanced carries 600 credits a month and the ladder continues above it. Note which meter you are moving: transformations and bandwidth are measured over a rolling 30-day window, so a spike ages out on its own, while storage is a current total that drops the moment you delete assets. If the import is being driven by an agent rather than by you clicking run, the pacing question gets sharper — metered billing under agent workloads is where an unattended retry loop turns into an invoice.
-
Add a status column and make the flow write to it on every row. This is the step that decides whether an interrupted migration is a job or an incident. Row-level failure handling is the whole difficulty: a run that stops at row 4,000 of 10,000 is recoverable only if the flow recorded which rows completed. Without that record, your options are re-running all 10,000 — paying again for 4,000 uploads and either overwriting or colliding on their
public_ids — or reconciling assets against rows by hand. With it, resuming is a filter onstatus is empty or status = error. Write three values per row: the outcome, the returnedpublic_idor secure URL, and the error text when there is one. The error text is what converts “412 rows failed” into “412 rows failed because the source host rate-limited us”, which is a different fix from “412 files were too large”. Set the upload to not overwrite an existingpublic_idwhile you are at it, so a duplicate re-run fails loudly instead of silently replacing good assets. -
Run 20 rows and inspect the assets, not the log. Open the resulting assets in the media library and check the things the log cannot tell you: the
public_idshape is what you intended, the folder is right, the tags and structured metadata actually landed on the asset rather than being accepted and dropped, and a delivery URL built from thepublic_idreturns the image. A naming mistake found at 20 rows costs 20 re-uploads. The same mistake found at 10,000 costs a rename of every asset, which is another full pass against the allowance you just spent. -
Run the full sheet in batches and resume from the status column. Split the rows into batches sized to the request ceiling you calculated in step 4 rather than submitting all of them at once. After each batch, filter the sheet for blank and error statuses and re-run only those rows. Repeat until that filter returns nothing. Read the error column between batches — a class of failure that appears in 300 consecutive rows is usually one bad export, and fixing it in the sheet is faster than letting the retry loop grind through it.
What done looks like
The status column has no blanks and no errors, and every row carries the public_id of a real asset. The asset count in the product environment matches the row count in the sheet. Tags, folders and structured metadata are present on the assets from the moment they were created, with no follow-up pass queued to add them. Your credit consumption for storage matches the arithmetic you did in step 4, within the margin of your own file-size estimate. The sheet is now a migration record, not a live index — the assets are the record, and the flow that created them is still sitting there, unmodified, ready for the next spreadsheet.
Sources
- Cloudinary's public pricing page cloudinary.com
- upload endpoint takes a remote URL in place of a file body cloudinary.com
- CSV upload PowerFlow that reads rows and creates an asset from each one cloudinary.com
- Cloudinary's credits FAQ cloudinary.com
See also
-
How no-code builders upload and deliver media: pre-built integrations, plain transformation URLs, one shared credential, and where signed requests stop.
-
PowerFlows give canvas-level control over branching and third-party calls; EasyFlows trade that for speed. Which MediaFlows form fits which automation.
-
A hosted visual automation and a hand-written webhook handler weighed on setup cost, ongoing maintenance, who is allowed to edit, and version control.
-
Generate alt text inside a MediaFlows automation, write it back to asset metadata, and keep decorative images out of the flow.