Skip to main content
All monetary values in the Admin API are canonical decimal strings (period decimal, no thousands grouping), both in responses and in request bodies. Responses write each amount with exactly its currency’s decimal places — "29.99" in USD, "1.500" in Kuwaiti dinar, "1000" in yen — and unit prices (price, amount on a price, compare_at_amount, cost_price, unit_cost) with up to four ("0.0125"). A currency’s decimal places are its decimal_places, from ISO 4217. Strings preserve decimal precision and avoid the floating-point rounding issues common with JSON numbers — critical when an admin is setting prices and costs. The format is the same regardless of locale; localized formatting is a presentation concern handled by the client (see Canonical format).

Reading

Every monetary field is returned as a decimal string, with its currency on the record or on the record it belongs to (an order’s payments and fulfillments are in the order’s currency):
The Admin and Seller APIs return amounts only, with no display_* formatted copies: format them for the person reading them, in their own language. Format and calculate with the SDK’s money helpers; without the SDK, pass the string to Intl.NumberFormat, which reads a numeric string exactly, and calculate with a decimal library rather than parseFloat. The Store API and webhook payloads still carry display_* fields, deprecated and removed in 6.1; email templates format amounts with the money filter.

Money helpers

@spree/admin-sdk and @spree/seller-sdk export helpers that work on the decimal strings directly. None of them turns an amount into a JavaScript number, so a sum or a comparison is exact.

Displaying

Calculating

Pass a currency to write the result with that currency’s decimal places. Without one, the result keeps only the decimals it needs.

Comparing and checking

The calculating and comparing helpers throw on a value that is not a canonical decimal string, such as "1,99" or "1e3", rather than guess what it means. Check input you did not get from the API with isDecimalString first.

Writing

Send amounts back as strings too, so a value reads and writes in the same type:
Only strings are accepted. A JSON number ("amount": 29.99), a localized or grouped string ("1.234,56", "1,234.56"), or more decimals than the field holds ("19.999" in USD, "100.5" in yen; unit prices allow four) is refused with 422 and the error code invalid_money_format, naming the field:
A null clears the value.

Canonical format — not localized

Amounts are canonical: a period decimal separator and no thousands grouping ("1234.56"), independent of any locale. The Admin API does not parse locale-specific formats — do not send "1.234,56" or "1,234.56". If you are taking input from a merchant in a localized format, normalize it to canonical form before sending. The Spree dashboard does this in the browser, in the number format of the person using it: an admin who writes German types 1.234,56, in any currency, and 1234.56 goes on the wire. This matches how other commerce APIs handle money (canonical decimal or minor-unit integers; localization is a presentation concern).

Affected types

This convention applies to all monetary fields across all resources, including back-office-only fields the Store API never exposes:

Amounts inside preferences

Calculators and rules carry their amounts inside a preferences object rather than as top-level fields. Each type lists its preferences with their types in preference_schema (from its …/types endpoint), and a preference of type money follows the same convention as every other amount: a decimal string, read back with the decimals of the record’s currency preference. On write, a money preference that is not a decimal string, or has more decimals than its currency, is refused with 422 and invalid_preferences, naming the field (/calculator/preferences/amount). The money preferences Spree ships: Read an extension’s money preferences from preference_schema rather than from this table. Percentage keys (percent, flat_percent, base_percent) are rates: decimal strings too ("10"), with no fixed number of decimals. Weights (minimum_weight, maximum_weight) and quantities (max_items, min_quantity) are not money and stay numbers. Promotion actions (create_adjustment, create_item_adjustments) hold their money inside a nested calculator.preferences, per the calculator rows above.

Rates and percentages

Rates and percentages are decimal strings without trailing zeros, on read and write. A field ending in _percent or _percentage holds 0–100 ("23"); a field named rate or ending in _rate holds a fraction ("0.23"). A tax rate has both: rate and rate_percent, and accepts either on write.