Datima.ai

Blog

Batch expiry dates in Shopify: one product, many dates

25 September 2026 · Datima.ai

Ask a merchant for the expiry date of their vitamin D3 and they will hesitate, because there isn't one. There are three: the 40 jars from the September delivery, the 24 jars from the one before it that the supplier cleared out, and the six left on the shelf from July. An expiry date is not a property of a product. It is a property of a batch — this delivery, this quantity, this date. Every system that stores “the expiry date” once per product is wrong from the second delivery on, and Shopify, which counts units per variant and nothing else, never asked the question at all.

This post is about doing it properly: what a batch record needs, how batches meet orders and refunds, and how to get the dates in without a month of typing.

What a batch record needs

Five fields carry almost all of the value. Everything else is decoration.

  • The expiry date. As printed — best-before or use-by, the distinction matters for what you may still sell, but both go in the same field. Labels disagree on format (12/2026, 2026-12-01, 01.12.26, a Julian code on the can); pick one way of writing it in the system and convert at the door, not later.
  • Quantity received, and quantity remaining. Two numbers, not one. Received never changes; remaining goes down with every sale and up with every refund. When they drift apart from what is physically on the shelf, the gap is the audit.
  • The lot code. The supplier's identifier, straight off the label. It is what a recall notice quotes, and it is how the picker tells two batches of the same product apart when both dates are in December.
  • The location. Which warehouse or shop the batch sits in. A sale from the London shop must never consume a batch that is in Manchester; that sounds obvious and is the single most common way batch numbers go wrong in multi-location stores.
  • The bin. Shelf, rack, drawer. Not for the software — for the human who has to find the oldest 24 jars in a room of identical boxes.

One row per delivery. The product with three deliveries has three rows, and that is the correct number, not a mess to be tidied.

How batches meet orders

Recording batches is the easy half. The hard half is keeping the remaining quantities true while orders flow, and that has to happen automatically, because nobody reconciles a spreadsheet on a Saturday in December.

The rule is FEFO: when an order is fulfilled, its units come off the batch with the earliest expiry date at the location that shipped it. Then the awkward cases, which are where a system earns its keep:

  • Refunds go back to the batch they came from, not to “the product”. If the December jars were sold and one comes back, the December batch has one more, not the November one.
  • A sale that outruns the records — a delivery nobody entered — must be flagged as unmatched, not silently borrowed from another batch. The flag is annoying. Numbers that look right and aren't are worse.
  • Corrections keep their history. When the picker took the other lot, you say so; the system records the guess and the correction both. At recall time you want the trail, not the tidied version.
  • Two orders in the same second hitting the same batch must not both take the last unit. This is a database problem, and it is why a spreadsheet cannot do the job past a few orders a day.

The number that falls out

Once every sale is charged to a batch, one figure comes free: value at risk by window — what expires in the next 7, 30 and 90 days, counted in money at your selling price. Not units; money is the unit decisions are made in. That figure decides a promotion, a bundle, a call to the supplier, or whether the next purchase order is too big. It is also the figure a weekly email should carry, so the decision happens before the date rather than at the stocktake after it.

Getting the dates in

Merchants put this off because of the first afternoon: hundreds of products, dates on labels, no field to type into. Three ways, in order of how well they survive:

  1. A CSV built from your own catalog. Export the products, add a column per batch for date, quantity, lot and bin, and import. The import should validate before it writes — a preview of what will be created, a file of the rows that failed with the reason next to each so you can fix and re-upload just those. An import that writes half a file and stops is how duplicate batches are born.
  2. Bulk edit for the many, one form for the few. When a delivery arrives, you should be able to add the same date and lot to twelve variants at once. The one-at-a-time form is for the odd correction.
  3. A metafield or a tag per product. The temptation, because Shopify offers them. They hold one date per product, which is the mistake this whole post is about.

After the first afternoon, the habit is small: when a delivery lands, add its batch. Thirty seconds per line, while the box is still open and the label is in your hand.

Showing the date to the customer

Once the earliest date per product is known, it can go on the product page — a small “Best before” line that reassures a supplement or food buyer and cuts the email that begins “I received an item that expires next week”. The clean way is a theme block that reads the earliest unexpired batch date and localises it; the storefront shows the date, the batch system keeps it true, and nobody edits a product description by hand when a new delivery shifts the date.

What this looks like in practice

Datima is this model built for Shopify: batches with date, lot, location and bin; FEFO on every order, refunds returned to their batch, unmatched sales flagged; value at risk by 7 / 30 / 90 days in money; a CSV import that previews before it writes and hands failures back as a file; a weekly email of what is expiring; and a theme block that puts the best-before date on the product page. It is free on the Shopify App Store, with no product limit, and the three-minute demo walks one product from three batches to a shipped order and a refund.