Three different stores. Three different requirements. All three said “we need expiry tracking” — and all three needed something subtly different. Here is how to tell which pattern fits your operation before you write a line of code.
Pattern 1 — FIFO by received date
First in, first out. The oldest stock leaves first regardless of expiry. This is the right pattern for non-perishable goods with a long shelf life: cosmetics, packaged dry goods, electronics with battery shelf life.
- What you track: received-on date per lot.
- What you don’t track: expiry per unit. You assume the oldest received is the closest to expiry.
- When it breaks: when you receive a batch with a much shorter expiry than the previous one. FIFO will happily ship the older, longer-dated batch first.
Pattern 2 — FEFO by expiry date
First expired, first out. The lot closest to its expiry date leaves first regardless of when it was received. This is the right pattern for perishables: supplements, pharmaceuticals, food, cell culture media, anything where a regulator cares about the date on the package.
- What you track: expiry date per lot, optionally per unit.
- What it costs: a pick-and-pack step that consults the lot record, not just the bin location.
- When it breaks: when stock is binned without lot separation. If a pallet of two different lots is consolidated, FEFO becomes a guess.
Pattern 3 — Lot-level traceability for recall
This is the heaviest pattern: every unit shipped is traceable back to the lot, supplier, and ship date. Regulated industries require this. So do high-end consumer brands that ever want to do a clean recall without blast-emailing every customer.
- What you track: lot + supplier + receive date + expiry + every order line that drew from that lot.
- What it unlocks: “lot 4471-B affects these 312 orders, send recall email to these 287 customers” — in five minutes, not five days.
- What it costs: a join table between lots and order lines. Adds storage, adds query complexity, removes existential risk from your next quality incident.
Which one are you, actually?
The honest test: imagine your worst-case quality incident. A supplier ships a contaminated lot. You find out three weeks later. What does the next hour look like?
- If your answer is “I don’t know which customers got that lot” — you need pattern 3, not last quarter, but today.
- If your answer is “I can pull a list of orders from the date range” — pattern 2 with date-bounded lot tracking is usually enough.
- If your answer is “the product can’t really go bad in a way that hurts anyone” — pattern 1 is fine, and the operational simplicity is worth keeping.
What we usually build
For most WooCommerce stores in supplements, food, and cosmetics, the right answer is pattern 2 with a lot-to-order join table — pattern 3 light. It’s enough to do a clean recall without the full operational weight of unit-level serialization. We ship it as a small set of custom post types, a lot record per receive event, and a hook on order completion that records which lot fulfilled each line.
The simplest version of this is about 600 lines of code. The version that survives an audit is about 1,400. The difference is mostly the recall query and the report templates — and you really do want them before you need them.