spree-promotions
Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.
Best use case
spree-promotions is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.
Teams using spree-promotions should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/spree-promotions/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How spree-promotions Compares
| Feature / Agent | spree-promotions | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Spree Promotions
## Before writing code
**Fetch live docs**:
1. Fetch https://spreecommerce.org/docs/developer/core-concepts/promotions for the canonical model.
2. Inspect the live `Spree::PromotionRule` / `Spree::PromotionAction` source — bundled rule/action classes change between minors.
3. Check the v5.1 announcement for the advanced rule-based engine introduced there.
4. For custom calculators, review live examples in the `app/models/spree/calculator/` directory of the `spree` gem.
5. Check the latest release notes for any promotions changes (v5.0 added coupon batch CSV export).
## Conceptual Architecture
### The Four Building Blocks
```
Promotion
├── PromotionRule[] (must-match criteria)
├── PromotionAction[] (what to do on match)
└── CouponCode[] (optional codes that trigger this promotion)
↓
creates
↓
Adjustment (attached to Order, LineItem, or Shipment)
```
### Rule Match Policy
A `Promotion` has a `match_policy` of either:
- **`all`** — every rule must match
- **`any`** — at least one rule must match
### Bundled Rules (verify against live source)
| Rule | Matches |
|------|---------|
| `Spree::Promotion::Rules::FirstOrder` | User's first order |
| `Spree::Promotion::Rules::ItemTotal` | Cart total ≥ N |
| `Spree::Promotion::Rules::Product` | Specific product(s) in cart |
| `Spree::Promotion::Rules::Taxon` | Product in specific taxon |
| `Spree::Promotion::Rules::User` | Specific user |
| `Spree::Promotion::Rules::UserLoggedIn` | Not a guest |
| `Spree::Promotion::Rules::OneUsePerUser` | User hasn't used promo before |
| `Spree::Promotion::Rules::Country` | Ship-to country in set |
| `Spree::Promotion::Rules::Currency` | Order in specific currency |
| `Spree::Promotion::Rules::OptionValue` | Specific OptionValue selected |
| `Spree::Promotion::Rules::CustomerGroup` | Customer in group |
### Bundled Actions
| Action | Effect |
|--------|--------|
| `Spree::Promotion::Actions::CreateAdjustment` | Order-level discount |
| `Spree::Promotion::Actions::CreateItemAdjustments` | Per-line-item discount |
| `Spree::Promotion::Actions::FreeShipping` | Zero out shipping cost |
| `Spree::Promotion::Actions::CreateLineItems` | Auto-add a gift line item |
### Calculators
Each action uses a `Calculator` to compute its dollar amount:
| Calculator | Math |
|------------|------|
| `Spree::Calculator::FlatPercentItemTotal` | % of order subtotal |
| `Spree::Calculator::FlatRate` | Fixed amount |
| `Spree::Calculator::FlexiRate` | Per-quantity tier |
| `Spree::Calculator::PercentOnLineItem` | % of line item |
| `Spree::Calculator::TieredPercent` | % based on cart total tier |
| `Spree::Calculator::DistributedAmount` | Fixed amount split proportionally |
Calculators are polymorphic — also used by Shipping and Tax.
### Coupon Codes
A `Promotion` can have:
- **No coupon code** (auto-applied if rules match)
- **One code** (single shared coupon)
- **Many codes** (coupon batch — CSV export added in v5.0)
### Single-Use, Limit, and Expiry
`Promotion` has:
- `usage_limit` — global cap
- `per_user_limit` — per-user cap (in addition to OneUsePerUser rule)
- `starts_at` / `expires_at` — windowed availability
### Advanced Rule Engine (v5.1+)
v5.1 added composable rule expressions — multiple rules with grouped boolean logic rather than the flat all/any. Verify the live UI and API surface for the current capability.
## Implementation Guidance
### Creating a Promotion Programmatically
```ruby
promo = Spree::Promotion.create!(
name: 'Welcome 10% off',
code: 'WELCOME10',
match_policy: 'all',
starts_at: Time.current,
expires_at: 30.days.from_now,
usage_limit: 1000
)
# Rule: first order
promo.promotion_rules.create!(
type: 'Spree::Promotion::Rules::FirstOrder'
)
# Action: 10% off the whole order
action = promo.promotion_actions.create!(
type: 'Spree::Promotion::Actions::CreateAdjustment'
)
action.calculator = Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)
action.save!
```
### Applying a Coupon in Code
```ruby
# Storefront flow
Spree::PromotionHandler::Coupon.new(order).apply
# Returns a status: successful / failed / not-found
```
(Verify the exact class name — Spree has refactored coupon handlers several times.)
### Auto-Apply Promotions
For promos with no coupon code, run the auto-apply handler on cart/order updates:
```ruby
Spree::PromotionHandler::Cart.new(order).activate
```
This is wired into the order updater pipeline by default.
### Custom Rule
```ruby
# app/models/my_app/promotion/rules/loyalty_tier.rb
class MyApp::Promotion::Rules::LoyaltyTier < Spree::PromotionRule
preference :tier, :string, default: 'gold'
def applicable?(promotionable)
promotionable.is_a?(Spree::Order)
end
def eligible?(order, options = {})
order.user&.loyalty_tier == preferred_tier
end
end
# Register in an initializer
Spree::Promotion::Rules.register(MyApp::Promotion::Rules::LoyaltyTier)
```
Verify the registration API in the current release — the registry pattern occasionally changes.
### Custom Action
```ruby
class MyApp::Promotion::Actions::FreeGift < Spree::PromotionAction
def perform(payload = {})
order = payload[:order]
order.line_items.create!(variant: gift_variant, quantity: 1, price: 0)
end
end
```
### Custom Calculator
```ruby
class MyApp::Calculator::WeekendDiscount < Spree::Calculator
preference :weekend_percent, :decimal, default: 15
def self.description
'Weekend Discount'
end
def compute(object)
return 0 unless [0, 6].include?(Date.current.wday)
object.amount * (preferred_weekend_percent / 100.0) * -1
end
end
# Register
Rails.application.config.spree.calculators.promotion_actions.create_adjustment << MyApp::Calculator::WeekendDiscount
```
### Coupon Batch Import
v5.0+ ships CSV import for coupon batches:
- Admin → Promotions → batch → Generate codes / Import CSV
- Each row creates a `CouponCode` record tied to the promotion
### Debugging Promotion Not Applying
1. Check `Order#promotions.eligible?(order)` for each promotion.
2. Inspect each rule's `eligible?` method return value.
3. Verify match_policy: `any` vs `all`.
4. Check expiry: `promo.expires_at > Time.current && promo.starts_at < Time.current`.
5. Check usage limits: `promo.usage_count < promo.usage_limit`.
6. Look at `Adjustment.where(source: action)` to see if it was created but later canceled.
### Common Pitfalls
- **Forgetting calculator preferences** — actions need a calculator with valid preferences or compute returns nil.
- **Custom rule not registered** — Spree doesn't auto-discover; register explicitly.
- **Coupon code typo in admin** — codes are case-sensitive (verify against current behavior).
- **Adjustments lingering after eligibility lost** — recompute via `Spree::OrderUpdater` to drop stale adjustments.
- **Stacking promotions** — by default, multiple eligible promos all apply. To enforce mutual exclusion, set Promotion#exclusive or use match_policy creatively.
- **Free shipping action with no shipping** — silent no-op.
Always verify the rule/action/calculator class registry against the live source — the registration mechanism varies.Related Skills
spree-upgrades
Upgrade a Spree application — version-to-version migration paths (v4 → v5 is a major rewrite; v5.x → v5.y are simpler), Gemfile pinning strategy, decorator brittleness across versions, deprecated gems to remove (`spree_auth_devise`, `deface`, `spree_backend`), API v2 → v3 migration via `spree_legacy_api_v2`, admin Bootstrap → Tailwind transition, the upgrade guide workflow, and rollback strategy. Use when planning a Spree upgrade or auditing technical debt blocking one.
spree-typescript-sdk
Build storefronts and integrations using `@spree/sdk` — the official TypeScript SDK for Spree's API v3 (v5.4+). Covers installation, the resource-builder pattern (`spree.products.list`, `spree.checkout.create`, etc.), Zod schema validation, server-only auth (httpOnly JWTs, never exposing API keys), error handling, typing customizations, and pinning SDK versions to backend Spree releases. Use when integrating Spree into a Next.js / Node.js / TypeScript project.
spree-testing
Test Spree applications and extensions with RSpec — `spree_dev_tools` gem (v5.2+) for factories and helpers, FactoryBot patterns (prefer `build` over `create`), Capybara feature specs, controller/request specs, testing decorators and subscribers, dummy app for extension testing, system specs for the Hotwire admin, and CI patterns. Use when writing Spree tests, setting up CI, or refactoring slow specs.
spree-shipping-fulfillment
Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.
spree-setup
Bootstrap a new Spree project — `create-spree-app` CLI (v5.2+), `spree-starter` Rails backend, the Next.js storefront repo, `bin/rails g spree:install`, sample data, Docker Compose, and the PostgreSQL + Redis + Sidekiq prerequisites. Use when starting a new Spree project from scratch or onboarding an existing repo.
spree-security
Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.
spree-performance
Profile and optimize a Spree application — N+1 queries with bullet/scout, database indexing strategy for Spree's polymorphic associations, Rails fragment + Russian doll caching, ActiveStorage variant pre-generation, Sidekiq queue tuning, MeiliSearch vs Postgres FTS tradeoffs, Puma worker/thread sizing, CDN strategy for catalog pages, asset precompile time, and load testing. Use when Spree is slow, the database is hot, or you're preparing for a traffic spike (Black Friday, launch).
spree-payments
Integrate payment gateways with Spree — PaymentMethod model, the v5.4+ PaymentSession provider-agnostic checkout flow, Stripe via `spree_stripe` (Apple/Google Pay, Link, Connect for marketplaces), Adyen via `spree_adyen`, PayPal via `spree_paypal_checkout`, StoreCredit / GiftCard as payment methods, refunds, payment state machine, and authoring a custom gateway. Use when wiring a payment integration, handling webhooks from a gateway, or debugging payment-state issues.
spree-multi-store
Configure Spree for multi-store and multi-region commerce — one Rails install running many `Store` records, the v5.4+ `Market` model (currency + locale + payment methods + shipping per region), what's shared vs per-store (products+inventory+customers shared; orders+payments+themes per-store), the Marketplace module (Enterprise — vendors/commission/payouts via Stripe Connect), and the Multi-tenant SaaS model. Use when planning a multi-brand or multi-region Spree deployment.
spree-legacy-api-v2
Work with Spree's legacy v2 APIs — JSON:API-style Storefront API at `/api/v2/storefront/*` and Platform API at `/api/v2/platform/*`, Doorkeeper OAuth2 (password grant for storefront, client_credentials + admin scope for platform), the `spree_legacy_api_v2` gem (required in v5+ for backward compatibility), and migration patterns to API v3. Use when maintaining a v2 client during a migration window, integrating an older partner system, or deciding when to cut over to v3.
spree-i18n
Localize a Spree application — the `spree_i18n` gem with 60+ locale packs, the v5.4+ Translations Center for product/CMS content (CSV import/export), Rails i18n basics applied to Spree (translation files, locale switching, pluralization, interpolation), per-Market locale routing in the headless storefront, RTL languages, and translating extensions. Use when localizing a Spree store, adding a new locale, or building i18n-aware extensions.
spree-headless-storefront
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.