Usage
How a number is allocated
Every number — from checkout, from a backfill, from the console — comes out of one method, holding a mutex named after the scheme:
- Take the counter for this scheme and this reset period.
- Step over anything on the skip list.
- Render the reference and check it is free, against both the orders table and the ledger.
- Write the new counter value and the ledger row in one transaction.
Commerce fires EVENT_BEFORE_COMPLETE_ORDER after it has generated its own reference and before
it saves the order, so Abacus replaces $order->reference in place. One save, no second write,
and if Abacus declines the order keeps the reference Commerce already gave it.
That fallback is the default and it is deliberate. A locked counter should not be able to fail a checkout. If you would rather the order failed than carried the wrong number, set If a number cannot be allocated to Fail the order completion.
Know what you are choosing there. Commerce wraps markAsComplete() in a mutex named after the
order but has no try/finally around the event Abacus listens on, so an exception thrown from it
escapes with that lock still held. The customer sees a failed checkout and that same order cannot
be retried until the lock lapses with the request. Use throw only where a wrong-format number
is genuinely worse than a stuck cart — reconciliation against an external accounting system, say.
Several schemes (Pro)
Schemes are tried in order and the first whose condition matches numbers the order. A scheme with no condition matches everything, so keep the catch-all last:
| Order | Scheme | Condition |
|---|---|---|
| 1 | Wholesale | Customer is in the Wholesale group |
| 2 | Big orders | Total ≥ 1000 |
| 3 | Orders | — |
The condition builder is Commerce's own, evaluated against the order as it completes — so unlike a cart-time condition it can ask about order status, payment and completion date.
A condition that throws is logged and skipped rather than allowed to take a checkout down.
Backfill (Pro)
Abacus → Backfill renumbers orders that completed before Abacus arrived. It always rehearses first: pick a window, press Preview, and read the table of now → would become before anything is written.
php craft abacus/backfill/preview --limit=500 --dateFrom=2026-01-01
php craft abacus/backfill/run --limit=500 --dateFrom=2026-01-01
php craft abacus/backfill/revert --batch=<id>
Orders are renumbered in completion order, so the sequence means what a sequence should mean. Each run is grouped under a batch ID, and each allocation records the reference the order carried before — which is what makes Put back possible.
Two things worth knowing before you run it:
- A run recomputes rather than replaying the preview. An order that completes between the rehearsal and the run shifts the sequence honestly instead of colliding with it.
- Reverting does not rewind the counter. The numbers the batch spent stay spent. Rewinding would mean reissuing a number that may already be printed on somebody's invoice, and a duplicate is a worse problem than a gap.
A backfill re-saves every order it touches so the search index follows the new reference. Anything else listening for order saves will hear about all of them, so run large backfills when that is acceptable.
Twig
{{ order.reference }} {# Abacus wrote it here — this is the number #}
{{ craft.abacus.numberForOrder(order) }} {# null if Abacus never numbered this order #}
{{ craft.abacus.allocationForOrder(order).previousReference }}
{{ craft.abacus.next('orderNumbers') }} {# what the next order would most likely get #}
{{ craft.abacus.preview('orderNumbers', 5) }}
Nothing in the Twig API spends a number.
Because Abacus writes to Commerce's own order.reference, the new number shows up everywhere the
old one did — the order index, emails, PDFs, {{ order.reference }} — without a single template
change.
Console
php craft abacus/numbers/status # every counter, and the next reference
php craft abacus/numbers/preview --scheme=orderNumbers --count=10
php craft abacus/numbers/set --scheme=orderNumbers --number=5000
php craft abacus/numbers/reclaim # Pro
php craft abacus/backfill/preview --limit=200 # writes nothing
php craft abacus/backfill/run --limit=200 # Pro
php craft abacus/backfill/batches
php craft abacus/backfill/revert --batch=<id> # Pro
abacus/numbers/set refuses to move a counter backwards, for the same reason a revert does not.