@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.
Files changed (45) hide show
  1. package/dist/api-reference/webhooks-events.md +1 -1
  2. package/dist/developer/contributing/developing-spree.md +2 -2
  3. package/dist/developer/core-concepts/metafields.md +1 -1
  4. package/dist/developer/core-concepts/store-credits-gift-cards.md +3 -5
  5. package/dist/developer/core-concepts/stores.md +1 -1
  6. package/dist/developer/customization/configuration.md +8 -8
  7. package/dist/developer/customization/dependencies.md +9 -9
  8. package/dist/developer/customization/metadata.md +3 -3
  9. package/dist/developer/customization/quickstart.md +7 -7
  10. package/dist/developer/customization/validations.md +1 -1
  11. package/dist/developer/dashboard/customization/backend.md +7 -4
  12. package/dist/developer/dashboard/customization/navigation.md +5 -3
  13. package/dist/developer/dashboard/customization/permissions.md +3 -1
  14. package/dist/developer/dashboard/customization/quickstart.md +3 -4
  15. package/dist/developer/dashboard/customization/routes.md +7 -6
  16. package/dist/developer/dashboard/customization/slots.md +7 -6
  17. package/dist/developer/dashboard/customization/tables.md +4 -2
  18. package/dist/developer/dashboard/customization/translations.md +4 -2
  19. package/dist/developer/dashboard/overview.md +5 -4
  20. package/dist/developer/dashboard/public-api.md +16 -2
  21. package/dist/developer/dashboard/recipes/attribute-end-to-end.md +2 -3
  22. package/dist/developer/dashboard/recipes/custom-form-field.md +12 -6
  23. package/dist/developer/dashboard/recipes/page-action-button.md +9 -8
  24. package/dist/developer/dashboard/recipes/sidebar-widget.md +11 -5
  25. package/dist/developer/dashboard/slots-catalog.md +1 -1
  26. package/dist/developer/how-to/build-a-marketplace.md +3 -3
  27. package/dist/developer/how-to/custom-digital-asset-provider.md +1 -1
  28. package/dist/developer/how-to/custom-document-numbers.md +3 -3
  29. package/dist/developer/how-to/custom-promotion.md +1 -1
  30. package/dist/developer/how-to/sell-digital-products.md +1 -1
  31. package/dist/developer/providers/dam.md +3 -1
  32. package/dist/developer/providers/erp.md +3 -1
  33. package/dist/developer/providers/pim.md +3 -1
  34. package/dist/developer/providers/sso.md +1 -1
  35. package/dist/developer/tutorial/dashboard.md +307 -0
  36. package/dist/developer/tutorial/events.md +66 -6
  37. package/dist/developer/tutorial/introduction.md +42 -13
  38. package/dist/developer/tutorial/model-and-api.md +187 -0
  39. package/dist/developer/tutorial/storefront.md +123 -6
  40. package/dist/developer/tutorial/testing.md +163 -7
  41. package/package.json +1 -1
  42. package/dist/developer/tutorial/admin-api.md +0 -13
  43. package/dist/developer/tutorial/dashboard-plugin.md +0 -14
  44. package/dist/developer/tutorial/model.md +0 -13
  45. 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 for one release and are removed in Spree 6.1 —
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 6.0 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.
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 (Spree 6.0, replaces the legacy Rails admin) |
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 were called **metafields** before Spree 6.0. The API and SDK have always called them custom fields; now the rest of Spree does too.
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 — both were retired in Spree 6.0
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` survive as deprecated
92
- > shells for one release and are removed in 6.1. Nothing writes them.
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 5.5+.
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 6 — grouped by area, with defaults and what each one changes.
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 6 actually reads. Settings that exist for backwards compatibility are listed under [Deprecated settings](#deprecated-settings) rather than mixed in with the rest.
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
- They were global before Spree 6. The global versions still exist for one release but are no longer read, so setting one in the initializer changes nothing — run `bin/rake spree:store_settings:backfill_from_config` during the upgrade to copy your values onto every store.
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
- Before Spree 6 this was two separate booleans, `auto_capture` and `auto_capture_on_dispatch`, which could contradict each other. Two upgrade tasks carry existing installs 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.
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 6 either ignores them or reads them only from subsystems it has replaced. Don't use them in new code.
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 in Spree 6, 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.
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 removed in Spree 6
208
+ ## Settings that have been removed
209
209
 
210
- These were deprecated in Spree 5 and are gone. Assigning one in your initializer now raises `NoMethodError`, so delete the line.
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
- ## Renamed seams in Spree 6.0
236
+ ## Seams backed by a workflow
237
237
 
238
- Seams backed by a [workflow](workflows.md) were renamed
239
- from `*_service` to `*_workflow` in 6.0:
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 for one release, but **assigning to one no longer
253
- > has any effect** — the override is recorded and a deprecation warning names the
254
- > seam to port to. A class written against the old service contract isn't
255
- > interchangeable with the workflow the new call sites use, so applying it
256
- > silently would break checkout in ways that are hard to trace.
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. The legacy names are removed in Spree 6.1.
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 and will be removed in Spree 6.0. Use `metadata` instead.
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`. It will be removed in Spree 6.0 and calling `public_metadata=` will emit a deprecation warning.
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 — will be removed in 6.0
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 6 backend — configuration, workflow hooks, events, checkout steps, providers, dependencies, and decorators as a last resort.
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 Spree 6 admin is a separate React application with its own extension points. See [Dashboard Customization](../dashboard/customization/quickstart.md).
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 6**. Before them, customizing a flow meant replacing the entire class and keeping your copy in sync with core forever.
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 6 has no checkout state machine. A cart reports what it still needs, and you can add to that list:
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 6 exposes pluggable providers for the domains that vary most between businesses:
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 6 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`.
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` in Spree 6. The old `*_service` names still resolve for one release 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 when you upgrade.
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
 
@@ -162,7 +162,7 @@ Spree::Checkout::Registry.add_requirement(
162
162
  )
163
163
  ```
164
164
 
165
- See the [Spree 6 quickstart](quickstart.md) for the full registry API.
165
+ See the [customization quickstart](quickstart.md) for the full registry API.
166
166
 
167
167
  ## Custom field values
168
168
 
@@ -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-core'
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-core'
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 { useResourceMutation } from '@spree/dashboard-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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>` from `@spree/dashboard-ui` to get the same header/main/sidebar grid as core pages:
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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-core'
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 happens via `@spree/dashboard-core`. You import its registries from your own host-app code (or from a separate plugin package) to add nav entries, routes, slot widgets, table columns, and translations.
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-core'
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. **Plugins don't import from here** — a plugin extends the dashboard through `@spree/dashboard-core` and never renders the shell. The host (the `dashboard-starter` project, or a create-spree-app project) is what imports it, to mount the admin:
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
- Importing from `@spree/dashboard` pins your code to the deployable app's version. Plugins and custom UI should prefer `@spree/dashboard-core` + `@spree/dashboard-ui`.
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