Bulk QR Generator
Upload a CSV and download a ZIP of QR codes — one per row, named by your chosen column, generated entirely in your browser.
The CSV stays in this tab — it is never uploaded and never stored.
Paste or drop a CSV to see the batch plan here.
Skipped rows: 0
Press ? for shortcuts · The CSV stays in this tab — it is never uploaded and never stored.
Do it offline:$ while IFS=, read -r name url; do qrencode -o "$name.png" "$url"; done < list.csv
How to use it
- Paste CSV text or drop a .csv file — up to 5 MB and 500 data rows per batch. A three-row example CSV is one click away.
- Check the header verdict — flip it to Header or Data if row 1 was misread (a bare URL list often is).
- Pick the QR content column — each cell becomes one code — and optionally a filename column. Names fold to lowercase a–z, 0–9, hyphen and underscore; duplicates get -2, -3 suffixes; empty cells fall back to row numbers.
- Set error correction and the format: SVG for print-sharp vectors, PNG where pixels are required. The plan preview recounts as options change and lists every skipped row with its reason.
- Generate the ZIP (Cancel stops the run), then download qr-codes.zip — your files plus manifest.csv, built in this tab.
Paste or drop a CSV and download a ZIP of QR codes — one per row, named by the column you choose. Batch generation is what QR services usually meter behind a signup; here the run happens in your browser tab, up to 500 codes per batch, and the file never uploads. Skipped rows are reported one by one; manifest.csv maps every file to its row.
How it works
The batch runs in a Web Worker — a background thread of this page — so the tab stays responsive. The worker parses your CSV, plans the batch, then loops: encode one symbol per row, append it to the archive, report progress. Nothing in the loop needs a server; the encoder and the ZIP writer are compiled into the page.
The caps are memory arithmetic, not a meter. An SVG symbol is a few kilobytes; PNGs at large module sizes reach a few hundred kilobytes each. So the batch stops at 500 rows and the archive at 64 MB — the worst case stays in the tens of megabytes — and when a cap fires, the refusal prints the cap and your actual number.
PNG rendering uses OffscreenCanvas inside the worker. Browsers without it (older Safari) get the batch as SVG instead, with a notice saying exactly that — never a frozen page.
Examples
name,url
menu,https://example.com/menu
patio,https://example.com/patio
counter,https://example.com/counter3 QR codes · 0 rows skipped — menu.svg, patio.svg, counter.svg + manifest.csvThe everyday shape: a name column and a URL column. Each URL becomes one code, each name one filename — print-ready table tags for three cafe stations from four lines of CSV.
label,payload
lobby,WIFI:T:WPA;S:LobbyGuest;P:latte-4-all;;
studio,WIFI:T:WPA;S:StudioGuest;P:film-not-dead;;2 QR codes · 0 rows skipped — lobby.svg, studio.svg + manifest.csvRows can carry any raw payload, not just URLs — here two WIFI: network strings become join-this-network codes. Build payload strings in the WiFi QR code generator, paste them into a column, and batch them; the semicolon syntax passes through byte-for-byte.
sku,item url
A-1001,https://shop.example.com/p/a-1001
A-1002,
A-1003,https://shop.example.com/p/a-10032 QR codes · 1 row skipped (row 2: empty payload) — a-1001.svg, a-1003.svg + manifest.csvSkip semantics on a SKU list: row 2 has no URL, so it is skipped and reported by row number — the other rows still encode. Note the filenames: SKU cells fold to lowercase, so A-1001 names its file a-1001.svg.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Filenames are sanitized, deduplicated, never invented
A filename cell folds to lowercase a–z, 0–9, hyphen and underscore (Café Menu becomes cafe-menu), truncated at 64 characters, and slashes die — a cell can never write outside the ZIP's qr-codes/ folder. Duplicates take -2, -3 suffixes; a cell that sanitizes to nothing becomes its row number, like row-7.svg.
Skipped rows are reported, never silently dropped
An empty payload cell, or one too long for the chosen error-correction level, skips that row only. Every skip is listed by row number before you generate; manifest.csv records exactly what the ZIP holds — row, filename, payload.
The 500-row cap is memory math, printed when it fires
Five hundred symbols keeps a worst-case batch comfortably inside a tab's memory. A bigger CSV is refused up front with both numbers — the cap and your row count — so splitting the file is arithmetic, not a guess.
SVG for print, PNG for everything that wants pixels
SVG files are the module grid as vectors: tiny and razor-sharp at any label size. PNG suits chat apps and label software that reject vectors; the module-size option sets its resolution. One batch, one format.
Each row obeys the same capacity law as a single code
A QR symbol holds at most 2,953 bytes at error-correction L and 1,273 at H. Rows over the limit for your level are skipped with that reason; dropping the level toward L is often all a long payload needs.
Common errors
This CSV has 501 data rows — the limit is 500 QR codes per batch. Split the file and run each part separately.- The batch cap, stated with both numbers — the limit and what you pasted — so the fix is arithmetic: split the file and run the parts. The cap keeps a worst-case batch inside a tab's memory; nothing about your CSV is wrong.
Column "url" was not found — this CSV has: name, link.- The selected payload column does not exist in the parsed file — this CSV names its second column link, not url. The message lists the columns the file really has; pick one, or flip the header switch if row 1 was read as data.
FAQ
Does my CSV upload when the QR codes are generated?
No. Parsing, encoding and ZIP assembly run in a worker in this tab — check rather than trust: keep the DevTools Network panel open while you generate, and nothing leaves. The CSV is also never stored between visits.
Why is there a 500-row limit?
Because the whole batch is built in your browser's memory — 500 worst-case symbols plus the archive is tens of megabytes. An engineering cap, not a plan tier — the refusal prints the cap and your row count, and splitting the CSV runs bigger jobs in batches.
Can rows hold WiFi, vCard or other special payloads?
Yes — a cell is encoded byte-for-byte, so any payload a single QR code accepts works in a column: WIFI: network strings, multi-line vCard blocks, mailto: and tel: links. Build one payload in the dedicated WiFi or vCard generator, verify it scans, then repeat it down your column.
What names do the files inside the ZIP get?
Your filename column, folded to lowercase letters, digits, hyphens and underscores and capped at 64 characters — duplicates get -2, -3 suffixes, and rows without a usable name become row-1, row-2. With no filename column every file is numbered by row; manifest.csv records the exact mapping.