← All posts Insights 5 min read

Expiry Tracking in WooCommerce: Three Patterns for FIFO, FEFO, and Lot Recall

WooCommerce does not track lots or batch numbers. For stores selling perishables, supplements, or regulated products, that is a compliance gap. Here are three concrete patterns: FIFO allocation, FEFO allocation, and a lot recall workflow with full bidirectional traceability.

FIFO FEFO and lot recall in WooCommerce — Softxone field notes
Quick Summary

Expiry Tracking Patterns for WooCommerce — Summary

  • FIFO (First In, First Out): simplest, picks the oldest stock first by date received.
  • FEFO (First Expired, First Out): picks the soonest-to-expire lot regardless of receipt date — mandatory for food, pharma, cosmetics.
  • Lot Recall: requires bidirectional traceability — know which lots went to which orders AND which orders contain a recalled lot.
  • All three patterns need a dedicated lot/batch table; WooCommerce core does not provide one.
FIFO
First In, First Out — oldest stock leaves first
FEFO
First Expired, First Out — closest expiry leaves first
21 days
FDA-mandated recall notification window for most food categories (21 CFR Part 7)
100%
Lot traceability required — every unit sold must be traceable to its source lot

WooCommerce doesn’t know what a lot is. It tracks product quantities as integers, not traceable batches. For stores selling perishables, supplements, cosmetics, or any regulated product, that’s a compliance gap — and a recall liability. Here are three concrete patterns to fill it.

The Data Model All Three Patterns Share

Before choosing FIFO, FEFO, or recall-ready architecture, you need a lot table. This is a custom WooCommerce / WordPress table (or a NetSuite custom record if NetSuite is your inventory source of truth):

Lot Table Schema
  wp_sx_lots
  ──────────────────────────────────────────────
  lot_id          INT          Primary key
  product_id      INT          WooCommerce product/variation ID
  lot_number      VARCHAR(64)  Supplier batch number
  expiry_date     DATE         Null allowed for non-expiring goods
  received_date   DATE         When stock arrived in warehouse
  qty_received    INT          Units received
  qty_allocated   INT          Units reserved for open orders
  qty_shipped     INT          Units already dispatched
  qty_available   INT (computed) qty_received - qty_allocated - qty_shipped
  supplier_id     INT          FK to supplier record
  notes           TEXT         Recall memo, cert of analysis link
  created_at      DATETIME
  ──────────────────────────────────────────────

A second table links lots to order line items for recall traceability:

Order–Lot Mapping Table
  wp_sx_order_lots
  ──────────────────────────────────────
  id              INT
  order_id        INT      WC order ID
  order_item_id   INT      WC order item ID
  lot_id          INT      FK to wp_sx_lots
  qty_from_lot    INT
  shipped_date    DATE
  ──────────────────────────────────────

Pattern 1: FIFO Allocation

FIFO picks the lot with the earliest received_date that has available quantity. It’s the simplest pattern and is appropriate when you don’t track expiry dates — general merchandise, electronics accessories, non-perishable goods.

1
On order placement

Query wp_sx_lots for all lots of the ordered product with qty_available > 0, ordered by received_date ASC.

2
Allocate from oldest first

Walk the result set and allocate from each lot until the ordered quantity is met. A single order may span multiple lots.

3
Write allocation records

Insert one row per lot into wp_sx_order_lots and increment qty_allocated on each used lot. Do this in a DB transaction — partial allocations cause phantom stock.

4
On shipment

Move qty_allocated to qty_shipped, write shipped_date to the order-lot row. This seals the trace record.

Pattern 2: FEFO Allocation

FEFO picks the lot with the earliest expiry_date — not the earliest receipt date. This is the correct pattern for food, supplements, cosmetics, and pharma. The goal is to ship the soonest-to-expire stock before it becomes unsellable or a disposal cost.

FEFO vs FIFO can give opposite results:

A lot received three months ago with a 2-year expiry should be shipped AFTER a lot received last week with a 6-month expiry. FIFO would pick the wrong lot. For any product with a defined shelf life, FEFO is the only legally defensible picking rule.

The query change from FIFO to FEFO is minimal — just change the ORDER BY:

FEFO Allocation Query
  -- FIFO:
  SELECT * FROM wp_sx_lots
  WHERE product_id = %d AND qty_available > 0
  ORDER BY received_date ASC;

  -- FEFO:
  SELECT * FROM wp_sx_lots
  WHERE product_id = %d AND qty_available > 0
    AND (expiry_date IS NULL OR expiry_date > CURDATE())
  ORDER BY expiry_date ASC, received_date ASC;
  --                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  -- Secondary sort by received_date breaks ties where
  -- multiple lots share the same expiry date.
Near-expiry automation:

Run a daily cron that finds lots where expiry_date <= CURDATE() + INTERVAL 30 DAY and qty_available > 0. Auto-apply a discount code to those products, or flag them for manual review. Selling at 20% discount beats writing off expired stock.

Pattern 3: Lot Recall Workflow

A recall means you need to answer two questions instantly: (a) which customers received a specific lot number, and (b) which lots are currently in open orders. The wp_sx_order_lots mapping table makes both queries trivial.

Recall Question Query Output
Who received lot X? JOIN order_lots on lot_id WHERE lot_number = ‘LOT-2026-A47’ Order IDs → customer emails for notification
Is lot X in open orders? Same join, filter order_status IN (‘processing’,’on-hold’) Orders to cancel or hold for inspection
How much of lot X is still in warehouse? SELECT qty_available FROM wp_sx_lots WHERE lot_number = ‘LOT-2026-A47’ Units to quarantine physically
Which lots of product Y are at risk? Supplier join on supplier_id, date_received range Adjacent lots from same supplier batch

A recall workflow that takes hours to run manually (searching orders, emailing customers one by one) can be reduced to minutes with these tables in place. The regulatory clock starts the moment you identify the issue — every hour you spend querying data is an hour you’re not notifying affected customers.

Sources & Further Reading

References

  1. FDA: Recalls, Market Withdrawals, & Safety AlertsU.S. Food & Drug Administration — regulatory framework for product recalls including notification timelines.
  2. 21 CFR Part 7 — Enforcement PolicyU.S. Electronic Code of Federal Regulations — FDA recall classification and procedures.
  3. WooCommerce Database DescriptionWooCommerce.com — schema reference for extending WC with custom tables.
  4. NetSuite Inventory Lot and Serial TrackingOracle NetSuite Help — native lot/serial number tracking features in NetSuite Inventory.
  5. ISO 22000:2018 — Food Safety ManagementISO — traceability requirements for food supply chain management.

Frequently asked questions

What is the difference between FIFO and FEFO?

FIFO allocates the oldest stock first; FEFO allocates the earliest expiry first. For perishable goods, FEFO is usually the correct default.

What data model do these patterns need?

All three share one model: stock tracked at batch level with received and expiry dates, not a single quantity per product.

How does lot recall work in WooCommerce?

A recall workflow needs batch-level traceability from goods-in through to the orders that shipped, so affected customers can be identified precisely.

Related guides

Discussion

Leave a Reply

Your email address will not be published. Required fields are marked *


Ship it

Need this in your stack?

We build, integrate, and ship — no calls, just delivery.

Start a project →