How to tag a catalogue of hosted images from a CSV

Published 15 July 2026

Most guides about image tagging assume the images are on your desktop. In any organisation of any size, they are not: they are on a CDN, in an S3 bucket, or inside a DAM that will happily give you a URL and nothing else.

Downloading forty thousand of them to your laptop so you can tag them is not a plan. This guide is about the route that works: tag them where they are, from a CSV of their URLs.

The shape of the job

You need three things:

  1. A list of image URLs, each reachable without a login.
  2. Your own identifier for each image, so the results can be joined back onto your catalogue.
  3. A pass that goes through the list, tags each one, and gives you a file back.

That is it. The whole method fits in a CSV with two columns.

Step 1 — Export the CSV

Two columns, and every other column is ignored:

image_url,asset_id
https://cdn.example.com/products/1024-front.jpg,SKU-1024-01
https://cdn.example.com/products/1024-side.jpg,SKU-1024-02
https://cdn.example.com/products/1188-front.jpg,SKU-1188-01
  • image_url — the publicly reachable URL.
  • asset_id — your identifier. This is the important one: it is carried through untouched to the export, so the results join straight back onto the rows you started with. Use whatever your system already keys on — a SKU, a DAM asset ID, a database primary key.

Header names are matched loosely (image url, Image_URL and imageurl all work), because every system exports them slightly differently.

Getting this CSV is usually a five-minute query against the database you already have. It is worth resisting the urge to make it clever — no extra columns, no nested JSON, no pagination. Two columns.

Step 2 — Run the pass

Upload the CSV to PicsTag, press Start, and it works through the rows: fetching each image, tagging it, captioning it. You can stop at any point and everything done so far is kept.

Two practical notes for a large run:

  • The first image is slow. The models (~250 MB) download once and are then cached by your browser. After that, every image is fast.
  • Do not close the tab. The queue lives in the page. This is the price of a tool with no server: nothing is stored anywhere, including your progress.

Step 3 — Expect CORS failures, and know what to do

This is the one thing that will surprise you, so let’s be blunt about it.

A browser cannot fetch an image from a server that does not explicitly allow cross-origin requests. If your CDN does not send an Access-Control-Allow-Origin header, the fetch fails — and this is a rule of the web enforced by the browser, not a limitation any tool can code around. Anyone claiming otherwise is running a server-side proxy and, therefore, receiving copies of your images.

Three ways through it:

  • Switch to the cloud model in the settings. In that mode the model fetches the URL server-side, so your browser never has to — CORS stops being your problem. You are then sending the URL to that provider, and paying per image.
  • Allow CORS on your bucket. If the images are yours and already public, adding the header is a one-line configuration change on S3, Cloudflare, or any CDN.
  • Download and drop the files in. Tedious for forty thousand, entirely fine for four hundred.

Failed rows are marked with an error and can be retried individually. The run does not abort because one URL is a 404 — and in an old catalogue, some of them will be.

Step 4 — Review, and don’t skip it

The temptation with a bulk run is to accept everything and export. Resist it: a model is confidently wrong often enough that “accept all, unreviewed” is how plausible nonsense enters your catalogue and stays there for years.

The workable compromise:

  1. Auto-accept above a threshold. 80% is a reasonable place to start. The model is rarely wrong when it is that sure, and this clears most of the batch in one click.
  2. Review the rest by image, not by row in a spreadsheet. You cannot judge a tag without seeing the picture.
  3. Only accepted tags are exported. Anything you rejected or never looked at is dropped.

Where to put that threshold is a real decision — how accurate is AI image tagging walks through the trade-off.

Step 5 — Join it back

The export is a CSV or JSON with, per row: asset_id, filename, file size, dimensions, description, the accepted tags, and the source URL.

Because asset_id came from your system and was never touched, the join is trivial:

UPDATE assets a
SET keywords = i.validated_tags,
    description = i.description
FROM imported_tags i
WHERE a.asset_id = i.asset_id;

No filename matching, no fuzzy reconciliation, no afternoon lost to a spreadsheet. This is the entire reason the asset_id column exists, and the reason it is worth being disciplined about in step 1.

Why this is worth doing at all

A backlog of untagged hosted images is the most common form of dead library there is. It is not that people do not want to tag it — it is that every route to doing so has been either a per-image API bill big enough to need approval, or a migration project big enough to need a budget.

Tagging them from a CSV, in a browser tab, at zero marginal cost, turns that project back into a task. It is not going to produce a perfect catalogue. It will produce one where the search box returns something — which is the entire point of having a library.

Try it on your own images

Free, no account, and your images never leave your browser.

Open PicsTag