spree-extensions
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
Best use case
spree-extensions is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
Teams using spree-extensions 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-extensions/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How spree-extensions Compares
| Feature / Agent | spree-extensions | 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 Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
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 Extensions & Decorators
## Before writing code
**Fetch live docs**:
1. Fetch https://spreecommerce.org/docs/developer/customization/decorators for the modern `prepend` pattern.
2. Fetch https://spreecommerce.org/docs/developer/customization/v4/deface to confirm Deface deprecation status.
3. Inspect any official Spree extension gem (`spree_stripe`, `spree_klaviyo`) for current engine scaffolding.
4. Check the v5.2 announcement for the generator suite added then.
5. Verify the current `Spree::Dependencies` registry by reading `lib/spree/dependencies.rb` in the live gem.
## Conceptual Architecture
### What an Extension Is
A Spree extension is a **Rails engine packaged as a gem**. It can ship:
- Models / migrations / decorators
- Controllers / routes
- Views / assets / JavaScript
- Subscribers / job classes
- Initializers wiring into `Spree::Dependencies` and event bus
- Admin navigation entries
Used the same way as `spree_stripe`, `spree_klaviyo`, `spree_paypal_checkout`.
### Four Customization Surfaces (in preference order)
1. **Events + Webhooks** — react to `Spree::Event` publications via `Spree::Subscriber` or external HTTP receivers. Stable across upgrades.
2. **Dependencies** — swap services via `Spree::Dependencies.foo_service = MyService`. Spree publishes a well-defined registry of replaceable services.
3. **Admin Navigation + Partials** — declarative `Spree.admin.navigation` API + partial injection slots. No code modification.
4. **Decorators (last resort)** — `prepend` to extend a core class. Most fragile; ties you to internals.
Reach for the highest-numbered tool only when lower-numbered tools can't express what you need.
### Generating a New Extension
```bash
gem install spree_cmd # or use the bundled generator
bin/rails g spree:extension spree_my_extension
```
(Verify the current generator name and flags — `spree_cmd` may have been replaced by the in-`spree` generator suite added in v5.2.)
Produces:
```
spree_my_extension/
├── app/
│ ├── controllers/
│ ├── models/
│ ├── views/
│ ├── subscribers/ # event subscribers
│ ├── decorators/ # _decorator.rb files
│ └── assets/
├── config/
│ ├── routes.rb
│ ├── locales/
│ └── initializers/spree_my_extension.rb
├── db/migrate/ # migrations
├── lib/
│ ├── spree_my_extension/
│ │ ├── engine.rb
│ │ └── version.rb
│ └── spree_my_extension.rb
├── spec/ # RSpec test suite
├── spree_my_extension.gemspec
└── Gemfile
```
### The Engine File
```ruby
# lib/spree_my_extension/engine.rb
module SpreeMyExtension
class Engine < ::Rails::Engine
require 'spree/core'
isolate_namespace SpreeMyExtension if defined?(SpreeMyExtension)
engine_name 'spree_my_extension'
initializer 'spree_my_extension.environment', before: :load_config_initializers do |app|
# Boot-time wiring (registries, dependencies)
end
def self.activate
cache_klasses = "#{config.root}/app/**/*_decorator*.rb"
Dir.glob(cache_klasses) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
config.to_prepare(&method(:activate).to_proc)
end
end
```
The `to_prepare` hook ensures decorators reload in development.
### The Decorator Pattern
Modern Spree decorators use **`Module#prepend`**, not `class_eval` reopening. File naming convention: `app/models/spree/product_decorator.rb` (suffix `_decorator.rb`).
```ruby
# app/models/spree/product_decorator.rb
module SpreeMyExtension::ProductDecorator
def self.prepended(base)
base.has_many :promotional_videos, class_name: 'SpreeMyExtension::PromotionalVideo'
base.validates :seo_title, length: { maximum: 70 }, allow_nil: true
base.scope :featured, -> { where(featured: true) }
end
def display_name
seo_title.presence || super # `super` walks up the prepend chain
end
end
Spree::Product.prepend(SpreeMyExtension::ProductDecorator) unless Spree::Product.include?(SpreeMyExtension::ProductDecorator)
```
**Why `prepend` not `include`**: `prepend` inserts the module *above* the class in the method lookup chain — `super` walks back to the original implementation. This is what allows overriding methods.
### Decorator Generators (v5.2+)
```bash
bin/rails g spree:model_decorator Spree::Product
bin/rails g spree:controller_decorator Spree::Admin::ProductsController
```
Generates the boilerplate file with the right naming convention.
### Spree::Dependencies — Swappable Services
```ruby
# config/initializers/spree.rb
Spree::Dependencies.cart_add_item_service = MyApp::CartAddItemService
Spree::Dependencies.shipping_rate_estimator = MyApp::CustomEstimator
Spree::Dependencies.order_updater_class = MyApp::OrderUpdater
```
Common replaceable services (verify against `lib/spree/dependencies.rb`):
- `cart_add_item_service`, `cart_remove_item_service`, `cart_update_service`
- `order_updater_class`, `order_recalculator`, `order_canceller`
- `shipping_rate_estimator`, `stock_splitter`, `package_factory`
- `tax_calculator`
- `payment_create_service`, `payment_capture_service`
- `pricing_options_factory`
Always implement the **same public contract** as the service you replace.
### When to Decorate vs. Use Dependencies
| Need | Approach |
|------|----------|
| Add a column to Product | Migration (or Metafield, no decorator) |
| Add a validation to Product | Decorator |
| Add a method to Product | Decorator |
| Change cart-add behavior globally | `Spree::Dependencies.cart_add_item_service = ...` |
| React to an order completing | Subscriber |
| Change how shipping rates are computed | Replace `shipping_rate_estimator` |
| Add an admin menu item | `Spree.admin.navigation.register(...)` |
| Override an admin view | Partial injection slots; avoid Deface in v5 |
### Deface (Deprecated in v5)
Deface used CSS-selector view overrides on ERB:
```ruby
# DEPRECATED — only works on legacy v4 ERB frontend
Deface::Override.new(
virtual_path: 'spree/admin/products/index',
name: 'add_button',
insert_top: "table.product-list",
text: '<th>Custom Column</th>'
)
```
In v5+:
- Admin views: use partial injection slots (`store_products_nav_partials`, etc.)
- Storefront views: customize the Next.js storefront directly
### Versioning Extensions
Pin the gemspec to Spree version ranges:
```ruby
# spree_my_extension.gemspec
spec.add_dependency 'spree', '~> 5.4'
```
Test against multiple Spree minor versions in CI before releasing.
## Implementation Guidance
### Adding a Field to a Spree Model
**First, try Metafields** (no migration, upgrade-safe):
```ruby
product.metafields.create!(namespace: 'my_app', key: 'editor_pick', value: 'true')
```
**If you need indexed / queryable data, write a migration**:
```ruby
# db/migrate/20260512_add_editor_pick_to_spree_products.rb
class AddEditorPickToSpreeProducts < ActiveRecord::Migration[7.1]
def change
add_column :spree_products, :editor_pick, :boolean, default: false, null: false
add_index :spree_products, :editor_pick
end
end
```
Plus a thin decorator if you need scopes/validations:
```ruby
module MyApp::ProductDecorator
def self.prepended(base)
base.scope :editor_picks, -> { where(editor_pick: true) }
end
end
Spree::Product.prepend(MyApp::ProductDecorator)
```
### Replacing a Service Object
```ruby
# app/services/my_app/cart_add_item_service.rb
module MyApp
class CartAddItemService < Spree::Cart::AddItem
def call(order:, variant:, quantity: 1, options: {})
# custom logic
result = super
# post-processing
result
end
end
end
# config/initializers/spree.rb
Spree::Dependencies.cart_add_item_service = MyApp::CartAddItemService
```
Always extend rather than rewrite — call `super` to preserve core behavior.
### Avoiding Common Decorator Pitfalls
- **Don't decorate to call observers** — emit/subscribe to events instead.
- **Don't decorate controllers to add filters** — use Rails middleware or before_action in a sibling controller.
- **Don't open `Spree::Order` and add methods directly** — wrap in a decorator module so it's reloadable.
- **Don't forget `unless Spree::Foo.include?` guard** — double-prepend on autoload chains causes infinite loops.
### Testing Decorators
```ruby
# spec/models/spree/product_decorator_spec.rb
require 'rails_helper'
RSpec.describe Spree::Product do
describe '#display_name' do
let(:product) { build(:product, seo_title: 'Premium Tee') }
it 'returns seo_title when present' do
expect(product.display_name).to eq('Premium Tee')
end
end
end
```
### Common Pitfalls
- **Forgetting `to_prepare` reload hook** — decorators don't reload in dev; only fire once at boot.
- **`include` instead of `prepend`** — method override doesn't work; `super` goes wrong way.
- **Decorating before the class is loaded** — autoloading gotchas. Use the engine's `activate` pattern.
- **Adding a `_decorator.rb` outside `app/`** — won't be picked up.
- **Engine asset paths** — Rails 7 propshaft and importmaps require explicit asset declaration.
- **Pinning Spree too tightly** — `gem 'spree', '5.4.2'` breaks on every patch. Use `~> 5.4` instead.
Always verify the engine scaffolding and generator names against current `spree` gem docs — the generator suite is evolving (v5.2 added Admin SDK generators).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-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.
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.