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.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: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 indb/migrate/:
db/migrate/20260901000000_create_spree_reviews.rb
app/models/spree/review.rb
- Reviews belong to a store.
Spree::SingleStoreResourcefills 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_idgives every review an ID likereview_k5nR8xLqin API responses. Integer database IDs are never exposed. - No foreign key constraints on business tables, and
class_nameon every association. - Lifecycle events.
publishes_lifecycle_eventsmakes the model emitreview.created,review.updatedandreview.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
app/controllers/spree/api/v3/store/reviews_controller.rb
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
?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 theread_reviews and write_reviews permissions appear in the staff role editor and can be granted to secret API keys:
config/initializers/spree.rb
config/locales/en.yml
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 areviews_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
+=, 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
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
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
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:Add the extension to your application
From your Spree application’s backend directory, add the gem to theGemfile. Adjust the path to point at your extension:
Gemfile
/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:spree_dev_tools. Add a factory for your model:
lib/spree_reviews/factories.rb
spec/controllers/spree/api/v3/admin/reviews_controller_spec.rb
Related documentation
- Customization Quickstart - Choose the right extension point
- Customizing the API - Controllers, serializers and permitted attributes
- Services & Workflows - The full list of workflow hooks
- Events - Subscribe to Spree events
- Permissions - Register permission scopes
- Dashboard plugins - Admin UI for your extension

