Overview
Product records track unique products within your store. These differ from Variants, which track the unique variations of a product. For instance, a product that’s a T-shirt would have variants denoting its different colors and sizes. Together, Products and Variants describe what is for sale.
Note that as of version 4.6, certain product fields are translatable (read more about this in Internationalization).
| Attribute | Description | Translatable |
|---|---|---|
name | Short name for the product | Yes |
description | The most elegant, poetic turn of phrase for describing your product’s benefits and features | Yes |
slug | SEO slug based on the product name that is placed into the URL for the product. We use a library called friendly_id to generate them | Yes |
status | The status of the product. Can be draft, active, archived. Defaults to draft. | No |
available_on | The first date the product becomes available for sale online in your shop | No |
discontinue_on | Date when the product will become unavailable for sale online in your shop | No |
deleted_at | The date the product is marked as deleted. We don’t remove products entirely, only soft deleting them using a library called | No |
meta_title | Optional title used for search engines instead of name | Yes |
meta_description | A description targeted at search engines for search engine optimization (SEO) | Yes |
meta_keywords | Several words and short phrases separated by commas, also targeted at search engines | Yes |
Option Types and Option Values
Option types denote the different options for a variant. A typical option type would be asize, with that option type’s values being something such as Small, Medium and Large.
Another typical option type could be a color, such as Red, Green, or Blue.
A product can be assigned many option types, but must be assigned at least one if you wish to create variants for that product
The
name and presentation fields for option types are translatable as of version 4.6.Variants
Variant records track the individual variants of a Product. Variants are of two types: master variants and normal variants.
Variant records can track some individual properties regarding a variant, such as height, width, depth, and cost price. These properties are unique to each variant, and so are different from Product Properties, which apply to all variants of that product.
| Attribute | Description | Example Value |
|---|---|---|
sku | Unique identifier for each variant | 123TSHRT-M-G |
barcode | Barcode code | “ |
barcode | A unique code that represents a variant, often used for scanning purposes. | 123456789 |
weight | The weight of the variant | 2 |
height | The height of the variant | 150 |
width | The width of the variant | 150 |
depth | The depth of the variant | 100 |
is_master | Indicates if the variant is a master variant | false |
track_inventory | Indicates if the inventory is tracked for this variant | true |
cost_price | The cost price of the variant | 5.00 |
cost_currency | The currency of the cost price | USD |
discontinue_on | Date when the variant will become unavailable for sale | 2023-12-31 18:00 |
Master Variants
Every single product has a master variant, which tracks basic information such as a count on hand, a price and a . Whenever a product is created, a master variant for that product will be created too.Logic behind is implemented in the
after_initialize callback of the Spree::Product model in a ensure_master method.Regular Variants
Variants which are not the master variant are unique based on a option type and option value combinations. For instance, you may be selling a product which is a Baseball Jersey, which comes in the sizes “Small”, “Medium” and “Large”, as well as in the colors of “Red”, “Green”. For this combination of sizes and colors, you would be able to create 9 unique variants:| SKU | Size | Color |
|---|---|---|
| SKU-S-R | Small | Red |
| SKU-S-G | Small | Green |
| SKU-M-R | Medium | Red |
| SKU-M-G | Medium | Green |
| SKU-L-R | Large | Red |
| SKU-L-G | Large | Green |
Default Variant
This all can sound complex and confusing, so we’ll simplify things for you. To get the default Variant for a product, you can call:- If a product has multiple Variants it will return the first non-master Variant based on their sort position set in the Admin Panel or Platform API.
- If there are no non-master Variants it will return the Master Variant
Images
Images can be associated to the Product (via master variant) or to the individual Variants. Product images can be fetched via:variant_images on the product:
images on the variant:
position attribute, which is an integer. By default, images are ordered from left to right.
Under the hood we use Rails Active Storage to handle image storage and manipulation. See the Images & Assets guide for more details on image processing, helpers, and best practices.
Product Properties
Product properties track individual attributes for a product that don’t apply to all products. These are typically additional information about the item. For instance, a T-Shirt may have properties representing information about the kind of material used, as well as the type of fit the shirt is. AProperty should not be confused with an OptionType, which is used when defining Variants for a product.
You can retrieve the value for a property on a Product object by calling the property method on it and passing through that property’s name:
set_property method:
Property instance with this name will be created on the fly.
As of version 4.6, product property
value fields are translatable.Prices
Price objects track a price for a particular currency and variant combination. For instance, a Variant may be available for $15 (15 USD) and €7 (7 Euro).
Price object contains 3 important attributes:
| Attribute | Description | Example Value |
|---|---|---|
amount | The current selling price of the variant in the specified currency. | 99.90 |
compare_at_amount | The recommended retail price of the variant in the specified currency. This can be used to display crossed out prices in the storefront. | 129.90 |
currency | The ISO code for the currency in which the amount and compare_at_amount are denominated. | USD |
prices to get a list of related Price objects:
prices to get a list of related Price objects:
If there’s no price set for requested currency this will return a new Price object with the currency set to the requested currency. It will not be a persisted object.
Getting amount (number)
Getting amount (number)
Getting amount (string)
Getting amount (string)
Displaying price with currency symbol
Displaying price with currency symbol
Spree behind the scenes uses Ruby Money gem with some additional tweaks.
Price Lists
Price Lists are available as of Spree 5.2.
- Regional pricing - Different prices for different geographic regions
- 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 users
How Price Lists Work
Each Price List contains a collection of prices for specific variants and currencies. When a customer views a product or adds it to their cart, Spree’s pricing resolver determines which price to use based on:- Price List priority - Price Lists are ordered by position; higher priority lists are checked first
- Status - Only
activeorscheduledPrice Lists are considered - Date range - The current time must fall within
starts_atandends_at(if set) - Price Rules - All configured rules must match (or any, depending on
match_policy)
Price List Attributes
| Attribute | Description |
|---|---|
name | Human-readable name for the Price List |
status | Current status: draft, active, scheduled, or inactive |
starts_at | Optional start date/time when the Price List becomes applicable |
ends_at | Optional end date/time when the Price List stops being applicable |
match_policy | How rules are evaluated: all (every rule must match) or any (at least one rule must match) |
position | Priority order for evaluation (lower numbers = higher priority) |
Price Rules
Price Rules define conditions that must be met for a Price List to apply. Spree includes three built-in rule types:Zone Rule
Zone Rule
Applies the Price List based on the customer’s tax/shipping zone. Useful for regional or country-specific pricing.
User Rule
User Rule
Applies the Price List to specific users. Useful for VIP customers, wholesale accounts, or B2B pricing.
Volume Rule
Volume Rule
Applies the Price List based on quantity purchased. Useful for bulk discounts or tiered pricing.
Creating Custom Price Rules
You can create custom Price Rules by subclassingSpree::PriceRule:
Pricing Context
TheSpree::Pricing::Context object carries all the information needed to resolve the correct price. It encapsulates the current pricing scenario and is used by the Spree::Pricing::Resolver to find the best applicable price.
Context Attributes
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
variant | Spree::Variant | Yes | - | The variant for which to resolve the price |
currency | String | Yes | - | ISO 4217 currency code (e.g., 'USD', 'EUR') |
store | Spree::Store | No | Spree::Current.store | The store context; Price Lists are scoped to stores |
zone | Spree::Zone | No | Spree::Current.zone | The tax/shipping zone for zone-based pricing rules (see below). Falls back to Spree::Zone.default_tax if not set. |
user | User class | No | nil | The current user; used by User Rules for customer-specific pricing |
quantity | Integer | No | nil | The quantity being purchased; used by Volume Rules for tiered pricing |
date | Time | No | Time.current | The date/time for price resolution; used to check Price List date ranges |
order | Spree::Order | No | nil | The current order; provides additional context for price resolution |
Understanding the Zone Attribute
Thezone attribute represents the geographic zone used for zone-based pricing. Zones in Spree can be based on countries or states and are typically used for tax and shipping calculations.
Where zones come from:
- During checkout, the zone is determined by the customer’s shipping or billing address
- The
order.tax_zonemethod returns the applicable tax zone based on the order’s addresses Spree::Current.zoneholds the current request’s zone (set automatically by controllers)- Falls back to
Spree::Zone.default_taxif no zone is explicitly set
Creating a Context
There are several ways to create a pricing context:From an Order (recommended)
From an Order (recommended)
The most common approach during checkout. Automatically extracts all relevant information from the order:
From Currency (simple)
From Currency (simple)
For basic price lookups when you only need currency-based pricing:
Manual Construction
Manual Construction
For full control over all pricing parameters:
Resolving the Price
Once you have a context, use theSpree::Pricing::Resolver to find the best price:
Price resolution results are cached for 15 minutes using the context’s cache key. The cache key includes all context attributes, so different scenarios are cached separately.
Getting Prices: price_for vs price_in
Spree provides two methods for fetching variant prices. Understanding when to use each is important for correct pricing behavior.
price_for (Recommended)
The price_for method resolves prices using the full Price List system. It considers all applicable Price Lists, rules, and returns the best matching price. This is the recommended method for most use cases.
price_for when:
- Displaying prices to customers in the storefront
- Calculating line item prices during checkout
- You need zone-based, user-based, or volume-based pricing
- You want prices from active Price Lists to apply
price_in (Base Prices Only)
The price_in method returns only the base price (prices without a Price List) for a given currency. It bypasses the Price List system entirely.
price_in when:
- You specifically need the base price, ignoring all Price Lists
- Managing prices in the admin interface
- Importing/exporting base pricing data
- Backward compatibility with pre-5.2 code
Comparison
| Method | Price Lists | Rules Applied | Use Case |
|---|---|---|---|
price_for | Yes | Yes | Storefront, checkout, customer-facing |
price_in | No | No | Admin, base price management |
Managing Price List Products
Products can be added to a Price List, which creates placeholder prices for all variants:Time-Based Pricing
Price Lists support scheduling throughstarts_at and ends_at attributes:
Scheduled Price Lists automatically become applicable when the current time falls within their date range. They don’t need to be manually activated.
Prototypes
A prototype is a useful way to share commonOptionType and Property combinations amongst many different products. They work as a product template to speed up the creation of new products.
For instance, if you’re creating a lot of clothing products, you may wish to maintain the “Size” and “Color” option types, as well as a “Fitting Type” property.
Taxons and Taxonomies
Taxonomies provide a simple, yet robust way of categorizing products by enabling store administrators to define as many separate structures as needed. When working with Taxonomies there are two key terms to understand:Taxonomy– a hierarchical list which is made up of individual Taxons. Each taxonomy relates to oneTaxon, which is its root node.Taxon– a single child node which exists at a given point within aTaxonomy. EachTaxoncan contain many (or no) sub / child taxons. Store administrators can define as many Taxonomies as required, and link a product to multiple Taxons from each Taxonomy.
position attribute.
Taxons use the Nested set model for their hierarchy. The
lft and rgt columns in the spree_taxons table represent the locations within the hierarchy of the item. This logic is handled by the awesome nested set gem.Classification. This model exists so that when a product is deleted, all of the links from that product to its taxons are deleted automatically. A similar action takes place when a taxon is deleted; all of the links to products are deleted automatically.
Storefront uses which will output
spree.nested_taxons_path helper method to generate full taxon URLs, which will use the taxon’s permalink, eg./t/categories/clothes/dresses.As of version 4.6, the taxon
name and description fields are translatable.
