Skip to main content

Overview

Spree’s promotion system is built around two extension points: Rules (eligibility conditions) and Actions (what happens when a promotion applies). While Spree ships with a comprehensive set of built-in rules and actions, you can create custom ones for business-specific logic. This guide covers:
  • Creating a custom promotion rule with admin UI
  • Creating a custom promotion action with a calculator
  • Understanding the eligible?, actionable?, and perform contracts
Before starting, make sure you understand how promotions work in Spree.

Custom Promotion Rules

Rules determine whether a promotion is eligible for a given order. Each rule implements eligible? which returns true or false.

Step 1: Create the Rule Class

Create a new class inheriting from Spree::PromotionRule:
app/models/spree/promotion/rules/minimum_quantity.rb

Key Methods to Implement

The options hash passed to eligible? can include :user, :email, and other context from the checkout flow.

Using Preferences

Rules use Spree’s preference system for configuration. Each preference creates getter/setter methods automatically:
Available types: :string, :integer, :decimal, :boolean, :array.

Step 2: Register the Rule

Add your rule to the promotion configuration so it appears in the admin panel:
config/initializers/spree.rb

Step 3: Create the Admin Partial

Create a form partial so admins can configure the rule’s preferences. The partial name must match the rule class name in underscore format:
app/views/spree/admin/promotions/rules/_minimum_quantity.html.erb

Step 4: Add Translations

config/locales/en.yml

Step 5: Restart and Test

After restarting your application, the new rule will be available in Admin > Promotions when adding rules to a promotion.

Example: Rule with actionable?

When your rule targets specific line items (not the whole order), implement actionable? so that actions like CreateItemAdjustments only discount matching items:
app/models/spree/promotion/rules/brand.rb

Custom Promotion Actions

Actions define what happens when a promotion is applied. Most actions create adjustments on orders or line items.

Step 1: Create the Action Class

Discount Action (with Calculator)

For actions that create monetary adjustments, include Spree::CalculatedAdjustments and Spree::AdjustmentSource:
app/models/spree/promotion/actions/tiered_discount.rb

Non-Discount Action

For actions that don’t create adjustments (e.g., awarding points, sending notifications):
app/models/spree/promotion/actions/add_loyalty_points.rb

Key Methods to Implement

Available Helper Methods

When you include Spree::AdjustmentSource, you get:
When you include Spree::CalculatedAdjustments, you get:

Step 2: Register the Action

config/initializers/spree.rb

Step 3: Add Translations

config/locales/en.yml

Step 4: Restart and Test

After restarting, the new action will be available in Admin > Promotions when adding actions to a promotion.

How Rules and Actions Work Together

Understanding how Spree evaluates promotions helps you build better custom rules and actions: Key points:
  • match_policy: 'all' means every rule must return eligible? == true
  • match_policy: 'any' means at least one rule must return eligible? == true
  • For item-level actions (CreateItemAdjustments), actionable?(line_item) on each rule filters which line items get the discount
  • When multiple promotions compete, Spree picks the best one (largest discount) and marks others as ineligible

Testing Custom Rules and Actions

spec/models/spree/promotion/rules/minimum_quantity_spec.rb
spec/models/spree/promotion/actions/tiered_discount_spec.rb
  • Promotions - Promotion architecture and built-in rules/actions
  • Calculators - Available calculator types for promotion actions
  • Adjustments - How adjustments work on orders and line items
  • Events - Subscribe to promotion events