dist/codex/spree-commerce/skills/spree-admin-customization/SKILL.md
Customize the Spree v5 Admin Dashboard — Tailwind 4 + Hotwire/Turbo/Stimulus stack, the `Spree.admin.navigation` declarative API (v5.2+) for menu items, partial injection slots (`store_nav_partials`, `store_products_nav_partials`, `store_orders_nav_partials`, `settings_nav_partials`), the Admin SDK components + form builder, custom dashboard reports, the Page Builder for storefront editing, and theme management. Use when adding admin pages, injecting UI into existing screens, or building admin extensions.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins spree-admin-customizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
Spree.admin.navigation API.spree_admin (or spree umbrella admin) source for current partial slot names — they evolve.| Layer | Tech | |-------|------| | CSS | Tailwind CSS 4 | | JS framework | Hotwire (Turbo + Stimulus) | | Asset pipeline | Propshaft + Importmap (or esbuild) | | Templates | ERB (server-rendered) | | Forms | Spree Admin SDK form builder (v5.2+) |
The Bootstrap-based spree_backend engine is archived — don't port v4 patterns.
Declaratively register top-level menu items:
# config/initializers/spree.rb
Spree.admin.navigation.register(
key: :my_reports,
label: 'My Reports',
url: '/admin/my_reports',
icon: 'chart-bar',
position: 50,
match_path: %r{^/admin/my_reports}
)
Properties (verify against live API):
key — unique symbollabel — display text (i18n key works)url — relative or named-route helpericon — Tailwind / Heroicons nameposition — sort ordermatch_path — regex/string for "active" highlightingif: — proc returning bool for visibility (e.g., role check)For nested items, use navigation.register_child (verify exact API).
In v5, admin views expose partial injection slots at structural points:
| Slot | Where it lives |
|------|----------------|
| store_nav_partials | Main sidebar |
| store_products_nav_partials | Product detail/list pages |
| store_orders_nav_partials | Order detail/list pages |
| settings_nav_partials | Settings section |
Register a partial:
# config/initializers/spree.rb
Spree::Admin::NavigationHelper.store_orders_nav_partials << 'my_app/admin/order_extra_actions'
Then create the partial:
<%# app/views/my_app/admin/_order_extra_actions.html.erb %>
<% if local_assigns[:order] %>
<%= link_to 'Sync to ERP', sync_order_path(order), class: 'btn btn-secondary' %>
<% end %>
The slot system replaces the old Deface engine — same goal (inject content into core views) without the CSS-selector fragility.
A set of helpers / components for building admin extensions:
Verify the exact component/helper inventory in the live admin docs.
Subclass the Spree admin base controller:
# app/controllers/spree/admin/my_reports_controller.rb
module Spree::Admin
class MyReportsController < Spree::Admin::BaseController
def index
@daily_sales = Spree::Order.complete.group_by_day(:completed_at).sum(:total)
end
end
end
Mount the route:
# config/routes.rb
Rails.application.routes.draw do
Spree::Core::Engine.routes.draw do
namespace :admin do
resources :my_reports, only: [:index]
end
end
end
(Verify the route-drawing helper name — it's occasionally renamed.)
Spree uses CanCanCan for authorization. Inject abilities:
# app/models/spree/ability_decorator.rb
module SpreeMyExtension::AbilityDecorator
def initialize(user)
super
return unless user.has_spree_role?(:report_viewer)
can :read, MyApp::Report
end
end
Spree::Ability.prepend(SpreeMyExtension::AbilityDecorator)
v5 admin has a pluggable reports engine. Register a report:
# config/initializers/spree.rb
Spree::Admin::Reports.register(
key: :my_custom_report,
name: 'My Custom Report',
description: 'Daily sales by store',
partial: 'my_app/admin/reports/my_custom'
)
(Verify the live API — reports registry has evolved.)
The v5.0+ admin ships a no-code Page Builder for editing storefront pages. Sections (hero, product carousel, blog list, FAQ) compose into pages. Each section is an ERB partial registered with the builder.
Theme management lives in Settings → Themes. Themes bundle:
Custom sections register via:
Spree::PageBuilder::Sections.register(
key: :featured_grid,
partial: 'my_app/page_sections/featured_grid',
schema: { rows: { type: :number, default: 2 } }
)
(Verify against live source — Page Builder is new in v5.)
Add interactivity via Stimulus:
// app/javascript/controllers/my_admin_widget_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['output'];
connect() {
this.outputTarget.textContent = 'Hello from extension';
}
}
Register through importmap:
# config/importmap.rb
pin 'controllers/my_admin_widget_controller', to: 'my_admin_widget_controller.js'
(Or via esbuild config if the project uses esbuild instead.)
config/initializers/spree.rbapp/controllers/spree/admin/app/views/spree/admin/Ability decorator for permissionsAlways prefer partial slots over view overrides:
Spree::Admin::NavigationHelper.store_products_nav_partials << 'my_app/admin/product_seo_tools'
If no slot exists for what you need, prefer opening a PR upstream to add one rather than overriding the whole view.
<%# Using the SDK form builder %>
<%= spree_admin_form_for @product do |f| %>
<%= f.text_field :name, label: t('.name'), required: true %>
<%= f.rich_text_field :description %>
<%= f.collection_select :tax_category_id, Spree::TaxCategory.all, :id, :name, label: 'Tax Category' %>
<%= f.submit %>
<% end %>
(Verify against live SDK helper names.)
Configure brand colors via Tailwind 4's @theme directive (Spree v5 uses Tailwind 4):
/* app/assets/tailwind/application.css */
@import 'tailwindcss';
@theme {
--color-spree-primary: oklch(0.6 0.18 250);
--color-spree-secondary: oklch(0.55 0.12 320);
}
Use the groupdate gem for time-grouped queries:
@daily_sales = current_store.orders.complete
.where(completed_at: 30.days.ago..)
.group_by_day(:completed_at)
.sum(:total)
Render with a chart library like Chartkick, registered as a section partial.
Spree::Admin::BaseController — without it, you lose auth + layout.data-controller attribute wrong.to_prepare hook missing.authorize! in controllers.Always re-fetch the live admin navigation API and partial slot list — they're the parts of the admin most likely to evolve.
development
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.
tools
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.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.