Skip to main content

Overview

Spree’s pricing system supports both simple single-currency pricing and advanced multi-currency, rule-based pricing through Price Lists. Every Variant can have multiple prices — a base price per currency, plus additional prices from Price Lists that apply conditionally based on rules like geography, customer segment, or quantity.

Prices

Each variant has one or more Price records — one per currency. The API automatically returns the correct price based on the current currency and Market context. See monetary amounts for how money fields (decimal, cents, and display string) are formatted across the API.
If a product doesn’t have a price in the selected currency, it won’t appear in Store API responses by default.

Fetching Prices via API

The Store API returns the resolved price (including Price List rules) for the current currency and market context:

Setting Prices via the Admin API

Base prices are set per variant per currency. Use the Admin API — via the Admin SDK — to set them individually or in bulk (bulk_upsert matches on variant + currency):
?expand=variants.prices returns every price a variant has, Price List amounts and quantity breaks included; each row carries its price_list_id, which is null for a base price. Writing prices back on a variant sets base prices only — a currency left out is removed — so filter the rows you read before sending them back. To write what a Price List charges, use /prices.

Price Lists

Price Lists allow you to create different pricing strategies based on various conditions. This enables advanced pricing scenarios like:
  • Market-based pricing — different prices for different Markets (e.g., North America vs Europe)
  • Regional pricing — different prices for different geographic zones
  • Wholesale/B2B pricing — special prices for business customers
  • Volume discounts — tiered pricing based on quantity purchased
  • Promotional pricing — time-limited special offers
  • VIP customer pricing — exclusive prices for specific customers

How Price Lists Work

When a customer views a product, Spree’s pricing resolver determines which price to use:
  1. Price List priority — Price Lists are ordered by position; higher priority lists are checked first
  2. Status — only active or scheduled Price Lists are considered
  3. Date range — the current time must fall within starts_at and ends_at (if set)
  4. Price Rules — all configured rules must match (or any, depending on match_policy)
If no applicable Price List is found, the base price is used.

Price List Attributes

Creating a Price List via the Admin API

Rules and per-variant price overrides ride on the create/update payload. Each rule is a { type, preferences } draft (see Price Rules below):

Price Rules

Price Rules define conditions that must be met for a Price List to apply. Rules describe the context of a purchase — where, through what, how much: Who gets a price list is not a rule — it’s a Catalog: attach the list to a catalog assigned to the customer groups or companies it’s for.
The User Rule and Customer Group Rule kinds still work and are not going away, but they’re no longer offered when adding a rule — targeting an audience through a catalog is the one way to do it now. Nothing needs migrating; rules you already have keep matching exactly as before.

Market Rule

Regional pricing. Applies the Price List when the customer is in one of the specified markets. Example: Price a product at $29.99 in North America and €24.99 in Europe, rather than relying on exchange rate conversion.

Channel Rule

Applies when the customer is buying through a particular sales channel — a discount that exists only in the mobile app, or a price that only applies at a retail till. For new setups, prefer giving the channel a default Catalog that owns the price list — that is where channel-wide commercial terms live going forward.

Volume Rule

Applies based on quantity purchased. Supports min_quantity and max_quantity to create tiered pricing:
Custom Price Rules can be created for specialized pricing logic. See the Customization Quickstart for details.

Pricing Context

When resolving prices, Spree considers the full context of the request: The Store API automatically builds this context from the request headers (X-Spree-Currency, X-Spree-Country) and authentication state. You don’t need to construct it manually — just make API requests and the correct price is resolved.
Price resolution runs per request — there is no in-process caching of resolved prices. The Store API instead relies on HTTP/CDN caching, with Vary headers keyed on every request header that changes the price — X-Spree-Country (market), X-Spree-Currency, X-Spree-Locale, X-Spree-Channel, the publishable key and Authorization — so each combination is cached separately at the edge. See HTTP caching.

Time-Based Pricing

Price Lists support scheduling through starts_at and ends_at attributes. Scheduled Price Lists automatically become applicable when the current time falls within their date range — no manual activation needed. Example: A Black Friday sale Price List with starts_at: 2025-11-28 00:00 and ends_at: 2025-11-28 23:59 will automatically activate and deactivate.

Managing Price Lists

Price Lists are managed in the Admin Panel under Products → Price Lists, or via the Admin API. Each Price List contains prices for specific variants and currencies. Products can be added to a Price List, and individual variant prices set within it.

Price History (EU Omnibus Directive)

Spree automatically records price changes for EU Omnibus Directive compliance. When a product goes on sale, EU regulations require displaying the lowest price in the preceding 30 days alongside the discounted price.

How It Works

Every time a base price amount changes, a PriceHistory record is created automatically. Price list prices are not tracked — only the base price visible to all customers.

Fetching Prior Price via API

The prior price is available as an expandable field on product and variant endpoints:
The response includes a prior_price object when expanded:
Response

Configuration

Price history tracking is enabled by default. To disable it (e.g., for non-EU stores):
Retention defaults to 30 days, set per store with the price_history_retention_days preference. A Rake task prunes anything older:

Seeding Existing Prices

After enabling price history on an existing store, seed the current prices as a baseline:
  • Products — Products, Variants, and base prices
  • Store SDK: Products — Fetching product and variant prices with the Store SDK
  • Markets — Geographic regions with currency and locale
  • Taxes — Tax categories, tax rates, and zones
  • Promotions — Discount-based pricing via promotion rules