> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Pricing

> Prices, Price Lists, Price Rules, and the Pricing Context — Spree's flexible pricing engine for regional, wholesale, volume, and market-based pricing.

export const Since = ({version, from}) => {
  const knownPrevious = {
    '5.0': '4.10',
    '6.0': '5.4'
  };
  const previous = (from ?? knownPrevious[version]) ?? (() => {
    const [major, minor] = version.split('.').map(Number);
    if (Number.isNaN(major) || Number.isNaN(minor) || minor < 1) {
      throw new Error(`<Since version="${version}" />: cannot derive previous version automatically. ` + `Pass an explicit "from" prop, e.g. <Since version="${version}" from="X.Y" />.`);
    }
    return `${major}.${minor - 1}`;
  })();
  return <Tooltip tip={`Available since Spree ${version}+.`} cta="Upgrade instructions" href={`/developer/upgrades/${previous}-to-${version}`}>
      <Badge icon="lock">Spree {version}+</Badge>
    </Tooltip>;
};

## Overview

Spree's pricing system supports both simple single-currency pricing and advanced multi-currency, rule-based pricing through Price Lists. Every [Variant](/developer/core-concepts/products#variants) 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](/developer/core-concepts/markets) context.

| Attribute           | Description                                      | Example  |
| ------------------- | ------------------------------------------------ | -------- |
| `amount`            | Current selling price                            | `99.90`  |
| `compare_at_amount` | Original/compare price for strikethrough display | `129.90` |
| `currency`          | ISO 4217 currency code                           | `USD`    |

<Warning>
  If a product doesn't have a price in the selected currency, it won't appear in Store API responses by default.
</Warning>

### Fetching Prices via API

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

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  // Product price is included by default
  const product = await client.products.get('spree-tote')
  console.log(product.price)          // "15.99" — resolved for current currency
  console.log(product.original_price) // "19.99" — compare-at price (if set)

  // Each variant has its own price
  const detailed = await client.products.get('spree-tote', {
    expand: ['variants'],
  })
  detailed.variants?.forEach(variant => {
    console.log(variant.price)          // "15.99"
    console.log(variant.original_price) // "19.99"
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  # Product price is always in the response
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
    -H 'Authorization: Bearer pk_xxx'

  # With variants
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=variants' \
    -H 'Authorization: Bearer pk_xxx'
  ```
</CodeGroup>

## Price Lists  <Since version="5.3" />

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](/developer/core-concepts/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

| Attribute      | Description                                                                    |
| -------------- | ------------------------------------------------------------------------------ |
| `name`         | Human-readable name for the Price List                                         |
| `status`       | `draft`, `active`, `scheduled`, or `inactive`                                  |
| `starts_at`    | Optional start date when the Price List becomes applicable                     |
| `ends_at`      | Optional end date when the Price List stops being applicable                   |
| `match_policy` | How rules are evaluated: `all` (every rule must match) or `any` (at least one) |
| `position`     | Priority order (lower numbers = higher priority)                               |

## Price Rules <Since version="5.3" />

Price Rules define conditions that must be met for a Price List to apply. Spree includes five built-in rule types:

| Rule                    | Description                                                             | Use Case                          |
| ----------------------- | ----------------------------------------------------------------------- | --------------------------------- |
| **Market Rule**         | Matches based on the current [Market](/developer/core-concepts/markets) | Regional pricing across markets   |
| **Zone Rule**           | Matches based on the customer's geographic zone                         | Country or state-level pricing    |
| **User Rule**           | Matches specific customer accounts                                      | VIP customers, wholesale accounts |
| **Customer Group Rule** | Matches members of customer groups                                      | Loyalty tiers, membership pricing |
| **Volume Rule**         | Matches based on quantity purchased                                     | Bulk discounts, tiered pricing    |

### Market Rule <Since version="5.4" />

The recommended approach for regional pricing when using Markets. 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.

### Zone Rule

Applies based on the customer's geographic zone. Useful for regional or country-specific pricing when not using Markets.

### User Rule

Limits the Price List to specific customer accounts. Useful for VIP customers, employee pricing, or wholesale accounts.

### Customer Group Rule

Applies to members of specific customer groups. Useful for wholesale tiers, loyalty programs, or membership-based pricing.

### Volume Rule

Applies based on quantity purchased. Supports `min_quantity` and `max_quantity` to create tiered pricing:

| Tier        | Quantity | Price   |
| ----------- | -------- | ------- |
| Base        | 1–9      | \$10.00 |
| Bulk Tier 1 | 10–49    | \$8.50  |
| Bulk Tier 2 | 50+      | \$7.00  |

<Info>
  Custom Price Rules can be created for specialized pricing logic. See the [Customization Quickstart](/developer/customization/quickstart) for details.
</Info>

## Pricing Context <Since version="5.3" />

When resolving prices, Spree considers the full context of the request:

| Context  | Source                   | Description                                                           |
| -------- | ------------------------ | --------------------------------------------------------------------- |
| Currency | Market or request header | The currency to price in                                              |
| Market   | Customer's country       | The [Market](/developer/core-concepts/markets) for market-based rules |
| Zone     | Customer's address       | The geographic zone for zone-based rules                              |
| Customer | JWT authentication       | The logged-in customer for user-based rules                           |
| Quantity | Cart line item           | The quantity for volume-based rules                                   |
| Date     | Current time             | For time-based Price List scheduling                                  |

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.

<Info>
  Price resolution results are cached for 15 minutes. Different context combinations are cached separately.
</Info>

## 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) <Since version="5.4" />

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:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const product = await client.products.get('spree-tote', {
    expand: ['prior_price'],
  })

  if (product.prior_price) {
    console.log(product.prior_price.amount)         // "9.99"
    console.log(product.prior_price.display_amount)  // "$9.99"
    console.log(product.prior_price.currency)        // "USD"
    console.log(product.prior_price.recorded_at)     // "2026-03-10T..."
  }
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=prior_price' \
    -H 'X-Spree-API-Key: pk_xxx'
  ```
</CodeGroup>

The response includes a `prior_price` object when expanded:

```json theme={"theme":"night-owl"}
{
  "id": "prod_xxx",
  "name": "Spree Tote",
  "price": { "amount": "15.99", "currency": "USD", "display_amount": "$15.99" },
  "prior_price": {
    "amount": "9.99",
    "amount_in_cents": 999,
    "currency": "USD",
    "display_amount": "$9.99",
    "recorded_at": "2026-03-10T14:30:00Z"
  }
}
```

### Configuration

Price history tracking is enabled by default. To disable it (e.g., for non-EU stores):

```ruby theme={"theme":"night-owl"}
# config/initializers/spree.rb
Spree.config do |config|
  config.track_price_history = false
  config.price_history_retention_days = 30 # default
end
```

Price history retention defaults to 30 days and can be configured globally. A Rake task is provided for cleanup:

```bash theme={"theme":"night-owl"}
bundle exec rake spree:price_history:prune
```

### Seeding Existing Prices

After enabling price history on an existing store, seed the current prices as a baseline:

```bash theme={"theme":"night-owl"}
bundle exec rake spree:price_history:seed
```

## Related Documentation

* [Products](/developer/core-concepts/products) — Products, Variants, and base prices
* [Markets](/developer/core-concepts/markets) — Geographic regions with currency and locale
* [Taxes](/developer/core-concepts/taxes) — Tax categories, tax rates, and zones
* [Promotions](/developer/core-concepts/promotions) — Discount-based pricing via promotion rules
