Overview
Every order, return, exchange, claim, stock transfer, import and export carries a document number — the short, human-readable reference a merchant reads out on a support call and a customer quotes in an email. Orders look likeR1001, returns like RET1001.
Numbers are separate from IDs. Every record also has a prefixed ID (order_86Rf07xd4z) which is what the API uses and what you should reference in code. The number exists purely so people can read, say and type it.
There are two ways to change it. Merchants can reshape order numbers from the dashboard without any code. Developers can replace the generator for any document type when the settings are not enough.
Merchant settings
Settings → Store → Order numbers controls four things:
Sequential numbering counts up:
R1001, R1002, R1003. Random numbering produces nine unpredictable digits. The trade-off is worth stating to merchants: sequential numbers are far easier to read back over the phone, but a customer who orders twice can see how many orders you took in between. Random numbers reveal nothing about that, at the cost of legibility.
Two rules apply to every change:
- Changes affect future numbers only. Numbers already issued are permanent — they are printed on invoices, quoted in emails, and stored at payment gateways.
- The starting value only applies before the first order. Set it to
10001on a new store and the first order isR10001, the secondR10002. Once the counter has issued a number the field is locked in the dashboard, because raising it then would do nothing — the counter keeps going from where it is.
INV with suffix -EU and a start of 5001 gives INV5001-EU, and existing orders keep the numbers they were issued.
Sequential numbering is mostly gapless, not guaranteed gapless — an abandoned checkout or a rare collision consumes a value. Do not rely on it for legal invoice numbering, which in most jurisdictions requires a provably unbroken sequence.
Custom generators
When the settings are not enough — you need the year in the number, a per-warehouse prefix, or a format your ERP already expects — register a generator class. A generator answers one question: what should this record’s number be? Uniqueness is handled for you, so a generator only has to propose.config/initializers/spree.rb:
NYC-2026-0042; every other document type keeps following the store’s settings.
The resource key matches the model name, underscored: :order, :return, :exchange, :claim, :stock_transfer, :import, :export. Register the same class under several keys to share one format across document types.
A registered generator wins over the store’s format setting — that is the point of registering one. If you want merchants to keep some control, read their settings yourself through prefix_for and suffix_for (below).
Spree.number_generators.delete(:order) removes the registration and hands numbering back to the store settings.
What a generator can read
The record is passed togenerate, so anything reachable from it is available:
record.number_store— the store whose settings apply, wherever it lives on the modelrecord.class.number_prefix— the model’s built-in prefix (R,RET, …)- Any attribute of the record itself
Spree::NumberGenerators::Base also gives you prefix_for(record) and suffix_for(record), which return the merchant’s configured values for orders and the code-level prefix for everything else. Use them when you want to extend the merchant’s choice rather than override it.
Sequential counters
If your generator needs its own counter, use the same one the built-in sequential generator uses rather than deriving a maximum from existing rows. This generator combines both — the merchant’s prefix, a year, and a zero-padded counter:R-2026-00001; a merchant who changes their prefix to ACME gets ACME-2026-00001 without you touching the class.
Deriving the next value by parsing existing numbers looks simpler but breaks in three ways: legacy numbers from before your format existed do not parse, string columns sort R999 above R1000, and two concurrent checkouts read the same maximum and produce the same number. The counter is locked for the increment, so it hands out distinct values under load.
Adding numbers to your own model
Models you add can carry document numbers too:number column with a unique index:
store, define number_store so the generator knows whose settings to read:
Fulfillments and payments
Fulfillments and payments do not have their own numbers. Theirs are derived from the order they belong to —R1001-F1 for the first parcel, R1001-P1 for the first payment — so they group visibly with their order and follow whatever format the merchant chose.
This is not configurable and there is no generator to register. If you need a different shape, override number on the model.
Upgrading from Spree 5.x
Two changes matter. Numbers are sequential by default. Existing numbers are never rewritten, and new documents get sequential numbers starting at 1001. To keep the old behavior, set the store’s numbering format to Random in Settings → Store → Order numbers.Spree::Core::NumberGenerator is deprecated. Models using it still work and log a deprecation warning, but will stop working in a future release. Replace it with the concern:
length: and letters: options are gone — they described the random format, which is now the generator’s business rather than the model’s. If you were relying on a specific length, register a custom generator.
