Skip to main content
Spree is a flexible platform allowing you to customize every part of it to suit your business needs. This guide presents customization options in order of recommendation - start from the top and only move down if simpler options don’t meet your needs.

Quick Reference

What you want to doRecommended approach
Change store settings (currency, zones, languages)Store Settings
Tweak Spree behavior globallyConfiguration
React to model changes (sync, notifications)Events & Subscribers
Notify external servicesWebhooks
Swap core services (cart, checkout, etc.)Dependencies
Add admin menu itemsAdmin Navigation
Add sections to admin formsAdmin Partials
Add searchable/filterable fieldsRansack Configuration
Add associations/validations to modelsDecorators (last resort)

Store settings

Best for: Changing currency, shipping zones, languages, and other business settings.There’s a lot of Store settings you can change in the admin panel without touching the code.Go to Admin > SettingsSpree Admin Store Settings
Best for: Tweaking Spree’s behavior globally without modifying source code.Global application configuration allows you to customize various aspects of Spree:
config/initializers/spree.rb
Spree.config do |config|
  config.allow_guest_checkout = false
  config.products_per_page = 20
end
Please see Configuration section for more information.
Best for: Reacting to model changes, syncing with external services, sending notifications, audit logging.
Events are the recommended way to add behavior when something happens in Spree, replacing the need for decorator callbacks.
Spree’s event system lets you subscribe to events like order.completed, product.updated, payment.paid, etc.:
app/subscribers/my_app/order_completed_subscriber.rb
module MyApp
  class OrderCompletedSubscriber < Spree::Subscriber
    subscribes_to 'order.completed'

    def handle(event)
      order = Spree::Order.find_by(id: event.payload['id'])
      return unless order

      # Sync to external service, send notification, etc.
      ExternalService.notify_order_placed(order)
    end
  end
end
Key benefits:
  • Loose coupling - your code doesn’t depend on Spree internals
  • Async by default - keeps requests fast
  • Easier testing and upgrades
Please see Events section for more information.
Best for: Notifying external services (ERPs, CRMs, fulfillment systems) when events occur.Webhooks send HTTP POST requests to external URLs when Spree events happen:
  • Order completed → Notify fulfillment system
  • Product updated → Sync with PIM
  • Customer created → Add to CRM
Configure webhooks in Admin > Developers > Webhooks or via the API.Please see Webhooks section for more information.
Best for: Swapping core services, serializers, and abilities with your own implementations.Spree allows you to replace core classes without modifying them:
config/initializers/spree.rb
Spree::Dependencies.cart_add_item_service = "MyCartAddItemService"
Spree::Dependencies.cart_remove_item_service = "MyCartRemoveItemService"
This is cleaner than decorating services because you provide a complete replacement rather than patching behavior.Please see Dependencies section for more information.
Best for: Adding menu items, form sections, dashboard widgets, and other UI elements to the admin panel.Spree provides declarative APIs for extending the admin without decorators or view overrides:Navigation API - Add menu items:
config/initializers/spree.rb
Rails.application.config.after_initialize do
  Spree.admin.navigation.sidebar.add :brands,
    label: :brands,
    url: :admin_brands_path,
    icon: 'award',
    position: 35
end
Partials API - Add sections to forms:
config/initializers/spree.rb
Spree.admin.partials.product_form << 'spree/admin/products/erp_section'
Please see:
Best for: Making custom fields searchable/sortable in the admin and API.Instead of decorating models to add ransackable_attributes, use the Ransack configuration API:
config/initializers/spree.rb
Spree.ransack.add_attribute :product, :erp_id
Spree.ransack.add_association :product, :brand
Please see Search & Filtering section for more information.
Best for: Using your own user model or authentication system.Spree allows you to use your own authentication system instead of the default Devise-based one.You can find more information in the Authentication section.
Best for: Customizing checkout steps and flow.With Spree you can change the checkout flow to fit your business needs - add steps, remove steps, or change the order.Please see Checkout flow customization section for more information.
Best for: Adding associations, validations, scopes, and methods to Spree models. Use as a last resort.
Decorators should be used only when no other option works. They tightly couple your code to Spree internals and can break during upgrades.Do NOT use decorators for:
Decorators are still appropriate for structural changes:
app/models/spree/product_decorator.rb
module Spree
  module ProductDecorator
    def self.prepended(base)
      base.belongs_to :brand, class_name: 'MyApp::Brand', optional: true
      base.validates :external_id, presence: true
      base.scope :featured, -> { where(featured: true) }
    end

    def full_title
      "#{brand&.name} #{name}"
    end
  end

  Product.prepend(ProductDecorator)
end
Please see Decorators section for more information.