@spree/docs 0.1.237 → 0.1.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-reference/webhooks-events.md +1 -1
- package/dist/developer/contributing/developing-spree.md +2 -2
- package/dist/developer/core-concepts/metafields.md +1 -1
- package/dist/developer/core-concepts/store-credits-gift-cards.md +3 -5
- package/dist/developer/core-concepts/stores.md +1 -1
- package/dist/developer/customization/configuration.md +8 -8
- package/dist/developer/customization/dependencies.md +9 -9
- package/dist/developer/customization/metadata.md +3 -3
- package/dist/developer/customization/quickstart.md +7 -7
- package/dist/developer/customization/validations.md +1 -1
- package/dist/developer/dashboard/customization/backend.md +7 -4
- package/dist/developer/dashboard/customization/navigation.md +5 -3
- package/dist/developer/dashboard/customization/permissions.md +3 -1
- package/dist/developer/dashboard/customization/quickstart.md +3 -4
- package/dist/developer/dashboard/customization/routes.md +7 -6
- package/dist/developer/dashboard/customization/slots.md +7 -6
- package/dist/developer/dashboard/customization/tables.md +4 -2
- package/dist/developer/dashboard/customization/translations.md +4 -2
- package/dist/developer/dashboard/overview.md +5 -4
- package/dist/developer/dashboard/public-api.md +16 -2
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +2 -3
- package/dist/developer/dashboard/recipes/custom-form-field.md +12 -6
- package/dist/developer/dashboard/recipes/page-action-button.md +9 -8
- package/dist/developer/dashboard/recipes/sidebar-widget.md +11 -5
- package/dist/developer/dashboard/slots-catalog.md +1 -1
- package/dist/developer/how-to/build-a-marketplace.md +3 -3
- package/dist/developer/how-to/custom-digital-asset-provider.md +1 -1
- package/dist/developer/how-to/custom-document-numbers.md +3 -3
- package/dist/developer/how-to/custom-promotion.md +1 -1
- package/dist/developer/how-to/sell-digital-products.md +1 -1
- package/dist/developer/providers/dam.md +3 -1
- package/dist/developer/providers/erp.md +3 -1
- package/dist/developer/providers/pim.md +3 -1
- package/dist/developer/providers/sso.md +1 -1
- package/dist/developer/tutorial/dashboard.md +307 -0
- package/dist/developer/tutorial/events.md +66 -6
- package/dist/developer/tutorial/introduction.md +42 -13
- package/dist/developer/tutorial/model-and-api.md +187 -0
- package/dist/developer/tutorial/storefront.md +123 -6
- package/dist/developer/tutorial/testing.md +163 -7
- package/package.json +1 -1
- package/dist/developer/tutorial/admin-api.md +0 -13
- package/dist/developer/tutorial/dashboard-plugin.md +0 -14
- package/dist/developer/tutorial/model.md +0 -13
- package/dist/developer/tutorial/store-api.md +0 -13
|
@@ -764,7 +764,7 @@ Events: `newsletter_subscriber.created`, `newsletter_subscriber.updated`, `newsl
|
|
|
764
764
|
Events: `digital_asset.created`, `digital_asset.updated`, `digital_asset.deleted`
|
|
765
765
|
|
|
766
766
|
The legacy `digital.created` / `digital.updated` / `digital.deleted` names are
|
|
767
|
-
still emitted alongside these
|
|
767
|
+
still emitted alongside these, but will stop being sent in a future release —
|
|
768
768
|
subscribe to the `digital_asset.*` names.
|
|
769
769
|
|
|
770
770
|
```json
|
|
@@ -191,7 +191,7 @@ Install chromedriver on macOS:
|
|
|
191
191
|
brew install chromedriver
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
The
|
|
194
|
+
The React dashboard (`packages/dashboard`) has its own end-to-end test suite running on Playwright against a real Rails backend — see [Dashboard E2E tests](#dashboard-e2e-tests) under TypeScript Development.
|
|
195
195
|
|
|
196
196
|
### Performance in development mode
|
|
197
197
|
|
|
@@ -216,7 +216,7 @@ The backend setup is the same as for Ruby work — see [Setup](#setup) above. Wi
|
|
|
216
216
|
| `@spree/cli` | `packages/cli` | yes | CLI for managing Spree Commerce projects |
|
|
217
217
|
| `create-spree-app` | `packages/create-spree-app` | yes | Project scaffolding (`npm create spree-app`) |
|
|
218
218
|
| `@spree/docs` | `packages/docs` | yes | Developer documentation for AI agents and local reference |
|
|
219
|
-
| `@spree/dashboard` | `packages/dashboard` | no | React SPA admin dashboard (
|
|
219
|
+
| `@spree/dashboard` | `packages/dashboard` | no | React SPA admin dashboard (replaces the legacy Rails admin) |
|
|
220
220
|
| `@spree/dashboard-core` | `packages/dashboard-core` | no | Framework: registries, providers, generic infra hooks for the dashboard |
|
|
221
221
|
| `@spree/dashboard-ui` | `packages/dashboard-ui` | no | Design system used by the dashboard |
|
|
222
222
|
| `@spree/sdk-core` | `packages/sdk-core` | no | Shared HTTP/retry/error layer used by both SDKs |
|
|
@@ -9,7 +9,7 @@ Sooner or later you need to store something Spree doesn't have a field for. A fa
|
|
|
9
9
|
|
|
10
10
|
Custom fields are how you do that without changing the database. You declare a field once — its name, its type, who can see it — and from then on it can be set on any record of that kind, edited in the dashboard, returned by the API, and searched or filtered like any built-in field.
|
|
11
11
|
|
|
12
|
-
> **NOTE:** Custom fields
|
|
12
|
+
> **NOTE:** Custom fields are also known as **metafields** in the dashboard. The API and SDK call them custom fields throughout.
|
|
13
13
|
|
|
14
14
|
## Definitions and values
|
|
15
15
|
|
|
@@ -76,9 +76,7 @@ erDiagram
|
|
|
76
76
|
|
|
77
77
|
### Why a Credit Exists
|
|
78
78
|
|
|
79
|
-
Store credits carry no category and no type
|
|
80
|
-
because neither answered a question anything asked. Two fields cover it
|
|
81
|
-
instead:
|
|
79
|
+
Store credits carry no category and no type. Two fields cover it instead:
|
|
82
80
|
|
|
83
81
|
| Field | What it tells you |
|
|
84
82
|
|-------|-------------------|
|
|
@@ -88,8 +86,8 @@ instead:
|
|
|
88
86
|
Store credits do not expire; only gift cards carry an expiry, on their own
|
|
89
87
|
record. Which credit is spent first is decided by `Spree::StoreCredit.oldest_first`.
|
|
90
88
|
|
|
91
|
-
> **NOTE:** `Spree::StoreCreditCategory` and `Spree::StoreCreditType`
|
|
92
|
-
> shells for
|
|
89
|
+
> **NOTE:** `Spree::StoreCreditCategory` and `Spree::StoreCreditType` still exist as
|
|
90
|
+
> deprecated shells for compatibility. Nothing writes them.
|
|
93
91
|
|
|
94
92
|
### Store Credit Events
|
|
95
93
|
|
|
@@ -102,7 +102,7 @@ Each store owns its own resources. Products, orders, channels, markets, and taxo
|
|
|
102
102
|
|
|
103
103
|
If you need one Spree backend to serve **multiple distinct merchant brands** — different domains, different catalogs — there are two patterns:
|
|
104
104
|
|
|
105
|
-
- **Multiple channels under one store** (recommended for most cases) — model each storefront as a Sales Channel. Products are scoped per-channel via publications, orders carry the channel that originated them, and routing/pricing can differ per channel. This is the supported pattern in core Spree
|
|
105
|
+
- **Multiple channels under one store** (recommended for most cases) — model each storefront as a Sales Channel. Products are scoped per-channel via publications, orders carry the channel that originated them, and routing/pricing can differ per channel. This is the supported pattern in core Spree.
|
|
106
106
|
- **Multiple isolated stores (tenants) in one app** — full data isolation, only recommended if you're building a SaaS platform or a multi-tenant application. Each store can have different staff members, payment methods, shipping methods, and branding. This is supported by the [Spree Multi Tenant](../multi-tenant/quickstart.md) extension, which is a separate gem from core Spree.
|
|
107
107
|
|
|
108
108
|
## Related Documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Configuration
|
|
3
|
-
description: Every global Spree setting that is actually read by Spree
|
|
3
|
+
description: Every global Spree setting that is actually read by Spree — grouped by area, with defaults and what each one changes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Global settings live in `config/initializers/spree.rb` and apply to the whole application. Anything that varies per store — currency, markets, delivery zones, languages — is data you change in the dashboard instead.
|
|
@@ -23,7 +23,7 @@ Settings that describe the deployment rather than commerce behavior can be set w
|
|
|
23
23
|
| `dashboard_url` | `SPREE_DASHBOARD_URL` | Origin where the admin dashboard is hosted (e.g. `https://admin.my-shop.com`). Used for every link that sends someone into the dashboard: invitation emails, SSO redirects, and the first-run setup link printed by `db:seed`. |
|
|
24
24
|
| `seller_panel_url` | `SPREE_SELLER_PANEL_URL` | Origin where the marketplace seller panel is hosted, when you serve it somewhere other than this app's own `/sellers` mount. Used for links sent to sellers, today the invitation email. Leave it unset to use `/sellers`; with no panel deployed at all, those links open the dashboard. |
|
|
25
25
|
|
|
26
|
-
This page lists only settings Spree
|
|
26
|
+
This page lists only settings Spree actually reads. Settings that exist for backwards compatibility are listed under [Deprecated settings](#deprecated-settings) rather than mixed in with the rest.
|
|
27
27
|
|
|
28
28
|
## Settings that belong to a store
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ Settings that shape how a shop sells — when customers are charged, whether sto
|
|
|
31
31
|
|
|
32
32
|
`capture_method`, `track_inventory_levels`, `stock_reservations_enabled`, `stock_reservation_ttl_minutes`, `track_price_history`, `show_products_without_price`, `disable_sku_validation`, `address_requires_phone`, `company_field_enabled`, `tax_using_ship_address`, plus `guest_checkout` and `storefront_access` — those last two are owned by the channel, and the store's value is what a channel falls back to.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
The global versions of these settings still exist but are no longer read, so setting one in the initializer changes nothing. If you're upgrading from an installation that used them, run `bin/rake spree:store_settings:backfill_from_config` to copy your values onto every store.
|
|
35
35
|
|
|
36
36
|
```ruby
|
|
37
37
|
store = Spree::Current.store
|
|
@@ -59,7 +59,7 @@ payment_method.update!(capture_method: nil) # back to inheriting
|
|
|
59
59
|
payment_method.resolved_capture_method # => "checkout"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
If you're upgrading from an installation that used the older `auto_capture` and `auto_capture_on_dispatch` booleans, two tasks carry it over: `spree:store_settings:backfill_from_config` maps the old globals onto each store's `capture_method`, and `spree:migrate_capture_methods` converts the per-payment-method `auto_capture` column. A method left without a value inherits its store.
|
|
63
63
|
|
|
64
64
|
## Checkout and payments
|
|
65
65
|
|
|
@@ -180,7 +180,7 @@ Assigning at runtime affects only the current process and is lost on restart. Se
|
|
|
180
180
|
|
|
181
181
|
## Deprecated settings
|
|
182
182
|
|
|
183
|
-
These still exist so upgrades don't break, but Spree
|
|
183
|
+
These still exist so upgrades don't break, but Spree either ignores them or reads them only from subsystems it has replaced. Don't use them in new code.
|
|
184
184
|
|
|
185
185
|
| Setting | Use instead |
|
|
186
186
|
|---|---|
|
|
@@ -203,11 +203,11 @@ These still exist so upgrades don't break, but Spree 6 either ignores them or re
|
|
|
203
203
|
| `tax_using_ship_address` | The `tax_using_ship_address` setting on the store |
|
|
204
204
|
| `disable_store_presence_validation` | Every store-owned record now requires a store |
|
|
205
205
|
|
|
206
|
-
> **WARNING:** These settings have no effect at all
|
|
206
|
+
> **WARNING:** These settings have no effect at all, and are scheduled for removal: `allow_checkout_on_gateway_error` (completion checks whether payments cover the total, so a failed gateway call never completes an order), `products_per_page`, `alternative_shipping_phone`, `show_variant_full_price`, `reserve_stock_on`, `storefront_products_path`, `storefront_taxons_path`, `storefront_pages_path`. Setting them changes nothing.
|
|
207
207
|
|
|
208
|
-
## Settings
|
|
208
|
+
## Settings that have been removed
|
|
209
209
|
|
|
210
|
-
These
|
|
210
|
+
These settings are gone. Assigning one in your initializer now raises `NoMethodError`, so delete the line.
|
|
211
211
|
|
|
212
212
|
| Setting | What to do instead |
|
|
213
213
|
|---|---|
|
|
@@ -233,10 +233,10 @@ Spree::Dependencies.validate!
|
|
|
233
233
|
# => true (or raises Spree::DependencyError)
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
##
|
|
236
|
+
## Seams backed by a workflow
|
|
237
237
|
|
|
238
|
-
Seams backed by a [workflow](workflows.md)
|
|
239
|
-
|
|
238
|
+
Seams backed by a [workflow](workflows.md) use a
|
|
239
|
+
`*_workflow` name, not `*_service`:
|
|
240
240
|
|
|
241
241
|
| Legacy name | Current name |
|
|
242
242
|
|---|---|
|
|
@@ -249,14 +249,14 @@ from `*_service` to `*_workflow` in 6.0:
|
|
|
249
249
|
| `fulfillment_create_service` | `fulfillment_create_workflow` |
|
|
250
250
|
| `payments_handle_webhook_service` | `payments_handle_webhook_workflow` |
|
|
251
251
|
|
|
252
|
-
> **WARNING:** The legacy names stay readable
|
|
253
|
-
>
|
|
254
|
-
>
|
|
255
|
-
>
|
|
256
|
-
>
|
|
252
|
+
> **WARNING:** The legacy names stay readable, but **assigning to one no longer has any
|
|
253
|
+
> effect** — the override is recorded and a deprecation warning names the seam to
|
|
254
|
+
> port to. A class written against the old service contract isn't interchangeable
|
|
255
|
+
> with the workflow the new call sites use, so applying it silently would break
|
|
256
|
+
> checkout in ways that are hard to trace.
|
|
257
257
|
>
|
|
258
258
|
> If you override any of these, move to the `*_workflow` name and make sure your
|
|
259
|
-
> class subclasses the workflow.
|
|
259
|
+
> class subclasses the workflow.
|
|
260
260
|
|
|
261
261
|
## Backwards compatibility
|
|
262
262
|
|
|
@@ -227,14 +227,14 @@ The Store API currently supports writing metadata on:
|
|
|
227
227
|
|
|
228
228
|
## Deprecation: public_metadata
|
|
229
229
|
|
|
230
|
-
> **WARNING:** `public_metadata` is deprecated
|
|
230
|
+
> **WARNING:** `public_metadata` is deprecated. Use `metadata` instead.
|
|
231
231
|
|
|
232
|
-
The `public_metadata` column was never exposed in Store API responses and in practice served the same purpose as `private_metadata`.
|
|
232
|
+
The `public_metadata` column was never exposed in Store API responses and in practice served the same purpose as `private_metadata`. Calling `public_metadata=` emits a deprecation warning.
|
|
233
233
|
|
|
234
234
|
Always use the single `metadata` accessor for all schemaless key-value storage. If you need data visible to customers on the storefront, use [Metafields](../core-concepts/metafields.md) instead.
|
|
235
235
|
|
|
236
236
|
```ruby
|
|
237
|
-
# Deprecated —
|
|
237
|
+
# Deprecated — use `metadata` instead
|
|
238
238
|
order.public_metadata = { 'gift_message' => 'Happy Birthday!' }
|
|
239
239
|
|
|
240
240
|
# Use metadata for internal storage
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Quickstart
|
|
3
3
|
og:title: Spree Customization Quickstart
|
|
4
|
-
description: Recommended ways to customize the Spree
|
|
4
|
+
description: Recommended ways to customize the Spree backend — configuration, workflow hooks, events, checkout steps, providers, dependencies, and decorators as a last resort.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Spree is designed to be customized without forking it. This guide presents the options **in order of recommendation** — start at the top and only move down when a simpler option doesn't fit.
|
|
@@ -25,7 +25,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
25
25
|
| Replace a whole workflow or service | [Dependencies](#dependencies) |
|
|
26
26
|
| Add associations/validations to models | [Decorators](#decorators) (last resort) |
|
|
27
27
|
|
|
28
|
-
> **INFO:** **Customizing the admin?** This page covers the backend — models, API and business logic. The
|
|
28
|
+
> **INFO:** **Customizing the admin?** This page covers the backend — models, API and business logic. The admin is a separate React application with its own extension points. See [Dashboard Customization](../dashboard/customization/quickstart.md).
|
|
29
29
|
|
|
30
30
|
**Best for:** currency, markets, delivery zones, languages, and other business settings.
|
|
31
31
|
|
|
@@ -50,7 +50,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
50
50
|
|
|
51
51
|
**Best for:** running your own logic *inside* a core flow — checkout completion, order cancellation, refunds, fulfillment creation.
|
|
52
52
|
|
|
53
|
-
> **INFO:** Hooks are the **headline extension point in Spree
|
|
53
|
+
> **INFO:** Hooks are the **headline extension point in Spree**, so you don't have to replace an entire class and keep your copy in sync with core forever.
|
|
54
54
|
|
|
55
55
|
Register a handler against a hook key — `<workflow key>.<hook name>`:
|
|
56
56
|
|
|
@@ -111,7 +111,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
111
111
|
|
|
112
112
|
**Best for:** adding, removing or reordering checkout steps.
|
|
113
113
|
|
|
114
|
-
Spree
|
|
114
|
+
Spree has no checkout state machine. A cart reports what it still needs, and you can add to that list:
|
|
115
115
|
|
|
116
116
|
```ruby config/initializers/spree.rb
|
|
117
117
|
Spree::Checkout::Registry.register_step(
|
|
@@ -146,7 +146,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
146
146
|
|
|
147
147
|
**Best for:** swapping a whole area of behavior for your own implementation.
|
|
148
148
|
|
|
149
|
-
Spree
|
|
149
|
+
Spree exposes pluggable providers for the domains that vary most between businesses:
|
|
150
150
|
|
|
151
151
|
| Provider | Controls |
|
|
152
152
|
|---|---|
|
|
@@ -182,7 +182,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
182
182
|
|
|
183
183
|
**Best for:** using your own user model or identity provider.
|
|
184
184
|
|
|
185
|
-
Spree
|
|
185
|
+
Spree owns its authentication stack rather than depending on Devise, and separates the customer identity from the staff identity. Point Spree at your own classes with `Spree.customer_class` and `Spree.admin_user_class`.
|
|
186
186
|
|
|
187
187
|
See [Authentication](../providers/sso.md).
|
|
188
188
|
|
|
@@ -208,7 +208,7 @@ Spree is designed to be customized without forking it. This guide presents the o
|
|
|
208
208
|
Spree::Dependencies.cart_add_item_workflow = 'MyApp::Carts::AddItem'
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
> **WARNING:** Seam names end in `_workflow
|
|
211
|
+
> **WARNING:** Seam names end in `_workflow`. The old `*_service` names still resolve with a warning, but **writes to them are ignored** — a class written against the old service contract isn't interchangeable with a workflow. Update the key if your code still uses the old name.
|
|
212
212
|
|
|
213
213
|
Prefer a hook when you only need to add behavior. Replace the class only when you need to change what the flow fundamentally does.
|
|
214
214
|
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Backend integration
|
|
|
4
4
|
description: Talk to your own (or Spree's) Admin API from the dashboard — typed SDK calls, custom endpoints, prefixed IDs, error handling, and React-Query hooks.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
The dashboard never reaches into Rails models or fetches HTML. All data flows through `@spree/admin-sdk` — a typed Admin API client. This page covers the patterns: calling existing endpoints, adding new ones, handling errors, and wrapping it all in React-Query hooks for caching.
|
|
8
10
|
|
|
9
11
|
## The `adminClient`
|
|
@@ -11,7 +13,7 @@ The dashboard never reaches into Rails models or fetches HTML. All data flows th
|
|
|
11
13
|
The SPA boots a single `adminClient` instance and exposes it from `@spree/dashboard-core`. Resource methods follow `client.<resource>.<verb>()`:
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
import { adminClient } from '@spree/dashboard
|
|
16
|
+
import { adminClient } from '@spree/dashboard'
|
|
15
17
|
|
|
16
18
|
await adminClient.products.list({ filter: { name_cont: 'shirt' } })
|
|
17
19
|
await adminClient.products.get('prod_86Rf07xd4z', { expand: ['variants'] })
|
|
@@ -50,7 +52,7 @@ Resource methods are just `request<T>()` calls with the right path + types pre-b
|
|
|
50
52
|
The dashboard's data layer is React-Query — every `useFoo` hook is `useQuery({ queryKey, queryFn })` over an SDK call. Follow the same pattern for your own data:
|
|
51
53
|
|
|
52
54
|
```ts
|
|
53
|
-
import { adminClient } from '@spree/dashboard
|
|
55
|
+
import { adminClient } from '@spree/dashboard'
|
|
54
56
|
import { useQuery } from '@tanstack/react-query'
|
|
55
57
|
import type { AdminBrand } from './types'
|
|
56
58
|
|
|
@@ -67,7 +69,8 @@ export function useBrands(params?: { search?: string }) {
|
|
|
67
69
|
For mutations, prefer the existing `useResourceMutation` helper — it wires up the SDK's error shape, the 422 silencer, and the toast for non-validation errors:
|
|
68
70
|
|
|
69
71
|
```ts
|
|
70
|
-
import {
|
|
72
|
+
import type { AdminBrand } from '@spree/admin-sdk'
|
|
73
|
+
import { adminClient, useResourceMutation } from '@spree/dashboard'
|
|
71
74
|
|
|
72
75
|
export function useUpdateBrand(id: string) {
|
|
73
76
|
return useResourceMutation({
|
|
@@ -100,7 +103,7 @@ try {
|
|
|
100
103
|
In a React Hook Form submit handler, prefer the higher-level helper:
|
|
101
104
|
|
|
102
105
|
```tsx
|
|
103
|
-
import { mapSpreeErrorsToForm } from '@spree/dashboard
|
|
106
|
+
import { mapSpreeErrorsToForm } from '@spree/dashboard'
|
|
104
107
|
|
|
105
108
|
async function handleSubmit(values: FormValues) {
|
|
106
109
|
try {
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Navigation
|
|
|
4
4
|
description: Add, modify, and remove items from the dashboard's main sidebar and settings sub-nav.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
The dashboard has two navigation registries: **main sidebar** (`nav`) and **settings sub-shell** (`settingsNav`). Both are module singletons exposed from `@spree/dashboard-core` and consumed by `<AppSidebar>` and `<SettingsSidebar>` via `useSyncExternalStore`, so late registration re-renders the sidebars automatically.
|
|
8
10
|
|
|
9
11
|
## Main sidebar (`nav`)
|
|
@@ -11,7 +13,7 @@ The dashboard has two navigation registries: **main sidebar** (`nav`) and **sett
|
|
|
11
13
|
### Add an entry
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
import { nav } from '@spree/dashboard
|
|
16
|
+
import { nav } from '@spree/dashboard'
|
|
15
17
|
import { BarChartIcon } from 'lucide-react'
|
|
16
18
|
|
|
17
19
|
nav.add({
|
|
@@ -31,7 +33,7 @@ nav.add({
|
|
|
31
33
|
The facade groups multiple registry calls:
|
|
32
34
|
|
|
33
35
|
```ts
|
|
34
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
36
|
+
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
35
37
|
|
|
36
38
|
defineDashboardPlugin({
|
|
37
39
|
nav: [
|
|
@@ -156,7 +158,7 @@ The settings page has its own sub-shell sidebar with grouped entries (Store, Loc
|
|
|
156
158
|
### Register a group
|
|
157
159
|
|
|
158
160
|
```ts
|
|
159
|
-
import { settingsNav } from '@spree/dashboard
|
|
161
|
+
import { settingsNav } from '@spree/dashboard'
|
|
160
162
|
|
|
161
163
|
settingsNav.addGroup({
|
|
162
164
|
key: 'integrations',
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Permissions
|
|
|
4
4
|
description: How to hide UI behind CanCanCan permissions in your customisations — and why hiding is never the same as authorising.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
The dashboard exposes the current admin's CanCanCan abilities to every component via the `usePermissions()` hook. The registry surfaces — nav entries, settings entries, custom routes — also accept a `subject` shortcut, and nav entries take a generic `if` predicate that reads from `permissions`.
|
|
8
10
|
|
|
9
11
|
## Two layers
|
|
@@ -88,7 +90,7 @@ Use it for combined checks (permission + store state), feature flags, multi-acti
|
|
|
88
90
|
Use the `usePermissions()` hook — it returns `{ permissions, rules, isLoading }`:
|
|
89
91
|
|
|
90
92
|
```tsx
|
|
91
|
-
import { usePermissions } from '@spree/dashboard
|
|
93
|
+
import { usePermissions } from '@spree/dashboard'
|
|
92
94
|
|
|
93
95
|
function ReportsPage() {
|
|
94
96
|
const { permissions } = usePermissions()
|
|
@@ -18,7 +18,7 @@ Add an "Analytics" item to the dashboard sidebar.
|
|
|
18
18
|
**Step 1.** Open `src/plugins.ts` — the starter ships this file already wired up — and register a nav entry:
|
|
19
19
|
|
|
20
20
|
```tsx
|
|
21
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
21
|
+
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
22
22
|
import { BarChartIcon } from 'lucide-react'
|
|
23
23
|
|
|
24
24
|
defineDashboardPlugin({
|
|
@@ -43,8 +43,7 @@ Clicking it lands on a "Page not found" screen — we registered the nav entry b
|
|
|
43
43
|
**Step 3.** Create the page component at `src/pages/analytics.tsx`:
|
|
44
44
|
|
|
45
45
|
```tsx
|
|
46
|
-
import { PageHeader } from '@spree/dashboard
|
|
47
|
-
import { ResourceLayout } from '@spree/dashboard-ui'
|
|
46
|
+
import { PageHeader, ResourceLayout } from '@spree/dashboard'
|
|
48
47
|
|
|
49
48
|
export function AnalyticsPage() {
|
|
50
49
|
return (
|
|
@@ -59,7 +58,7 @@ export function AnalyticsPage() {
|
|
|
59
58
|
**Step 4.** Register the route in `src/plugins.ts`:
|
|
60
59
|
|
|
61
60
|
```tsx
|
|
62
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
61
|
+
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
63
62
|
import { BarChartIcon } from 'lucide-react'
|
|
64
63
|
import { AnalyticsPage } from './pages/analytics'
|
|
65
64
|
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Routes
|
|
|
4
4
|
description: Mount custom pages under your dashboard's `/$storeId/...` path. Supports path params, permission gating, and works alongside the dashboard's built-in routes.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
The dashboard's first-party routes (`customers/`, `orders/`, `products/`, …) are file-based via TanStack Router and live in `@spree/dashboard/src/routes/`. Custom pages have two mechanisms:
|
|
8
10
|
|
|
9
11
|
1. **File routes** (packaged plugins — preferred): the plugin ships ordinary TanStack route files, and the host build compiles them into its typed route tree. Typed `<Link>`s with no casts, route-level code splitting, `validateSearch`, loaders — full router citizenship.
|
|
@@ -28,7 +30,7 @@ src/
|
|
|
28
30
|
|
|
29
31
|
```tsx
|
|
30
32
|
// src/routes/brands.index.tsx
|
|
31
|
-
import { resourceSearchSchema } from '@spree/dashboard
|
|
33
|
+
import { resourceSearchSchema } from '@spree/dashboard'
|
|
32
34
|
import { createFileRoute } from '@tanstack/react-router'
|
|
33
35
|
import { BrandsListPage } from '../pages/brands-list'
|
|
34
36
|
|
|
@@ -70,7 +72,7 @@ Rename one of the routes, or drop the plugin that shouldn't own it. (Duplicate p
|
|
|
70
72
|
Register from your host app's `plugins.ts` (or a plugin that genuinely needs runtime registration):
|
|
71
73
|
|
|
72
74
|
```tsx
|
|
73
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
75
|
+
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
74
76
|
import { ReportsPage } from './pages/reports'
|
|
75
77
|
|
|
76
78
|
defineDashboardPlugin({
|
|
@@ -119,7 +121,7 @@ function ReportDetailPage({ params, storeId, searchParams }: PluginRouteProps) {
|
|
|
119
121
|
`searchParams` is typed as `Record<string, unknown>` because the dispatcher can't statically know your page's shape. If you're handing it to `<ResourceTable>`, cast to `ResourceSearch`:
|
|
120
122
|
|
|
121
123
|
```tsx
|
|
122
|
-
import type { ResourceSearch } from '@spree/dashboard
|
|
124
|
+
import type { ResourceSearch } from '@spree/dashboard'
|
|
123
125
|
|
|
124
126
|
<ResourceTable searchParams={searchParams as ResourceSearch} ... />
|
|
125
127
|
```
|
|
@@ -143,11 +145,10 @@ Always pair this with a matching `subject` on the **nav entry** so users without
|
|
|
143
145
|
|
|
144
146
|
## Layout
|
|
145
147
|
|
|
146
|
-
Custom routes inherit the dashboard's `_authenticated` + `$storeId` layout (auth guard, sidebar, top bar, etc.) — your component renders inside the `<Outlet />`. Use `<ResourceLayout>`
|
|
148
|
+
Custom routes inherit the dashboard's `_authenticated` + `$storeId` layout (auth guard, sidebar, top bar, etc.) — your component renders inside the `<Outlet />`. Use `<ResourceLayout>` to get the same header/main/sidebar grid as core pages:
|
|
147
149
|
|
|
148
150
|
```tsx
|
|
149
|
-
import { PageHeader } from '@spree/dashboard
|
|
150
|
-
import { ResourceLayout } from '@spree/dashboard-ui'
|
|
151
|
+
import { PageHeader, ResourceLayout } from '@spree/dashboard'
|
|
151
152
|
|
|
152
153
|
function ReportsPage() {
|
|
153
154
|
return (
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Slots
|
|
|
4
4
|
description: Inject your own widgets into existing dashboard pages without forking them. Slots are named injection points the dashboard exposes on built-in pages.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
A slot is a named injection point in a built-in page. Components registered against a slot render inside it, sorted by `position`, gated by an optional `if` predicate. The host page knows nothing about your widget — it just renders `<Slot name="..." context={...} />` and the slot registry handles the rest.
|
|
8
10
|
|
|
9
11
|
## Quick example
|
|
@@ -11,8 +13,7 @@ A slot is a named injection point in a built-in page. Components registered agai
|
|
|
11
13
|
Render a card with the current user's email in every page's actions dropdown:
|
|
12
14
|
|
|
13
15
|
```tsx
|
|
14
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
15
|
-
import { DropdownMenuItem } from '@spree/dashboard-ui'
|
|
16
|
+
import { DropdownMenuItem, defineDashboardPlugin } from '@spree/dashboard'
|
|
16
17
|
|
|
17
18
|
function UserBadge({ user }: { user?: { email: string } }) {
|
|
18
19
|
if (!user) return null
|
|
@@ -45,7 +46,7 @@ For anything beyond the slot's own context — permissions, the current store, t
|
|
|
45
46
|
If you'd rather have full type safety, call `registerSlot` directly with the explicit type parameter:
|
|
46
47
|
|
|
47
48
|
```tsx
|
|
48
|
-
import { registerSlot } from '@spree/dashboard
|
|
49
|
+
import { registerSlot } from '@spree/dashboard'
|
|
49
50
|
|
|
50
51
|
interface ProductSlotContext {
|
|
51
52
|
product: { id: string; brand_id?: string | null }
|
|
@@ -63,7 +64,7 @@ registerSlot<ProductSlotContext>('product.form_sidebar', {
|
|
|
63
64
|
Gate inside the component with `usePermissions()` — return `null` when the user lacks access:
|
|
64
65
|
|
|
65
66
|
```tsx
|
|
66
|
-
import { usePermissions } from '@spree/dashboard
|
|
67
|
+
import { usePermissions } from '@spree/dashboard'
|
|
67
68
|
|
|
68
69
|
function AdminOnlyMenuItem() {
|
|
69
70
|
const { permissions } = usePermissions()
|
|
@@ -79,7 +80,7 @@ The entry-level `if` predicate also exists, but it only receives the slot's own
|
|
|
79
80
|
Slots on form pages (product, category, store settings — see the [slots catalog](../slots-catalog.md#detail-page-form-slots) for which) render **inside the page's `<form>`** and expose its react-hook-form instance. A widget can register inputs against it with `useHostForm()` — they hydrate from the API, flip the Save button on change, and persist in the page's own save, with no save logic in the widget:
|
|
80
81
|
|
|
81
82
|
```tsx
|
|
82
|
-
import { useHostForm } from '@spree/dashboard
|
|
83
|
+
import { useHostForm } from '@spree/dashboard'
|
|
83
84
|
|
|
84
85
|
function TechSpecsCard() {
|
|
85
86
|
const form = useHostForm<{ tech_specs: string }>()
|
|
@@ -96,7 +97,7 @@ Pair it with a `formFields` registration so the field hydrates and ships in the
|
|
|
96
97
|
## Removing or updating an entry
|
|
97
98
|
|
|
98
99
|
```ts
|
|
99
|
-
import { removeSlot, updateSlot } from '@spree/dashboard
|
|
100
|
+
import { removeSlot, updateSlot } from '@spree/dashboard'
|
|
100
101
|
|
|
101
102
|
removeSlot('product.form_sidebar', 'brand-card')
|
|
102
103
|
updateSlot('product.form_sidebar', 'brand-card', { position: 999 })
|
|
@@ -4,6 +4,8 @@ sidebarTitle: Tables
|
|
|
4
4
|
description: Add, change, or remove columns on built-in list tables (products, orders, customers, …), or define a table for your own resource.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
Every list page in the dashboard renders through `<ResourceTable>`, which reads its column definitions from a **table registry** keyed by table name (`products`, `orders`, `customers`, …). That gives you two levers:
|
|
8
10
|
|
|
9
11
|
1. **Extend a built-in table** — add, patch, or remove columns (including their filters) without forking the page.
|
|
@@ -14,7 +16,7 @@ Every list page in the dashboard renders through `<ResourceTable>`, which reads
|
|
|
14
16
|
Imperatively, from `src/plugins.ts` (or a plugin entry):
|
|
15
17
|
|
|
16
18
|
```tsx
|
|
17
|
-
import { tables } from '@spree/dashboard
|
|
19
|
+
import { tables } from '@spree/dashboard'
|
|
18
20
|
|
|
19
21
|
tables.products.addColumn({
|
|
20
22
|
key: 'brand',
|
|
@@ -117,7 +119,7 @@ Filtering and sorting are executed by the Admin API (Ransack), not in the browse
|
|
|
117
119
|
For your own list page, declare the table once — typically right next to your `defineDashboardPlugin` call:
|
|
118
120
|
|
|
119
121
|
```tsx
|
|
120
|
-
import { defineTable } from '@spree/dashboard
|
|
122
|
+
import { defineTable } from '@spree/dashboard'
|
|
121
123
|
|
|
122
124
|
defineTable<Brand>('brands', {
|
|
123
125
|
title: 'Brands',
|
|
@@ -4,12 +4,14 @@ sidebarTitle: Translations
|
|
|
4
4
|
description: Add your own i18n strings to the dashboard so labels, validation messages, and notifications respect the admin's chosen locale.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
> **NOTE:** Examples here import from `@spree/dashboard`, which re-exports the framework and the design system for host applications. A **distributed plugin** imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly instead — it extends the shell rather than shipping it. See [plugin overview](../plugins/overview.md).
|
|
8
|
+
|
|
7
9
|
The dashboard uses [i18next](https://www.i18next.com/) with the `react-i18next` bindings. All strings live in i18next's default `translation` namespace, with every key under a top-level `admin.` prefix (`admin.nav.orders`, `admin.fields.<resource>.<attribute>.label`, …). You register your strings into the same namespace with the same prefix so they look and feel native.
|
|
8
10
|
|
|
9
11
|
## Add translations
|
|
10
12
|
|
|
11
13
|
```ts
|
|
12
|
-
import { i18n } from '@spree/dashboard
|
|
14
|
+
import { i18n } from '@spree/dashboard'
|
|
13
15
|
|
|
14
16
|
i18n.addResourceBundle('en', 'translation', {
|
|
15
17
|
admin: {
|
|
@@ -126,7 +128,7 @@ Register translations **before** any code that calls `i18n.t()` at module-load t
|
|
|
126
128
|
|
|
127
129
|
```tsx
|
|
128
130
|
// src/plugins.ts (same pattern in a plugin package's entry module)
|
|
129
|
-
import { defineDashboardPlugin, i18n } from '@spree/dashboard
|
|
131
|
+
import { defineDashboardPlugin, i18n } from '@spree/dashboard'
|
|
130
132
|
|
|
131
133
|
i18n.addResourceBundle('en', 'translation', en, true, true) // ① translations first
|
|
132
134
|
|
|
@@ -4,8 +4,7 @@ sidebarTitle: Overview
|
|
|
4
4
|
description: Learn about the Spree React Dashboard architecture and how to customize it and extend it
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
> **INFO:** The React Dashboard is currently a **Developer Preview** — APIs may still change between releases.
|
|
8
|
-
> In Spree 6 it will become the default Admin interface replacing the Spree 5 admin.
|
|
7
|
+
> **INFO:** The React Dashboard is currently a **Developer Preview** — APIs may still change between releases. It is becoming the default Admin interface.
|
|
9
8
|
|
|
10
9
|
The React Dashboard is a React single-page application (SPA) that talks to the [Spree Admin API](../../api-reference/admin-api.md). It can be distributed with the Spree API or as a standalone npm package, and it can be extended with plugins. The dashboard is built with modern React libraries and patterns, including:
|
|
11
10
|
|
|
@@ -32,7 +31,9 @@ Dashboard consinsts of 3 NPM packages, each with a different purpose. You can im
|
|
|
32
31
|
| `@spree/dashboard-core` | Framework: registries (nav, route, slot, table, settings-nav), providers (auth, permission, store), admin SDK client, infra hooks, the `defineDashboardPlugin` API | Extending the admin — every customization API lives here |
|
|
33
32
|
| `@spree/dashboard` | The deployable SPA: routes, resource hooks, schemas, locale strings, app shell | Replacing or restyling specific routes (rare) |
|
|
34
33
|
|
|
35
|
-
Most customization
|
|
34
|
+
Most customization uses the registries in `@spree/dashboard-core` to add nav entries, routes, slot widgets, table columns and translations.
|
|
35
|
+
|
|
36
|
+
**In your own dashboard app, import from `@spree/dashboard`.** It re-exports both packages, so you never have to work out which one an export lives in. A distributed plugin is the exception: it imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly, because it plugs into the shell rather than shipping it.
|
|
36
37
|
|
|
37
38
|
You can also built your own dashboard using `@spree/dashboard-ui` primitives and `@spree/dashboard-core` registries, if you want to replace the default dashboard entirely. This is rare — most teams just add a few pages or cards to the existing dashboard.
|
|
38
39
|
|
|
@@ -55,7 +56,7 @@ You're building a Spree store and want a "Featured products" page or a custom ca
|
|
|
55
56
|
|
|
56
57
|
```tsx
|
|
57
58
|
// src/plugins.ts — already imported by the app; just add your registrations
|
|
58
|
-
import { defineDashboardPlugin } from '@spree/dashboard
|
|
59
|
+
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
59
60
|
import { MyAnalyticsPage } from './pages/analytics'
|
|
60
61
|
|
|
61
62
|
defineDashboardPlugin({
|
|
@@ -195,7 +195,15 @@ import { cn } from '@spree/dashboard-ui' // clsx + tailwind-merge
|
|
|
195
195
|
|
|
196
196
|
## `@spree/dashboard`
|
|
197
197
|
|
|
198
|
-
The app shell
|
|
198
|
+
The app shell, and the single import a host application needs. It re-exports everything from `@spree/dashboard-core` and `@spree/dashboard-ui`, so code in your own dashboard app never has to work out which package an export lives in:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { defineDashboardPlugin, useStore, adminClient, Button, ResourceTable } from '@spree/dashboard'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Plugins don't import from here** — a plugin extends the dashboard through `@spree/dashboard-core` and never renders the shell, so importing the shell would pull the whole app into a package meant to plug into it.
|
|
205
|
+
|
|
206
|
+
The host (the `dashboard-starter` project, or a create-spree-app project) is also what mounts the admin:
|
|
199
207
|
|
|
200
208
|
```ts
|
|
201
209
|
import { createDashboardRouter, Dashboard } from '@spree/dashboard'
|
|
@@ -211,7 +219,13 @@ import { spreeDashboardPlugin } from '@spree/dashboard/vite' // Vite plugin: Ta
|
|
|
211
219
|
import '@spree/dashboard/styles.css' // the app's CSS entry
|
|
212
220
|
```
|
|
213
221
|
|
|
214
|
-
|
|
222
|
+
A few names exist in both re-exported packages — the design system ships a presentational component and the framework wraps it with data (`ResourceCombobox`, `StatusCard`, `Slot`, `DateRange`, `ResourceMultiAutocomplete`). The facade gives you the framework's version, which is the one an application wants. Reach past it when you want the presentational half:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { ResourceCombobox } from '@spree/dashboard-ui' // takes its options as props
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Importing from `@spree/dashboard` pins your code to the deployable app's version, which is what a host app wants and what a distributed plugin must avoid.
|
|
215
229
|
|
|
216
230
|
## What about `@spree/admin-sdk`?
|
|
217
231
|
|