@spree/docs 0.1.239 → 0.1.241
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/store-api/metadata.md +6 -6
- package/dist/developer/core-concepts/architecture.md +15 -19
- package/dist/developer/core-concepts/carts.md +71 -1
- package/dist/developer/core-concepts/metafields.md +0 -2
- package/dist/developer/create-spree-app/quickstart.md +22 -19
- package/dist/developer/customization/api.md +2 -2
- package/dist/developer/customization/checkout.md +117 -0
- package/dist/developer/customization/configuration.md +76 -175
- package/dist/developer/customization/decorators.md +120 -130
- package/dist/developer/customization/metadata.md +9 -9
- package/dist/developer/customization/model-preferences.md +34 -68
- package/dist/developer/customization/permissions.md +13 -28
- package/dist/developer/customization/validations.md +16 -43
- package/dist/developer/dashboard/concepts.md +4 -2
- package/dist/developer/dashboard/customization/backend.md +5 -2
- package/dist/developer/dashboard/customization/navigation.md +1 -1
- package/dist/developer/dashboard/customization/quickstart.md +1 -1
- package/dist/developer/dashboard/overview.md +2 -2
- package/dist/developer/dashboard/plugins/distributing.md +1 -1
- package/dist/developer/dashboard/public-api.md +3 -2
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -3
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
- package/dist/developer/dashboard/recipes/sidebar-widget.md +2 -2
- package/dist/developer/dashboard/slots-catalog.md +60 -0
- package/dist/developer/getting-started/quickstart.md +25 -24
- package/dist/developer/how-to/custom-promotion.md +1 -1
- package/dist/developer/how-to/custom-search-provider.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +2 -2
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/configuration.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/testing.md +45 -91
- package/package.json +1 -1
|
@@ -11,15 +11,17 @@ Every grantable capability is a flat key of the form `read_<resource>` or `write
|
|
|
11
11
|
|
|
12
12
|
Keys are per resource, not per action. The money-adjacent areas (`payments`, `refunds`, `gift_cards`, `store_credits`) are separate resources from `orders`, so "view orders but don't refund" needs no special casing.
|
|
13
13
|
|
|
14
|
-
Roles
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
Roles are data, so create them where you manage the rest of your store's data: **Settings → Staff → Roles** in the dashboard, or the Admin API when you want it scripted.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
spree api post roles --data '{
|
|
18
|
+
"name": "Support",
|
|
19
|
+
"description": "Read-only support",
|
|
20
|
+
"permissions": ["read_orders", "read_customers"]
|
|
21
|
+
}'
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
A role
|
|
24
|
+
A role marked immutable renders read-only in the dashboard, for roles the application does not want store staff editing. The `admin` role is always protected.
|
|
23
25
|
|
|
24
26
|
## Registering extension permissions
|
|
25
27
|
|
|
@@ -56,30 +58,13 @@ Naming another audience exposes the scope to a principal outside the store's own
|
|
|
56
58
|
|
|
57
59
|
A key never decides *which rows* its holder reaches. That is the controller's job, through scope fetching — a seller holding `write_products` manages their own products because the seller API reads through `current_seller`, not because the key says so.
|
|
58
60
|
|
|
59
|
-
##
|
|
61
|
+
## What the catalog does not cover
|
|
60
62
|
|
|
61
|
-
The catalog is deliberately flat: it answers "may this role touch this kind of record", and that is the whole grant vocabulary.
|
|
63
|
+
The catalog is deliberately flat: it answers "may this role touch this kind of record", and that is the whole grant vocabulary.
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
- **Row-level restrictions** (a support agent limited to one market) are not expressible as keys. Replace the ability class outright:
|
|
65
|
+
**Record-state rules** — "a completed order cannot be deleted" — belong on models and workflows, not on roles. Put them there and they bind every caller, including secret API keys, which never consult the role system at all. A rule enforced only in the permission layer is a rule the API can walk straight past.
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
# config/initializers/spree.rb
|
|
68
|
-
Spree::Dependencies.ability_class = 'MyApp::Ability'
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
# app/models/my_app/ability.rb
|
|
73
|
-
class MyApp::Ability < Spree::Ability
|
|
74
|
-
def initialize(user, options = {})
|
|
75
|
-
super
|
|
76
|
-
return unless user.respond_to?(:market_id) && user.market_id
|
|
77
|
-
|
|
78
|
-
cannot :manage, Spree::Order
|
|
79
|
-
can [:read, :admin], Spree::Order, market_id: user.market_id
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
```
|
|
67
|
+
**Row-level restrictions**, such as a support agent limited to one market, are not expressible as permission keys. Scope them where the records are read: a controller reaching through `current_store.orders` rather than `Spree::Order` already restricts by tenant, and the same technique narrows by market or seller. See [extending the API](api.md).
|
|
83
68
|
|
|
84
69
|
## How enforcement works
|
|
85
70
|
|
|
@@ -52,27 +52,6 @@ Both cart keys appear above because adding to a cart and setting a quantity are
|
|
|
52
52
|
different flows. See [Services & Workflows](workflows.md)
|
|
53
53
|
for the full hook list.
|
|
54
54
|
|
|
55
|
-
## Adding a rule to a model
|
|
56
|
-
|
|
57
|
-
For rules about the shape of a record, a decorator is the supported route:
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
# app/models/spree/product_decorator.rb
|
|
61
|
-
module MyStore
|
|
62
|
-
module ProductDecorator
|
|
63
|
-
def self.prepended(base)
|
|
64
|
-
base.validates :external_id, presence: true, uniqueness: true
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
Spree::Product.prepend(MyStore::ProductDecorator)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Decorators couple you to Spree internals, so prefer a hook when the rule is
|
|
73
|
-
really about an operation. See
|
|
74
|
-
[Decorators](decorators.md).
|
|
75
|
-
|
|
76
55
|
## Relaxing a rule Spree ships
|
|
77
56
|
|
|
78
57
|
Never call `clear_validators!` — it wipes every validation on the model,
|
|
@@ -81,7 +60,8 @@ cause. Use one of these instead.
|
|
|
81
60
|
|
|
82
61
|
### Store preferences
|
|
83
62
|
|
|
84
|
-
Some rules are
|
|
63
|
+
Some rules are switchable per store, with no code at all. Change them in the
|
|
64
|
+
dashboard under **Settings → Store**, or through the Admin API:
|
|
85
65
|
|
|
86
66
|
| Preference | Effect |
|
|
87
67
|
|---|---|
|
|
@@ -90,30 +70,23 @@ Some rules are already switchable, per store, from the dashboard or a console:
|
|
|
90
70
|
| `address_requires_company` | Requires it — only meaningful with the field shown |
|
|
91
71
|
| `disable_sku_validation` | Turns off SKU uniqueness |
|
|
92
72
|
|
|
93
|
-
```ruby
|
|
94
|
-
store.update!(preferred_address_requires_company: true)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Overriding the gating predicate
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
module AddressDecorator
|
|
105
|
-
# Business addresses only in this store.
|
|
106
|
-
def require_company?
|
|
107
|
-
!quick_checkout
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
74
|
+
```typescript Admin SDK
|
|
75
|
+
await adminClient.store.update({
|
|
76
|
+
preferred_address_requires_phone: false,
|
|
77
|
+
})
|
|
78
|
+
```
|
|
111
79
|
|
|
112
|
-
|
|
80
|
+
```bash cURL
|
|
81
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
82
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
83
|
+
-H 'Content-Type: application/json' \
|
|
84
|
+
-d '{ "preferred_address_requires_phone": false }'
|
|
113
85
|
```
|
|
114
86
|
|
|
115
|
-
|
|
116
|
-
|
|
87
|
+
|
|
88
|
+
> **NOTE:** All four are editable in the dashboard except `address_requires_company`, which
|
|
89
|
+
> is API-only for now.
|
|
117
90
|
|
|
118
91
|
### The address validator registry
|
|
119
92
|
|
|
@@ -121,7 +94,7 @@ Address rules are regional and business-specific, so addresses carry a registry
|
|
|
121
94
|
of extra validator classes you can add to and remove from:
|
|
122
95
|
|
|
123
96
|
```ruby
|
|
124
|
-
# config/initializers/spree.rb — or config.to_prepare for your own classes
|
|
97
|
+
# server/config/initializers/spree.rb — or config.to_prepare for your own classes
|
|
125
98
|
Spree.validators.addresses.register(MyStore::PostBoxValidator)
|
|
126
99
|
|
|
127
100
|
# Drop a rule Spree ships
|
|
@@ -30,7 +30,9 @@ The dashboard is a **Single Page Application**: one HTML page, all routing clien
|
|
|
30
30
|
@spree/dashboard ─ app shell: routes, resource hooks, locales, vite config
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
[Overview](overview.md) covers the split in more detail. The short version: **`-ui`** has no providers or hooks (data comes via props); **`-core`** is the extension API and runtime services; **`-dashboard`** is the deployable app shell.
|
|
33
|
+
[Overview](overview.md) covers the split in more detail. The short version: **`-ui`** has no providers or hooks (data comes via props); **`-core`** is the extension API and runtime services; **`-dashboard`** is the deployable app shell.
|
|
34
|
+
|
|
35
|
+
The split matters when you publish a plugin: it consumes `-core` and `-ui` and never the app shell, which it plugs into rather than ships. In your own dashboard app the distinction is noise, so `@spree/dashboard` re-exports both — one import, and you never have to work out which package a given export lives in.
|
|
34
36
|
|
|
35
37
|
## How customization plugs in
|
|
36
38
|
|
|
@@ -53,7 +55,7 @@ The facade `defineDashboardPlugin({ nav, routes, slots, … })` groups all five
|
|
|
53
55
|
The signed-in admin's identity and abilities are exposed via three providers wrapping the app:
|
|
54
56
|
|
|
55
57
|
- `<AuthProvider>` — the current admin user; `useAuth()` returns `{ user, signIn, signOut, … }`
|
|
56
|
-
- `<PermissionProvider>` — CanCanCan abilities; `usePermissions()` returns `{
|
|
58
|
+
- `<PermissionProvider>` — CanCanCan abilities; `usePermissions()` returns `{ permissions, rules, isLoading }`, and the checks live on `permissions.can(...)`
|
|
57
59
|
- `<StoreProvider>` — current store + timezone + currency; `useStore()` returns `{ storeId, store, … }`
|
|
58
60
|
|
|
59
61
|
When you write a custom page or hook, pull from these. **Never reach into local state for identity** — it changes on store switch or session refresh, and the providers wire all that for you.
|
|
@@ -52,13 +52,16 @@ Resource methods are just `request<T>()` calls with the right path + types pre-b
|
|
|
52
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:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
import { adminClient } from '@spree/dashboard'
|
|
55
|
+
import { adminClient, useResourceKey } from '@spree/dashboard'
|
|
56
56
|
import { useQuery } from '@tanstack/react-query'
|
|
57
57
|
import type { AdminBrand } from './types'
|
|
58
58
|
|
|
59
59
|
export function useBrands(params?: { search?: string }) {
|
|
60
60
|
return useQuery({
|
|
61
|
-
|
|
61
|
+
// `useResourceKey` scopes the key to the current store. A bare
|
|
62
|
+
// `['brands', params]` key would serve one store's rows to another
|
|
63
|
+
// after a store switch.
|
|
64
|
+
queryKey: useResourceKey('brands', params),
|
|
62
65
|
queryFn: () =>
|
|
63
66
|
adminClient.request<{ data: AdminBrand[] }>('GET', '/brands', { params }),
|
|
64
67
|
staleTime: 30_000,
|
|
@@ -26,7 +26,7 @@ nav.add({
|
|
|
26
26
|
})
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`position` controls ordering — built-ins
|
|
29
|
+
`position` controls ordering — built-ins run 50–600 (Getting Started 50 when shown, Home 100, Orders 200, Products 300, Inventory 350, Customers 400, Sellers 450, Loyalty 475, Promotions 500, Reports 600), leaving gaps to slot into. The default is 100. Keys must be unique, including against the built-in keys (`getting-started`, `home`, `orders`, `products`, `inventory`, `customers`, `sellers`, `loyalty`, `promotions`, `reports`, `settings`) — duplicates throw at boot.
|
|
30
30
|
|
|
31
31
|
### Add via `defineDashboardPlugin`
|
|
32
32
|
|
|
@@ -80,7 +80,7 @@ defineDashboardPlugin({
|
|
|
80
80
|
|
|
81
81
|
Save. Click "Analytics" — your page renders inside the dashboard chrome, at `/<store id>/analytics`.
|
|
82
82
|
|
|
83
|
-
> **WARNING:** Every `key` must be unique, including against the built-ins (`getting-started`, `home`, `orders`, `products`, `customers`, `promotions`, `reports`, `settings`). Registering a duplicate key throws at boot with a message naming the conflict.
|
|
83
|
+
> **WARNING:** Every `key` must be unique, including against the built-ins (`getting-started`, `home`, `orders`, `products`, `inventory`, `customers`, `sellers`, `loyalty`, `promotions`, `reports`, `settings`). Registering a duplicate key throws at boot with a message naming the conflict.
|
|
84
84
|
|
|
85
85
|
## What just happened
|
|
86
86
|
|
|
@@ -6,7 +6,7 @@ description: Learn about the Spree React Dashboard architecture and how to custo
|
|
|
6
6
|
|
|
7
7
|
> **INFO:** The React Dashboard is currently a **Developer Preview** — APIs may still change between releases. It is becoming the default Admin interface.
|
|
8
8
|
|
|
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:
|
|
9
|
+
The React Dashboard is a React single-page application (SPA) that talks to the [Spree Admin API](../../api-reference/admin-api/introduction.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:
|
|
10
10
|
|
|
11
11
|
| Layer | Choice |
|
|
12
12
|
|---|---|
|
|
@@ -35,7 +35,7 @@ Most customization uses the registries in `@spree/dashboard-core` to add nav ent
|
|
|
35
35
|
|
|
36
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.
|
|
37
37
|
|
|
38
|
-
You can also
|
|
38
|
+
You can also build 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.
|
|
39
39
|
|
|
40
40
|
## Get the dashboard
|
|
41
41
|
|
|
@@ -120,7 +120,7 @@ The registries are global singletons, so a key collision is the consumer's probl
|
|
|
120
120
|
- **Namespace locale keys** — `admin.acme_brands.title` not `admin.brands.title`.
|
|
121
121
|
- **Namespace API paths** — your Rails engine should mount under `/api/v3/admin/acme/...`, not `/api/v3/admin/brands` (which collides with core Brands if it ever ships).
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Registering a key that already exists throws at boot, with a message naming the conflict. That is deliberate: a silently shadowed entry would leave the consumer with a plugin that half works, where a failure at startup points straight at the two plugins stepping on each other.
|
|
124
124
|
|
|
125
125
|
## Distribution channels
|
|
126
126
|
|
|
@@ -4,7 +4,7 @@ sidebarTitle: Public API
|
|
|
4
4
|
description: Every importable symbol the dashboard exposes — components, hooks, registries, providers, and the SDK client. Treat this as the surface you can safely depend on.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The dashboard is structured as three packages
|
|
7
|
+
The dashboard is structured as three packages. This page lists the symbols we commit to keeping — **treat anything not listed as internal**, liable to move or be renamed without a deprecation cycle, even where the barrel happens to export it today. Never reach past a package entry point into `@spree/dashboard-core/src/...`.
|
|
8
8
|
|
|
9
9
|
## `@spree/dashboard-core`
|
|
10
10
|
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
// Page chrome
|
|
52
52
|
PageHeader,
|
|
53
53
|
PageTabs,
|
|
54
|
-
|
|
54
|
+
MobileTopBar,
|
|
55
55
|
AppSidebar,
|
|
56
56
|
SettingsSidebar,
|
|
57
57
|
Slot,
|
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
useCustomFields,
|
|
93
93
|
useDirectUpload, // ActiveStorage direct uploads
|
|
94
94
|
useExport, // CSV export job lifecycle
|
|
95
|
+
useResourceKey, // store-scoped query key — use it for every custom query
|
|
95
96
|
useResourceMutation, // useMutation wrapper with 422 handling
|
|
96
97
|
useHostForm, // the built-in form a slot widget renders inside (throws when absent)
|
|
97
98
|
useOptionalHostForm, // same, but null when the page has no host form
|
|
@@ -14,7 +14,7 @@ It's the full version of the [custom form field recipe](custom-form-field.md)
|
|
|
14
14
|
|
|
15
15
|
## First: should this be a custom field instead?
|
|
16
16
|
|
|
17
|
-
**Default to a [custom field definition](custom-form-field.md#path-a-custom-field-definition
|
|
17
|
+
**Default to a [custom field definition](custom-form-field.md#path-a-custom-field-definition-no-code).** It needs zero code — declare the field, and the product form renders and saves it. That covers most plugin data and everything merchants define themselves.
|
|
18
18
|
|
|
19
19
|
Graduate to a real database column only when the attribute needs **list operations or database guarantees**:
|
|
20
20
|
|
|
@@ -90,7 +90,7 @@ Add a Rails validation while you're here (`validates :lead_time_days, numericali
|
|
|
90
90
|
```tsx
|
|
91
91
|
// src/plugins.ts (or a plugin's entry module)
|
|
92
92
|
import { defineDashboardPlugin, i18n } from '@spree/dashboard'
|
|
93
|
-
import { LeadTimeCard } from './
|
|
93
|
+
import { LeadTimeCard } from './slots/lead-time-card'
|
|
94
94
|
|
|
95
95
|
i18n.addResourceBundle('en', 'translation', en, true, true)
|
|
96
96
|
|
|
@@ -123,7 +123,7 @@ defineDashboardPlugin({
|
|
|
123
123
|
The form widget binds to the host form — no `<form>` of its own, no save button:
|
|
124
124
|
|
|
125
125
|
```tsx
|
|
126
|
-
// src/
|
|
126
|
+
// src/slots/lead-time-card.tsx
|
|
127
127
|
import { Card, CardContent, CardHeader, CardTitle, Field, FieldError, FieldLabel, Input, useHostForm } from '@spree/dashboard'
|
|
128
128
|
import { useTranslation } from 'react-i18next'
|
|
129
129
|
|
|
@@ -93,7 +93,7 @@ defineDashboardPlugin({
|
|
|
93
93
|
The `product.form_sidebar` slot renders inside the product form. Bind your input to the host form with `useHostForm()` — no `<form>` of your own, no save button:
|
|
94
94
|
|
|
95
95
|
```tsx
|
|
96
|
-
// src/
|
|
96
|
+
// src/slots/tech-specs-card.tsx
|
|
97
97
|
import {
|
|
98
98
|
Card,
|
|
99
99
|
CardContent,
|
|
@@ -21,7 +21,7 @@ import { Button, adminClient, usePermissions, useResourceMutation } from '@spree
|
|
|
21
21
|
import { Send } from 'lucide-react'
|
|
22
22
|
|
|
23
23
|
interface Props {
|
|
24
|
-
resource: { id: string;
|
|
24
|
+
resource: { id: string; status: string } // Order
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function SendInvoiceButton({ resource }: Props) {
|
|
@@ -34,7 +34,7 @@ export function SendInvoiceButton({ resource }: Props) {
|
|
|
34
34
|
|
|
35
35
|
// Only show to users who can act, and only on completed orders.
|
|
36
36
|
if (!permissions.can('update', 'Spree::Order')) return null
|
|
37
|
-
if (resource.
|
|
37
|
+
if (resource.status !== 'placed') return null
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<Button
|
|
@@ -11,7 +11,7 @@ This recipe builds a "Loyalty status" card for the customer detail page: read-on
|
|
|
11
11
|
## The component
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
// src/
|
|
14
|
+
// src/slots/loyalty-status-card.tsx
|
|
15
15
|
import { useQuery } from '@tanstack/react-query'
|
|
16
16
|
import {
|
|
17
17
|
adminClient,
|
|
@@ -101,7 +101,7 @@ Worth noting:
|
|
|
101
101
|
```ts
|
|
102
102
|
// src/plugins.ts (in your dashboard app)
|
|
103
103
|
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
104
|
-
import { LoyaltyStatusCard } from './
|
|
104
|
+
import { LoyaltyStatusCard } from './slots/loyalty-status-card'
|
|
105
105
|
|
|
106
106
|
defineDashboardPlugin({
|
|
107
107
|
slots: {
|
|
@@ -89,6 +89,66 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
89
89
|
| Context | `{ customer }` — the full `Customer` record |
|
|
90
90
|
| Host form | No — the page edits through sheets; widgets save via their own API calls |
|
|
91
91
|
|
|
92
|
+
### `collection.form_sidebar`
|
|
93
|
+
|
|
94
|
+
| Where | Collection detail page (`products/collections/$collectionId`), end of the sidebar column |
|
|
95
|
+
|---|---|
|
|
96
|
+
| Use | Collection-scoped cards — merchandising rules, feed settings, external sync state |
|
|
97
|
+
| Context | `{ collection }` — the `Collection` record |
|
|
98
|
+
| Host form | **Yes** — form key `collection` |
|
|
99
|
+
|
|
100
|
+
### `company.form_main`
|
|
101
|
+
|
|
102
|
+
| Where | Company detail page, end of the main column |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Use | B2B account cards — credit terms, approval settings, external account references |
|
|
105
|
+
| Context | `{ company, kind, canEdit }` — `kind` is the company's own `kind`, `canEdit` whether the viewer may write |
|
|
106
|
+
| Host form | No — the widget owns its persistence |
|
|
107
|
+
|
|
108
|
+
### `company.form_sidebar`
|
|
109
|
+
|
|
110
|
+
| Where | Company detail page, end of the sidebar column |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Use | Narrow company-scoped cards alongside the main form |
|
|
113
|
+
| Context | `{ company, kind, canEdit }` |
|
|
114
|
+
| Host form | No |
|
|
115
|
+
|
|
116
|
+
## Seller panel slots
|
|
117
|
+
|
|
118
|
+
Rendered by the marketplace seller panel (`@spree/seller-dashboard`) rather than the admin dashboard, and registered the same way.
|
|
119
|
+
|
|
120
|
+
### `seller.form_main`
|
|
121
|
+
|
|
122
|
+
| Where | Seller detail page, end of the main column |
|
|
123
|
+
|---|---|
|
|
124
|
+
| Use | Seller-scoped cards — payout configuration, onboarding state, compliance records |
|
|
125
|
+
| Context | `{ seller, canEdit }` |
|
|
126
|
+
| Host form | No |
|
|
127
|
+
|
|
128
|
+
### `seller.form_sidebar`
|
|
129
|
+
|
|
130
|
+
| Where | Seller detail page, end of the sidebar column |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Use | Narrow seller-scoped cards |
|
|
133
|
+
| Context | `{ seller, canEdit }` |
|
|
134
|
+
| Host form | No |
|
|
135
|
+
|
|
136
|
+
### `seller.team.actions`
|
|
137
|
+
|
|
138
|
+
| Where | Seller team screen, in the header's action area |
|
|
139
|
+
|---|---|
|
|
140
|
+
| Use | Team-level actions — bulk invite, export, an external directory sync |
|
|
141
|
+
| Context | `{ sellerId }` |
|
|
142
|
+
| Host form | No |
|
|
143
|
+
|
|
144
|
+
### `seller.team.after`
|
|
145
|
+
|
|
146
|
+
| Where | Seller team screen, below the member list |
|
|
147
|
+
|---|---|
|
|
148
|
+
| Use | Anything that belongs under the team — audit log, role explainer, seat usage |
|
|
149
|
+
| Context | `{ sellerId }` |
|
|
150
|
+
| Host form | No |
|
|
151
|
+
|
|
92
152
|
## Page tabs slot
|
|
93
153
|
|
|
94
154
|
### `page.tabs` (default)
|
|
@@ -6,35 +6,39 @@ description: "Install Spree Commerce locally, run your first store, and deploy i
|
|
|
6
6
|
|
|
7
7
|
## Using `create-spree-app`
|
|
8
8
|
|
|
9
|
-
The quickest way to get started. Scaffolds a full Spree project with Next.js Storefront (optional).
|
|
9
|
+
The quickest way to get started. Scaffolds a full Spree project with React admin dashboard and Next.js Storefront (optional).
|
|
10
10
|
|
|
11
|
-
**Requirements:** [Node.js](https://nodejs.org/)
|
|
11
|
+
**Requirements:** [Node.js](https://nodejs.org/) 24+ and [Docker](https://docs.docker.com/get-docker/) running.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx create-spree-app@latest my-store
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
The CLI walks you through an interactive setup
|
|
17
|
+
The CLI walks you through an interactive setup where you choose the project name and whether to include the Next.js Storefront. It then builds a Docker stack with Postgres, and Spree API.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
2. Optionally load **sample data** (products, categories, images)
|
|
19
|
+
Once complete it will open a browser window so you can finalize the store setup (pick name, currency, your email/password).
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
<video autoPlay muted loop playsInline controls className="block w-auto max-w-full h-auto rounded-xl" src="/videos/store-setup.mp4"></video>
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
## Project structure and overview
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
`create-spree-app` creates a project folder like this:
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
```
|
|
28
|
+
my-store/
|
|
29
|
+
├── server/ # The Spree API
|
|
30
|
+
├── apps/
|
|
31
|
+
│ ├── dashboard/ # Admin dashboard (Vite/React)
|
|
32
|
+
│ ├── seller-dashboard/ # Seller panel, for marketplaces (Vite/React)
|
|
33
|
+
│ └── storefront/ # Next.js storefront (if selected)
|
|
34
|
+
├── .env # Ports, secrets and service URLs
|
|
35
|
+
├── .spree/ # CLI credentials — gitignored, created on first run
|
|
36
|
+
├── docker-compose.yml # The local stack
|
|
37
|
+
├── package.json # Scripts and the pinned @spree/cli
|
|
38
|
+
├── AGENTS.md # Instructions for coding agents
|
|
39
|
+
├── CLAUDE.md # Project layout and conventions
|
|
40
|
+
└── README.md # How to run everything
|
|
41
|
+
```
|
|
38
42
|
|
|
39
43
|
## Using the Spree CLI
|
|
40
44
|
|
|
@@ -58,18 +62,15 @@ See the [Spree CLI guide](../cli/quickstart.md) for all commands and the [Admin
|
|
|
58
62
|
|
|
59
63
|
## Next Steps
|
|
60
64
|
|
|
65
|
+
- [How Spree fits together](../core-concepts/architecture.md) — The catalog, the cart, the order and fulfillment — what each one is for, and the two APIs you build against. Twenty minutes here saves a lot of guessing later, and it ends by pointing you at whichever path suits you.
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
- [Customization Tutorial](../tutorial/introduction.md) — Build a complete custom feature — model, admin UI, API, and TypeScript SDK — step by step.
|
|
64
|
-
- [Core Concepts](../core-concepts/architecture.md) — Understand how Spree models commerce — stores, products, orders, payments.
|
|
65
|
-
- [Next.js Storefront](../storefront/nextjs/quickstart.md) — Customize and extend the headless storefront.
|
|
66
|
-
|
|
67
|
+
In a hurry? Jump straight to the [tutorial](../tutorial/introduction.md) to build a feature end to end, the [Next.js Storefront](../storefront/nextjs/quickstart.md) to put a storefront in front of it, or the [API reference](../../api-reference/admin-api/introduction.md) to see what you can do with the Admin API.
|
|
67
68
|
|
|
68
69
|
> **TIP:** Using an AI coding agent? Run `npx skills add spree/agent-skills` in your new project — it teaches your agent the customization patterns before you write a line of code. See [Agentic Development](../agentic/overview.md).
|
|
69
70
|
|
|
70
71
|
---
|
|
71
72
|
|
|
72
|
-
Congrats! You've set up your Spree
|
|
73
|
+
Congrats! You've set up your Spree application and it's looking amazing!
|
|
73
74
|
|
|
74
75
|
Give Spree a [GitHub Star](https://github.com/spree/spree), why don't ya? Thank you for supporting Spree open-source!
|
|
75
76
|
|
|
@@ -150,7 +150,7 @@ module Spree
|
|
|
150
150
|
private
|
|
151
151
|
|
|
152
152
|
def matches_brand?(line_item)
|
|
153
|
-
brand = line_item.product.
|
|
153
|
+
brand = line_item.product.get_custom_field('details.brand')&.value
|
|
154
154
|
preferred_brand_names.include?(brand)
|
|
155
155
|
end
|
|
156
156
|
end
|
|
@@ -224,9 +224,9 @@ Background jobs (`IndexJob`, `RemoveJob`) fire on `after_commit` when `indexing_
|
|
|
224
224
|
|
|
225
225
|
Always use prefixed IDs (`ctg_abc`, `prod_xyz`, `optval_abc`) when indexing. Never use raw database IDs — Spree supports UUID primary keys.
|
|
226
226
|
|
|
227
|
-
###
|
|
227
|
+
### Custom Field search schema
|
|
228
228
|
|
|
229
|
-
Product
|
|
229
|
+
Product custom fields marked `searchable` / `sortable` on `CustomFieldDefinition` are projected through `Spree::SearchProvider::CustomFieldSchema` — shared by providers and `ProductPresenter`. Toggle participation with definition flags; run `rake spree:search:reindex` after changes when using Meilisearch.
|
|
230
230
|
|
|
231
231
|
Document shaping for `cf_*` fields stays on `search_product_presenter` — the only search-related dependency.
|
|
232
232
|
|
|
@@ -170,12 +170,12 @@ end
|
|
|
170
170
|
|
|
171
171
|
### Step 4 (optional): Add a Variant-side Predicate
|
|
172
172
|
|
|
173
|
-
The example above relies on `variant.refrigerated?`. In a real plugin you'd back that with a [Custom Field](../core-concepts/metafields.md) on `Spree::Variant` — say a boolean
|
|
173
|
+
The example above relies on `variant.refrigerated?`. In a real plugin you'd back that with a [Custom Field](../core-concepts/metafields.md) on `Spree::Variant` — say a boolean custom field with key `refrigerated` — and define the predicate as a thin reader:
|
|
174
174
|
|
|
175
175
|
```ruby app/models/spree/variant_decorator.rb
|
|
176
176
|
Spree::Variant.class_eval do
|
|
177
177
|
def refrigerated?
|
|
178
|
-
|
|
178
|
+
custom_fields.find_by(namespace: 'logistics', key: 'refrigerated')&.value == 'true'
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
```
|
|
@@ -66,5 +66,5 @@ The SDK transforms `filter`/`sort` into the Ransack-flavored query string the Ad
|
|
|
66
66
|
## See also
|
|
67
67
|
|
|
68
68
|
- The [Store SDK extending guide](../extending.md) covers the same pattern for storefront endpoints — the API is identical, only the base path (`/api/v3/store`) and auth header differ.
|
|
69
|
-
- The [API](../../tutorial/
|
|
69
|
+
- The [API](../../tutorial/model-and-api.md) and [SDK](../../tutorial/storefront.md) tutorials walk through creating new Spree endpoints end-to-end (Rails controller, serializer, TypeScript types). Everything they show for the Store API applies equivalently for the Admin API.
|
|
70
70
|
- For building dashboard UI on top of your custom endpoint, see the [Dashboard](../../dashboard/overview.md) section — start with [Customization Quickstart](../../dashboard/customization/quickstart.md) for in-app changes or [Plugin Overview](../../dashboard/plugins/overview.md) for redistributable packages.
|
|
@@ -66,4 +66,4 @@ const page: PaginatedResponse<Order> = await client.orders.list()
|
|
|
66
66
|
- [Admin API Reference](../../../api-reference/admin-api/introduction.md) — The underlying REST API the SDK wraps — every endpoint, parameter, and response shape.
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
> **TIP:** Adding your own resources? The [`spree generate api_resource`](../../tutorial/
|
|
69
|
+
> **TIP:** Adding your own resources? The [`spree generate api_resource`](../../tutorial/model-and-api.md) generator scaffolds Admin API endpoints (model, serializers, controllers, specs) in one command, and the [extending guide](../extending.md) shows how to call custom endpoints from the SDK.
|
|
@@ -127,4 +127,4 @@ await client.customFields('Spree::Product', 'prod_xxx').list()
|
|
|
127
127
|
|
|
128
128
|
## Adding your own resources
|
|
129
129
|
|
|
130
|
-
The resource map grows with your store. The [`spree generate api_resource`](../../tutorial/
|
|
130
|
+
The resource map grows with your store. The [`spree generate api_resource`](../../tutorial/model-and-api.md) generator scaffolds new Admin API endpoints (model, serializers, controllers, routes, specs) in one command — then call them from the SDK with a [custom fetch or raw request](../extending.md), or generate a typed client for them.
|
|
@@ -162,7 +162,7 @@ All types are exported as unprefixed names (e.g., `Product`, `Order`). Legacy `S
|
|
|
162
162
|
- `OptionType` - Option type (e.g., Size, Color)
|
|
163
163
|
- `OptionValue` - Option value (e.g., Small, Red)
|
|
164
164
|
- `DigitalLink` - Digital download link
|
|
165
|
-
- `
|
|
165
|
+
- `CustomField` - Custom field data
|
|
166
166
|
|
|
167
167
|
### Wishlist Types
|
|
168
168
|
- `Wishlist` - Wishlist
|
|
@@ -30,4 +30,4 @@ const nike = await client.request<Brand>('GET', '/brands/nike')
|
|
|
30
30
|
|
|
31
31
|
`client.request` uses the same auth headers, retry logic, and locale/currency defaults as `client.products.list()` or any other built-in resource.
|
|
32
32
|
|
|
33
|
-
For a complete walkthrough — creating the API endpoints on the backend, defining TypeScript types, filtering, and expanding associations — see the [API](../tutorial/
|
|
33
|
+
For a complete walkthrough — creating the API endpoints on the backend, defining TypeScript types, filtering, and expanding associations — see the [API](../tutorial/model-and-api.md) and [SDK](../tutorial/storefront.md) tutorials.
|