Skip to main content

Overview

A Spree extension is a Ruby gem containing a Rails engine. It adds features to any Spree application without forking Spree: new models, new API endpoints, new permissions, and custom logic that runs inside Spree’s workflows. You can share an extension with the Spree community on GitHub so anyone can use it and contribute improvements. For officially supported integrations, see the Integrations directory. An extension is backend code only. Spree 6 has no Rails storefront or Rails admin to add views to:
Prefer Spree’s extension points over decorators: workflow hooks to change what happens inside a flow, events to react after something happened, and additional_permitted_attributes to make your own columns writable. They keep working when Spree changes internally. See the Customization Quickstart for guidance on choosing the right approach.
In this guide we build spree_reviews: customers read product reviews through the Store API, and staff manage them through the Admin API.

Generate the extension

Install the Spree extension generator. Spree 6 needs version 2.0 or newer — earlier versions generate a Spree 5 scaffold built around the Rails admin:
Run the following command from a directory outside your Spree application:
This creates a spree_reviews directory containing the engine, a gemspec, a test setup and continuous integration configuration. Its config/routes.rb and config/initializers/spree.rb come with commented-out Spree 6 examples — API routes, permission scopes, workflow hooks and event subscribers — that the steps below fill in. Change to its directory:

Add a model

Create the migration in db/migrate/:
db/migrate/20260901000000_create_spree_reviews.rb
Then the model:
app/models/spree/review.rb
A few Spree conventions are at work here:
  • Reviews belong to a store. Spree::SingleStoreResource fills in the store for new records, and the API only returns the current store’s reviews. Commerce data is always per store.
  • Prefixed IDs. has_prefix_id gives every review an ID like review_k5nR8xLq in API responses. Integer database IDs are never exposed.
  • No foreign key constraints on business tables, and class_name on every association.
  • Lifecycle events. publishes_lifecycle_events makes the model emit review.created, review.updated and review.deleted, which subscribers and webhooks can react to.

Expose it through the API

Add serializers for both APIs. The Admin API serializer extends the Store API one with the fields only staff should see:
app/serializers/spree/api/v3/review_serializer.rb
app/serializers/spree/api/v3/admin/review_serializer.rb
The Store API controller is read-only and returns approved reviews only:
app/controllers/spree/api/v3/store/reviews_controller.rb
The Admin API controller gives staff full create, read, update and delete access:
app/controllers/spree/api/v3/admin/reviews_controller.rb
scoped_resource :reviews means only staff roles and secret API keys holding the read_reviews or write_reviews permission can call these endpoints. The next section registers that permission. Finally, add the routes. The scaffold’s config/routes.rb already contains the route hook:
config/routes.rb
Pagination, Ransack filtering (?q[rating_gteq]=4), sorting and prefixed ID lookup all come from the base ResourceController. See Customizing the API for everything you can override.

Register permissions

Register your model as a permission scope, so the read_reviews and write_reviews permissions appear in the staff role editor and can be granted to secret API keys:
config/initializers/spree.rb
Label the permission for the dashboard in your locale file:
config/locales/en.yml
See Permissions for audiences and read-only scopes.

Extend core models and behavior

Extensions often need to change Spree’s own resources as well as add new ones. Reach for these options in order.

Make a new column writable

Suppose each product gets a reviews_enabled switch. Add the column with a migration, then let the existing product endpoints accept it — no controller changes needed:
lib/spree_reviews/engine.rb
Always append with +=, so you don’t remove attributes that other extensions added.

Run code inside a Spree workflow

Workflow hooks run your code at a named point inside a core flow — checkout completion, cancellations, refunds, product changes — and can stop the operation. For example, to require a description before a product with reviews enabled goes on sale:
config/initializers/spree.rb
app/services/spree_reviews/require_description.rb
Hook names are checked when the application boots, so a typo fails at startup rather than silently never running.

React to events

To do something after an event, without influencing it, write an event subscriber. For example, to ask customers for a review once their order arrives:
app/subscribers/spree_reviews/review_request_subscriber.rb
Subscribers are not discovered automatically. Register them in the extension’s initializer — bin/rails g spree:subscriber adds the line for you:
config/initializers/spree.rb

Decorators, as a last resort

When none of the above fits — adding an association to a core model, for example — use a decorator:
app/models/spree_reviews/product_decorator.rb
Rails does not load decorators on its own, so the scaffold’s engine does it for you: lib/spree_reviews/engine.rb loads every app/**/*_decorator*.rb file at boot and again on each code reload in development. Name the file with a _decorator suffix and there is nothing else to wire up.

Add admin screens

Admin screens are built as a dashboard plugin: a React package that registers navigation entries, pages and product page widgets, and reads and writes data through your Admin API endpoints. Scaffold one with the Spree CLI:
See Scaffolding and Backend integration to connect it to the endpoints above, and Distributing to ship the gem and the npm package together.

Add the extension to your application

From your Spree application’s backend directory, add the gem to the Gemfile. Adjust the path to point at your extension:
Gemfile
Install it, then copy the extension’s migrations into the application and run them:
The Store API now serves reviews at /api/v3/store/reviews, and the Admin API manages them at /api/v3/admin/reviews.

Test the extension

An extension is not a full Rails application, so its tests run against a small generated Spree application. Create it from the extension’s root directory:
Run this again whenever you add a migration. Tests use RSpec and Factory Bot, with Spree’s factories and test helpers coming from spree_dev_tools. Add a factory for your model:
lib/spree_reviews/factories.rb
Then test your endpoints:
spec/controllers/spree/api/v3/admin/reviews_controller_spec.rb
Run the suite:
Test the logic you wrote — the approved-only scope, your hook handler, your subscriber — rather than behavior that Rails and Spree already guarantee.