Home Guides Serial numbers on Shopify, item by item

How to give every item its own number on Shopify

Shopify counts variants, not objects. The four ways to number individual items anyway, starting with the one that costs nothing and needs no app at all.

Last updated 11 August 2026

Start here, because you may not need anything

If the only person who will ever read the numbers is you, write them in a spreadsheet. One column for the number, one for the product, one for the date, one for where it went. It costs nothing, it works offline, and it is genuinely the right answer for a workshop making a few dozen things a year.

The spreadsheet stops being enough at one specific moment: when somebody outside your workshop needs to read the record. A customer checking their edition number. A resale buyer three owners later. Your own repairs bench, when the person who keeps the spreadsheet is on holiday. Until that moment arrives, everything below is more machinery than your problem deserves.

The rest of this page assumes the moment has arrived, or that you can see it coming.

Why Shopify does not do this out of the box

Shopify's model has three levels: product, variant, inventory quantity. A variant with forty units in stock is one row with the number forty in it. There is no fortieth object anywhere in the system, and no place to hang anything off one.

That is a reasonable design for most commerce, where the units genuinely are interchangeable. It stops being reasonable the moment they are not: hand-made pieces that vary, limited editions that are numbered, anything with a warranty that runs from a date, anything that gets resold.

So every approach below is a way of storing something Shopify has no native place for. They differ mostly in who can read it and how much work it is.

The four ways, cheapest first

1. One variant per item

The blunt one. Make "Number 07" an option value, so each object is its own variant with its own SKU.

Where it works. Editions of ten or twenty, sold once, where the number is part of what the buyer is choosing. It is native, it needs no app, inventory is correct automatically, and Shopify's own reporting understands it.

Where it falls over. Shopify's variant limits arrive faster than you expect once you multiply numbers by colourways. Your product page becomes a dropdown of a hundred options. And the moment a piece is sold, its variant is a permanent zero-stock row cluttering every list you look at for the rest of time. Nothing about a variant survives being sold, because a variant describes something buyable and this object no longer is.

2. Metafields on the order or the line item

The one most people land on. When you pack an order, you write the item's number into a metafield on that order.

{% comment %}
  Showing a per-item number stored on the order, in an order status or
  customer account template. `item_number` is a namespace and key you
  defined yourself in Settings → Custom data → Orders.
{% endcomment %}
{% if order.metafields.custom.item_number %}
  <p>Your piece: {{ order.metafields.custom.item_number }}</p>
{% endif %}

Where it works. Genuinely well, for warranty and for repairs. The number is attached to the sale, it is searchable in the admin, and it costs nothing beyond the discipline of typing it at packing time.

Where it falls over. It is a record of a sale, not of an object. Once the piece is resold, the order says nothing to the person now holding it, and there is no way for them to look anything up. It also has no uniqueness check anywhere: type the same number into two orders and Shopify will let you, and nothing will tell you until somebody notices.

3. Metaobjects

Shopify's own custom data type. Define a metaobject called something like item with fields for a code, a product reference and a date, and create one entry per object. Set it to public read and your theme can look one up.

Where it works. It is the right shape. The record describes an object rather than a sale, it lives in your own admin, it survives everything, and a theme can read it without an app in the middle.

Where it falls over. Three things, and they are the reason this guide exists.

  • Metaobjects cannot enforce uniqueness. There is no constraint you can put on a field that says "no two entries may have this value". You can check before you write, and you will be right almost every time, and the time you are wrong you will have two objects in the world carrying the same code and no way to tell which is which.
  • Creating them in bulk is cost-limited. The Admin GraphQL API charges points per mutation against a ceiling. Creating a few hundred entries is not one request, it is a batching problem with retry logic.
  • metaobjectCreate against a handle already in use does not fail. It succeeds, with a suffixed handle. So the naive "just retry it" repair loop quietly creates duplicates rather than erroring, which is the worst available behaviour for a register.

If you are making a few dozen items and are careful, metaobjects on their own are a perfectly good answer. At a few hundred, the three points above stop being theoretical.

4. An app that keeps a register

Which is what Provenance is. The short version of what an app adds over metaobjects on their own:

  • Uniqueness enforced as a database constraint rather than a hopeful check.
  • Codes generated from a cryptographic random source, in an alphabet with no I, L, O or U in it, so nothing can be misread off a card.
  • Batched writes into your metaobjects with every returned handle compared against the one sent, and a repair path that looks each entry up before deciding whether to create or update it.
  • Printable output: vector QR codes and a merge-ready CSV, or a print sheet you can use today.
  • Somewhere for the record to keep changing after the sale.

The metaobjects are still created in your admin and are still yours. The app is the thing that keeps them correct.

Choosing what the code looks like

Whichever route you take, this part is yours to get right and it is easy to get wrong.

Do not encode anything in it. A code like TOTE-BLK-0042 tells anybody holding two cards how many totes you have made, how you break them down by colourway, and roughly what your production volume is. That is competitive information you have printed onto a card and handed to a stranger. Random is better, and shorter than you think: twelve characters from a 32-letter alphabet is more combinations than you will ever need.

Drop the ambiguous letters. I, L, O and U. Crockford's Base32 alphabet does this already and is worth borrowing. A 0 read as an O is a lookup that fails silently: no error in your theme, no error in Shopify, just a customer who concludes your verification does not work.

Normalise before you compare. Trim the whitespace and lowercase it, on both sides, always. A trailing space pasted out of an email is otherwise a failed lookup with no explanation.

Decide the format before you print. Everything above is free to change right up until the moment cards go to the printer, and impossible afterwards.

Where the number has to appear

The code on its own is not the deliverable. Three places matter:

  1. On the object or its packaging, as a QR code and as readable text. The text is not decoration: a phone with no signal, a damaged card or an elderly customer all end in somebody typing four characters at a time.
  2. In your own records, searchable, so the repairs bench can find it.
  3. Somewhere the holder can look it up, if anyone but you needs to read it.

The second and third are different requirements and it is worth being honest about which you actually have. If it is only the second, go back to the spreadsheet at the top of this page.

The version of this with a register behind it

Provenance does what is described above without a spreadsheet to keep in step: unique identifiers, printable codes, and a lookup that answers from a register held in your own Shopify admin.