All posts

Bulk QR codes from a spreadsheet, without the 3 a.m. surprises

8 min readOperationsPackaging

ONE FILE · 2,500 CODES · A RECEIPT PER ROW

Nobody creates a print run one form at a time. Real deployments — a code per SKU, per table, per yard sign, per machine — are born as spreadsheets, and the question is whether the tool treats your CSV as a first-class citizen or as a stunt. The difference shows up at row 1,204, at 3 a.m., when something is half-created and nothing says what.

The columns are the product

A serious import reads more than a URL column. Ours takes: type, title, link, an optional per-row passcode, folder and subfolder (created on the fly if new), and the five campaign-tag fields — matched by header name, order-independent, so your export from whatever system you run drops straight in. A passcode in the sheet ships properly hashed; a folder in the sheet means the library is organised the moment the import lands, not as a weekend project afterwards.

Demand receipts, not vibes

Bulk operations are made of edge cases: a malformed URL on one row, a destination a safety screen refuses, a batch that brushes the plan limit. The only acceptable behaviour is per-row honesty — every line ends as created, with its slug or failed, with its reason and line number. You fix six rows and re-import six rows, not the file.

skus.csv — 2,500 rows50 rows a time,claimed exactly oncecreated · slug aB3xK2created · slug mQ7pW9row 1,204: bad URLstated, not silentcreated · slug zR4nT6A RECEIPT PER ROW
The shape that survives production: rows claimed exactly once by a bounded runner, receipts per line, and a failure that names itself instead of poisoning the batch.

Why chunking matters more than speed

The naive implementation creates every row inside the upload request — which works in the demo and dies at scale, when the request hits a platform limit partway and leaves an unknown fraction created. The durable design enqueues the file and drains it in bounded chunks that cannot hit a limit, resuming automatically until done. Ours processes a 2,500-row file in a couple of minutes with the tab closed, each row created exactly once even if several workers drain at once — and a crashed chunk self-heals rather than duplicating codes. You feel none of this machinery; you feel its absence in tools that lack it.

Do the quota math before the upload

  • The batch is checked against your plan's allowance up front — a file that won't fit is refused with numbers, not discovered at row N. Plans run 150 to 2,500 dynamic codes; a full print run imports in one file on Business.
  • Rows become dynamic codes — that's the point of a repointable estate. If you're importing permanent identifiers, consider whether those should be static instead: the decision rule.
  • Name rows for the physical object (“Sign 14 — Park St”), not the current destination — destinations change; the object doesn't.

After the import: the estate, not the event

The import is an afternoon; the estate lives for years. Folders keep a thousand codes navigable, bulk repoints move a whole product line at once with every change recorded, and per-code analytics tell you which of the 2,500 actually earn their ink. For the print-side discipline — vector exports, sizing, the URL-length trap — the packaging guide carries on from here; for how it feels day to day, the packaging solutions page is the worked example.