@spree/docs 0.1.93 → 0.1.94
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/admin-api/authentication.md +8 -7
- package/dist/api-reference/admin-api/endpoints.md +348 -0
- package/dist/api-reference/admin-api/errors.md +2 -0
- package/dist/api-reference/admin-api/introduction.md +11 -0
- package/dist/api-reference/store.yaml +243 -232
- package/dist/developer/agentic/overview.md +1 -1
- package/dist/developer/cli/admin-api.md +146 -0
- package/dist/developer/cli/quickstart.md +40 -5
- package/dist/developer/core-concepts/addresses.md +32 -16
- package/dist/developer/core-concepts/adjustments.md +11 -5
- package/dist/developer/core-concepts/architecture.md +8 -8
- package/dist/developer/core-concepts/calculators.md +31 -51
- package/dist/developer/core-concepts/channels.md +13 -6
- package/dist/developer/core-concepts/customers.md +47 -23
- package/dist/developer/core-concepts/events.md +22 -17
- package/dist/developer/core-concepts/imports-exports.md +69 -14
- package/dist/developer/core-concepts/inventory.md +79 -1
- package/dist/developer/core-concepts/markets.md +64 -20
- package/dist/developer/core-concepts/media.md +43 -6
- package/dist/developer/core-concepts/metafields.md +76 -13
- package/dist/developer/core-concepts/orders.md +95 -17
- package/dist/developer/core-concepts/payments.md +14 -13
- package/dist/developer/core-concepts/pricing.md +95 -9
- package/dist/developer/core-concepts/products.md +192 -26
- package/dist/developer/core-concepts/promotions.md +61 -4
- package/dist/developer/core-concepts/reports.md +4 -2
- package/dist/developer/core-concepts/search-filtering.md +82 -32
- package/dist/developer/core-concepts/shipments.md +16 -13
- package/dist/developer/core-concepts/slugs.md +20 -11
- package/dist/developer/core-concepts/staff-roles.md +51 -1
- package/dist/developer/core-concepts/store-credits-gift-cards.md +90 -9
- package/dist/developer/core-concepts/stores.md +16 -14
- package/dist/developer/core-concepts/taxes.md +28 -0
- package/dist/developer/core-concepts/translations.md +16 -7
- package/dist/developer/core-concepts/users.md +13 -9
- package/dist/developer/core-concepts/webhooks.md +95 -64
- package/dist/developer/how-to/custom-api-authentication.md +103 -23
- package/dist/developer/multi-store/quickstart.md +1 -1
- package/dist/developer/sdk/admin/authentication.md +1 -1
- package/dist/developer/sdk/admin/resources.md +2 -0
- package/dist/developer/upgrades/5.3-to-5.4.md +1 -1
- package/dist/developer/upgrades/5.4-to-5.5.md +1 -1
- package/dist/integrations/integrations.md +0 -7
- package/package.json +1 -1
- package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +0 -57
- package/dist/integrations/sso-mfa-social-login/storefront.md +0 -56
|
@@ -77,7 +77,7 @@ erDiagram
|
|
|
77
77
|
|
|
78
78
|
Refund {
|
|
79
79
|
decimal amount
|
|
80
|
-
|
|
80
|
+
integer refund_reason_id
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
LogEntry {
|
|
@@ -128,7 +128,7 @@ A `PaymentMethod` can have the following attributes:
|
|
|
128
128
|
| `name` | The visible name for this payment method | `Check` |
|
|
129
129
|
| `description`| The description for this payment method | `Pay by check.` |
|
|
130
130
|
| `active` | Whether or not this payment method is active. Set it `false` to hide it in the Store API. | `true` |
|
|
131
|
-
| `display_on` | Determines where the payment method can be visible. Values can be `
|
|
131
|
+
| `display_on` | Determines where the payment method can be visible. Values can be `front_end` (Store API), `back_end` (admin panel only), or `both` (both). | `both` |
|
|
132
132
|
| `position` | The position of the payment method in lists. Lower numbers appear first. | `1` |
|
|
133
133
|
|
|
134
134
|
> **INFO:** Each payment method is associated to a Store, so you can decide which Payment Method will appear on which Store. This allows you to create different experiences for your customers in different countries.
|
|
@@ -227,7 +227,7 @@ The frontend calls the API to create a Payment Session for a specific payment me
|
|
|
227
227
|
|
|
228
228
|
**Step 2: Customer pays on the frontend**
|
|
229
229
|
|
|
230
|
-
The frontend uses the gateway's JavaScript SDK (e.g., Stripe.js, Adyen Drop-in) with the `client_secret` to securely collect payment details. Card data never touches your server — it goes directly to the payment provider, ensuring
|
|
230
|
+
The frontend uses the gateway's JavaScript SDK (e.g., Stripe.js, Adyen Drop-in) with the `client_secret` to securely collect payment details. Card data never touches your server — it goes directly to the payment provider, ensuring [PCI compliance](../security/pci_compliance.md). If the payment requires **3D Secure** authentication or redirects to an offsite gateway (CashApp, Klarna, etc.), the gateway SDK handles it automatically.
|
|
231
231
|
|
|
232
232
|
**Step 3: Complete Payment Session**
|
|
233
233
|
|
|
@@ -291,14 +291,14 @@ sequenceDiagram
|
|
|
291
291
|
participant Frontend
|
|
292
292
|
participant Spree API
|
|
293
293
|
|
|
294
|
-
Frontend->>Spree API: GET /
|
|
295
|
-
Spree API-->>Frontend:
|
|
294
|
+
Frontend->>Spree API: GET /carts/:id
|
|
295
|
+
Spree API-->>Frontend: Cart (with embedded payment_methods, each carrying session_required)
|
|
296
296
|
|
|
297
297
|
Frontend->>Spree API: POST /payments (payment_method_id)
|
|
298
298
|
Spree API->>Spree API: Create Payment (checkout state)
|
|
299
299
|
Spree API-->>Frontend: Payment created
|
|
300
300
|
|
|
301
|
-
Frontend->>Spree API:
|
|
301
|
+
Frontend->>Spree API: POST /carts/:id/complete
|
|
302
302
|
Spree API->>Spree API: process_payments! (authorize succeeds immediately)
|
|
303
303
|
Spree API->>Spree API: Payment → pending/completed
|
|
304
304
|
Spree API-->>Frontend: Order completed
|
|
@@ -306,7 +306,7 @@ sequenceDiagram
|
|
|
306
306
|
|
|
307
307
|
**Step 1: List payment methods**
|
|
308
308
|
|
|
309
|
-
The frontend
|
|
309
|
+
The frontend reads the cart's embedded `payment_methods` (returned by `GET /carts/:id`) and checks the `session_required` flag on each method. Methods with `session_required: false` use this direct flow.
|
|
310
310
|
|
|
311
311
|
**Step 2: Create payment**
|
|
312
312
|
|
|
@@ -461,7 +461,7 @@ Once a Payment Session is completed, Spree creates a `Payment` record (`Spree::P
|
|
|
461
461
|
|
|
462
462
|
| Attribute | Description | Example Value |
|
|
463
463
|
|-------------------|-----------------------------------------------------------------------------|---------------------|
|
|
464
|
-
| `number` | A unique identifier for the payment. | `
|
|
464
|
+
| `number` | A unique identifier for the payment. | `PAB12CD34` |
|
|
465
465
|
| `source_type` | The type of source used for the payment. | `Spree::CreditCard` |
|
|
466
466
|
| `source_id` | The ID of the source used for the payment. | `1` |
|
|
467
467
|
| `amount` | The amount of the payment. | `99.99` |
|
|
@@ -590,12 +590,12 @@ sequenceDiagram
|
|
|
590
590
|
| `external_id` | The provider-side session ID (e.g., Stripe SetupIntent ID) | `seti_ABC123` |
|
|
591
591
|
| `external_client_secret` | Client secret for the frontend SDK | `seti_ABC123_secret_xyz` |
|
|
592
592
|
| `external_data` | Provider-specific data | `{}` |
|
|
593
|
-
| `payment_source_id` | The saved payment source created after completion | `
|
|
593
|
+
| `payment_source_id` | The saved payment source created after completion | `card_xyz789` |
|
|
594
594
|
| `payment_source_type` | The type of saved payment source | `Spree::CreditCard` |
|
|
595
595
|
|
|
596
596
|
### Payment Setup Session API
|
|
597
597
|
|
|
598
|
-
> **NOTE:** Payment Setup Sessions require customer authentication. The customer must be logged in.
|
|
598
|
+
> **NOTE:** Payment Setup Sessions require [customer authentication](../../api-reference/store-api/authentication.md). The customer must be logged in.
|
|
599
599
|
|
|
600
600
|
**Create a Payment Setup Session:**
|
|
601
601
|
|
|
@@ -624,7 +624,7 @@ console.log(setupSession.external_client_secret) // e.g. 'seti_ABC123_secret_xyz
|
|
|
624
624
|
"payment_method_id": "pm_xyz789",
|
|
625
625
|
"payment_source_id": null,
|
|
626
626
|
"payment_source_type": null,
|
|
627
|
-
"customer_id": "
|
|
627
|
+
"customer_id": "cus_def456"
|
|
628
628
|
}
|
|
629
629
|
```
|
|
630
630
|
|
|
@@ -643,7 +643,7 @@ const completed = await client.customer.paymentSetupSessions.complete(
|
|
|
643
643
|
options
|
|
644
644
|
)
|
|
645
645
|
console.log(completed.status) // 'completed'
|
|
646
|
-
console.log(completed.payment_source_id) // e.g. '
|
|
646
|
+
console.log(completed.payment_source_id) // e.g. 'card_xyz789' - the saved payment method
|
|
647
647
|
```
|
|
648
648
|
|
|
649
649
|
Spree will verify the result with the provider and create a saved payment source (e.g., `Spree::CreditCard`) that can be used for future payments.
|
|
@@ -660,7 +660,7 @@ Spree team maintains several payment gateway integrations. All of these gateways
|
|
|
660
660
|
|
|
661
661
|
## Payment Events
|
|
662
662
|
|
|
663
|
-
Spree publishes events throughout the payment lifecycle that you can subscribe to:
|
|
663
|
+
Spree publishes events throughout the payment lifecycle that you can subscribe to. For the delivered payload schemas of these events (e.g. `payment.paid`, `payment_session.completed`), see the [Webhooks & Events reference](../../api-reference/webhooks-events.md):
|
|
664
664
|
|
|
665
665
|
### Payment Events
|
|
666
666
|
| Event | Description |
|
|
@@ -688,6 +688,7 @@ See [Events](events.md) for more details on subscribing to events.
|
|
|
688
688
|
|
|
689
689
|
## Related Documentation
|
|
690
690
|
|
|
691
|
+
- [Payments (Store SDK)](../sdk/store/payments.md) - SDK how-to for payment sessions, payments, and setup sessions
|
|
691
692
|
- [Build a Custom Payment Method](../how-to/custom-payment-method.md) - Step-by-step guide to creating your own payment gateway integration
|
|
692
693
|
- [Orders](orders.md) - Order management and state machine
|
|
693
694
|
- [Checkout Customization](../customization/checkout.md) - Customizing the checkout flow
|
|
@@ -11,7 +11,7 @@ Spree's pricing system supports both simple single-currency pricing and advanced
|
|
|
11
11
|
|
|
12
12
|
## Prices
|
|
13
13
|
|
|
14
|
-
Each variant has one or more `Price` records — one per currency. The API automatically returns the correct price based on the current currency and [Market](markets.md) context.
|
|
14
|
+
Each variant has one or more `Price` records — one per currency. The API automatically returns the correct price based on the current currency and [Market](markets.md) context. See [monetary amounts](../../api-reference/store-api/monetary-amounts.md) for how money fields (decimal, cents, and display string) are formatted across the API.
|
|
15
15
|
|
|
16
16
|
| Attribute | Description | Example |
|
|
17
17
|
|-----------|-------------|---------|
|
|
@@ -26,19 +26,25 @@ Each variant has one or more `Price` records — one per currency. The API autom
|
|
|
26
26
|
The Store API returns the resolved price (including Price List rules) for the current currency and market context:
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
```typescript SDK
|
|
29
|
+
```typescript Store SDK
|
|
30
30
|
// Product price is included by default
|
|
31
31
|
const product = await client.products.get('spree-tote')
|
|
32
|
-
console.log(product.price)
|
|
33
|
-
console.log(product.
|
|
32
|
+
console.log(product.price.amount) // "15.99" — resolved for current currency
|
|
33
|
+
console.log(product.price.compare_at_amount) // "19.99" — compare-at/strikethrough price (if set)
|
|
34
34
|
|
|
35
35
|
// Each variant has its own price
|
|
36
36
|
const detailed = await client.products.get('spree-tote', {
|
|
37
37
|
expand: ['variants'],
|
|
38
38
|
})
|
|
39
39
|
detailed.variants?.forEach(variant => {
|
|
40
|
-
console.log(variant.price)
|
|
41
|
-
console.log(variant.
|
|
40
|
+
console.log(variant.price.amount) // "15.99"
|
|
41
|
+
console.log(variant.price.compare_at_amount) // "19.99"
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```typescript Admin SDK
|
|
46
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z', {
|
|
47
|
+
expand: ['variants'],
|
|
42
48
|
})
|
|
43
49
|
```
|
|
44
50
|
|
|
@@ -53,6 +59,41 @@ curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=variants'
|
|
|
53
59
|
```
|
|
54
60
|
|
|
55
61
|
|
|
62
|
+
### Setting Prices via the Admin API
|
|
63
|
+
|
|
64
|
+
Base prices are set per variant per currency. Use the [Admin API](../../api-reference/admin-api/introduction.md) — via the [Admin SDK](../sdk/admin/quickstart.md) — to set them individually or in bulk (`bulk_upsert` matches on variant + currency):
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
```typescript Admin SDK
|
|
68
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
69
|
+
|
|
70
|
+
const client = createAdminClient({
|
|
71
|
+
baseUrl: 'https://store.example.com',
|
|
72
|
+
secretKey: 'sk_xxx',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Set / change one variant's price in a currency
|
|
76
|
+
await client.prices.create({ variant_id: 'variant_xxx', currency: 'USD', amount: 15.99 })
|
|
77
|
+
|
|
78
|
+
// Upsert many at once
|
|
79
|
+
await client.prices.bulkUpsert({
|
|
80
|
+
prices: [
|
|
81
|
+
{ variant_id: 'variant_xxx', currency: 'USD', amount: 15.99 },
|
|
82
|
+
{ variant_id: 'variant_xxx', currency: 'EUR', amount: 14.99 },
|
|
83
|
+
],
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```bash CLI
|
|
88
|
+
spree api post /prices/bulk_upsert -d '{
|
|
89
|
+
"prices": [
|
|
90
|
+
{ "variant_id": "variant_xxx", "currency": "USD", "amount": 15.99 },
|
|
91
|
+
{ "variant_id": "variant_xxx", "currency": "EUR", "amount": 14.99 }
|
|
92
|
+
]
|
|
93
|
+
}'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
56
97
|
## Price Lists
|
|
57
98
|
|
|
58
99
|
Price Lists allow you to create different pricing strategies based on various conditions. This enables advanced pricing scenarios like:
|
|
@@ -86,6 +127,44 @@ If no applicable Price List is found, the base price is used.
|
|
|
86
127
|
| `match_policy` | How rules are evaluated: `all` (every rule must match) or `any` (at least one) |
|
|
87
128
|
| `position` | Priority order (lower numbers = higher priority) |
|
|
88
129
|
|
|
130
|
+
### Creating a Price List via the Admin API
|
|
131
|
+
|
|
132
|
+
Rules and per-variant price overrides ride on the create/update payload. Each rule is a `{ type, preferences }` draft (see [Price Rules](#price-rules) below):
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
```typescript Admin SDK
|
|
136
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
137
|
+
|
|
138
|
+
const client = createAdminClient({
|
|
139
|
+
baseUrl: 'https://store.example.com',
|
|
140
|
+
secretKey: 'sk_xxx',
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const priceList = await client.priceLists.create({
|
|
144
|
+
name: 'EU wholesale',
|
|
145
|
+
match_policy: 'all',
|
|
146
|
+
rules: [
|
|
147
|
+
{ type: 'customer_group_rule', preferences: { customer_group_ids: ['cg_xxx'] } },
|
|
148
|
+
],
|
|
149
|
+
prices: [
|
|
150
|
+
{ variant_id: 'variant_xxx', currency: 'EUR', amount: '19.99' },
|
|
151
|
+
],
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Price Lists are draft until activated
|
|
155
|
+
await client.priceLists.activate(priceList.id)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```bash CLI
|
|
159
|
+
spree api post /price_lists -d '{
|
|
160
|
+
"name": "EU wholesale",
|
|
161
|
+
"match_policy": "all",
|
|
162
|
+
"prices": [{ "variant_id": "variant_xxx", "currency": "EUR", "amount": "19.99" }]
|
|
163
|
+
}'
|
|
164
|
+
spree api patch /price_lists/pl_xxx/activate
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
|
|
89
168
|
## Price Rules
|
|
90
169
|
|
|
91
170
|
Price Rules define conditions that must be met for a Price List to apply. Spree includes five built-in rule types:
|
|
@@ -141,9 +220,9 @@ When resolving prices, Spree considers the full context of the request:
|
|
|
141
220
|
| Quantity | Cart line item | The quantity for volume-based rules |
|
|
142
221
|
| Date | Current time | For time-based Price List scheduling |
|
|
143
222
|
|
|
144
|
-
The Store API automatically builds this context from the request headers (`X-Spree-Currency`, `X-Spree-Country`) and authentication state. You don't need to construct it manually — just make API requests and the correct price is resolved.
|
|
223
|
+
The Store API automatically builds this context from the [request headers](../../api-reference/store-api/localization.md) (`X-Spree-Currency`, `X-Spree-Country`) and authentication state. You don't need to construct it manually — just make API requests and the correct price is resolved.
|
|
145
224
|
|
|
146
|
-
> **INFO:** Price resolution
|
|
225
|
+
> **INFO:** Price resolution runs per request — there is no in-process caching of resolved prices. The Store API instead relies on HTTP/CDN caching, with `Vary` headers keyed on the `X-Spree-Currency` and `X-Spree-Locale` request headers so each currency/locale combination is cached separately at the edge.
|
|
147
226
|
|
|
148
227
|
## Time-Based Pricing
|
|
149
228
|
|
|
@@ -170,7 +249,7 @@ Every time a base price amount changes, a `PriceHistory` record is created autom
|
|
|
170
249
|
The prior price is available as an expandable field on product and variant endpoints:
|
|
171
250
|
|
|
172
251
|
|
|
173
|
-
```typescript SDK
|
|
252
|
+
```typescript Store SDK
|
|
174
253
|
const product = await client.products.get('spree-tote', {
|
|
175
254
|
expand: ['prior_price'],
|
|
176
255
|
})
|
|
@@ -183,6 +262,12 @@ if (product.prior_price) {
|
|
|
183
262
|
}
|
|
184
263
|
```
|
|
185
264
|
|
|
265
|
+
```typescript Admin SDK
|
|
266
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z', {
|
|
267
|
+
expand: ['prior_price'],
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
186
271
|
```bash cURL
|
|
187
272
|
curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=prior_price' \
|
|
188
273
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -247,6 +332,7 @@ bundle exec rake spree:price_history:seed
|
|
|
247
332
|
## Related Documentation
|
|
248
333
|
|
|
249
334
|
- [Products](products.md) — Products, Variants, and base prices
|
|
335
|
+
- [Store SDK: Products](../sdk/store/products.md) — Fetching product and variant prices with the Store SDK
|
|
250
336
|
- [Markets](markets.md) — Geographic regions with currency and locale
|
|
251
337
|
- [Taxes](taxes.md) — Tax categories, tax rates, and zones
|
|
252
338
|
- [Promotions](promotions.md) — Discount-based pricing via promotion rules
|
|
@@ -79,7 +79,7 @@ erDiagram
|
|
|
79
79
|
## Listing Products
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
```typescript SDK
|
|
82
|
+
```typescript Store SDK
|
|
83
83
|
// List products with pagination
|
|
84
84
|
const { data: products, meta } = await client.products.list({
|
|
85
85
|
limit: 12,
|
|
@@ -100,7 +100,14 @@ const results = await client.products.list({
|
|
|
100
100
|
|
|
101
101
|
// Sort products
|
|
102
102
|
const sorted = await client.products.list({
|
|
103
|
-
sort: '
|
|
103
|
+
sort: '-price', // high to low; also: price, name, -name, available_on, -available_on, best_selling
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```typescript Admin SDK
|
|
108
|
+
const { data: products, meta } = await adminClient.products.list({
|
|
109
|
+
limit: 12,
|
|
110
|
+
page: 1,
|
|
104
111
|
})
|
|
105
112
|
```
|
|
106
113
|
|
|
@@ -124,7 +131,7 @@ See [Querying](../../api-reference/store-api/querying.md) for the full list of f
|
|
|
124
131
|
## Getting a Product
|
|
125
132
|
|
|
126
133
|
|
|
127
|
-
```typescript SDK
|
|
134
|
+
```typescript Store SDK
|
|
128
135
|
// Get by slug
|
|
129
136
|
const product = await client.products.get('spree-tote')
|
|
130
137
|
|
|
@@ -133,8 +140,13 @@ const detailed = await client.products.get('spree-tote', {
|
|
|
133
140
|
expand: ['variants', 'media', 'option_types', 'categories'],
|
|
134
141
|
})
|
|
135
142
|
// detailed.variants => [{ id: "var_xxx", sku: "TOTE-S-R", price: { amount: "15.99", currency: "USD" }, ... }]
|
|
136
|
-
// detailed.media => [{ id: "img_xxx",
|
|
137
|
-
// detailed.option_types => [{ name: "size",
|
|
143
|
+
// detailed.media => [{ id: "img_xxx", original_url: "https://cdn...", position: 1 }]
|
|
144
|
+
// detailed.option_types => [{ name: "size", label: "Size", position: 1, kind: "..." }]
|
|
145
|
+
// detailed.option_values => [{ name: "small", label: "S", option_type_name: "size", ... }] // separate top-level array when expanded
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```typescript Admin SDK
|
|
149
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z')
|
|
138
150
|
```
|
|
139
151
|
|
|
140
152
|
```bash cURL
|
|
@@ -143,17 +155,91 @@ curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=variants,m
|
|
|
143
155
|
```
|
|
144
156
|
|
|
145
157
|
|
|
158
|
+
Pass `expand` to include related resources in a single response — see [expand relations](../../api-reference/store-api/relations.md) for how relation inclusion works.
|
|
159
|
+
|
|
160
|
+
## Managing Products
|
|
161
|
+
|
|
162
|
+
The examples above use the **Store API** (publishable key, read-only, customer-facing). To **create and manage** products, use the [Admin API](../../api-reference/admin-api/introduction.md) — via the [Admin SDK](../sdk/admin/quickstart.md) or the [Spree CLI](../cli/admin-api.md).
|
|
163
|
+
|
|
164
|
+
A product's purchasable attributes (SKU, prices, stock) live on its **variants**, which you can create inline:
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
```typescript Admin SDK
|
|
168
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
169
|
+
|
|
170
|
+
const client = createAdminClient({
|
|
171
|
+
baseUrl: 'https://store.example.com',
|
|
172
|
+
secretKey: 'sk_xxx',
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const product = await client.products.create({
|
|
176
|
+
name: 'Premium T-Shirt',
|
|
177
|
+
description: 'Soft, organic cotton.',
|
|
178
|
+
status: 'active',
|
|
179
|
+
variants: [
|
|
180
|
+
{
|
|
181
|
+
sku: 'TSHIRT-S-NAVY',
|
|
182
|
+
options: [
|
|
183
|
+
{ name: 'size', value: 'Small' },
|
|
184
|
+
{ name: 'color', value: 'navy' },
|
|
185
|
+
],
|
|
186
|
+
prices: [{ currency: 'USD', amount: 29.99 }],
|
|
187
|
+
stock_items: [{ stock_location_id: 'sloc_xxx', count_on_hand: 50 }],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```bash CLI
|
|
194
|
+
spree api post /products -d '{
|
|
195
|
+
"name": "Premium T-Shirt",
|
|
196
|
+
"status": "active",
|
|
197
|
+
"variants": [{
|
|
198
|
+
"sku": "TSHIRT-S-NAVY",
|
|
199
|
+
"options": [{ "name": "size", "value": "Small" }],
|
|
200
|
+
"prices": [{ "currency": "USD", "amount": 29.99 }]
|
|
201
|
+
}]
|
|
202
|
+
}'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
Update, clone, or archive a product (deleting soft-deletes it):
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
```typescript Admin SDK
|
|
210
|
+
await client.products.update('prod_xxx', { name: 'Premium Tee', status: 'active' })
|
|
211
|
+
await client.products.clone('prod_xxx') // duplicate as a new draft
|
|
212
|
+
await client.products.delete('prod_xxx') // soft-delete
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```bash CLI
|
|
216
|
+
spree api patch /products/prod_xxx -d '{"name": "Premium Tee"}'
|
|
217
|
+
spree api post /products/prod_xxx/clone
|
|
218
|
+
spree api delete /products/prod_xxx
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
> **TIP:** Operating on many products at once? The Admin API has bulk actions — `bulkStatusUpdate`, `bulkAddToCategories`, `bulkAddTags`, `bulkDestroy`, and more. See the [Admin API endpoint index](../../api-reference/admin-api/endpoints.md).
|
|
223
|
+
|
|
146
224
|
## Product Filters
|
|
147
225
|
|
|
148
226
|
Get available filter options for building a faceted search UI. Returns price ranges, option values, and categories with counts:
|
|
149
227
|
|
|
150
228
|
|
|
151
|
-
```typescript SDK
|
|
229
|
+
```typescript Store SDK
|
|
152
230
|
const filters = await client.products.filters()
|
|
153
231
|
// {
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
232
|
+
// filters: [
|
|
233
|
+
// { id: "price", type: "price_range", min: 9.99, max: 199.99, currency: "USD" },
|
|
234
|
+
// { id: "availability", type: "availability", options: [{ id: "in_stock", count: 42 }] },
|
|
235
|
+
// { id: "opt_xxx", type: "option", name: "size", label: "Size", kind: "...",
|
|
236
|
+
// options: [{ id: "optv_xxx", name: "small", label: "Small", count: 12 }, ...] },
|
|
237
|
+
// { id: "categories", type: "category",
|
|
238
|
+
// options: [{ id: "ctg_xxx", name: "Clothing", permalink: "clothing", count: 45 }] },
|
|
239
|
+
// ],
|
|
240
|
+
// sort_options: [{ id: "price" }, ...],
|
|
241
|
+
// default_sort: "best_selling",
|
|
242
|
+
// total_count: 120,
|
|
157
243
|
// }
|
|
158
244
|
|
|
159
245
|
// Scoped to a specific category
|
|
@@ -206,6 +292,30 @@ When a product has option types, each unique combination of option values create
|
|
|
206
292
|
|
|
207
293
|
The product's `default_variant_id` points to the first non-master variant (or the master variant if none exist).
|
|
208
294
|
|
|
295
|
+
Add a variant to an existing product via the Admin API (SKU, prices, and stock all live on the variant):
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
```typescript Admin SDK
|
|
299
|
+
const variant = await client.products.variants.create('prod_xxx', {
|
|
300
|
+
sku: 'TEE-L-R',
|
|
301
|
+
options: [
|
|
302
|
+
{ name: 'size', value: 'Large' },
|
|
303
|
+
{ name: 'color', value: 'Red' },
|
|
304
|
+
],
|
|
305
|
+
prices: [{ currency: 'USD', amount: 24.99 }],
|
|
306
|
+
stock_items: [{ stock_location_id: 'sloc_xxx', count_on_hand: 30 }],
|
|
307
|
+
})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```bash CLI
|
|
311
|
+
spree api post /products/prod_xxx/variants -d '{
|
|
312
|
+
"sku": "TEE-L-R",
|
|
313
|
+
"options": [{ "name": "size", "value": "Large" }],
|
|
314
|
+
"prices": [{ "currency": "USD", "amount": 24.99 }]
|
|
315
|
+
}'
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
|
|
209
319
|
## Option Types and Option Values
|
|
210
320
|
|
|
211
321
|
Option types define the axes of variation for a product (e.g., Size, Color, Material). Option values are the specific choices within each type (e.g., Small, Medium, Large).
|
|
@@ -213,26 +323,62 @@ Option types define the axes of variation for a product (e.g., Size, Color, Mate
|
|
|
213
323
|
A product must have at least one option type to have multiple variants. Option types and their values are included in the product response when requested:
|
|
214
324
|
|
|
215
325
|
|
|
216
|
-
```typescript SDK
|
|
326
|
+
```typescript Store SDK
|
|
217
327
|
const product = await client.products.get('spree-tee', {
|
|
218
|
-
expand: ['option_types'],
|
|
328
|
+
expand: ['option_types', 'option_values'],
|
|
219
329
|
})
|
|
220
330
|
|
|
331
|
+
// Option types describe the axes of variation
|
|
221
332
|
product.option_types?.forEach(optionType => {
|
|
222
|
-
console.log(optionType.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
333
|
+
console.log(optionType.label) // "Size"
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// Option values are a separate flat array; each carries its parent's id/label
|
|
337
|
+
product.option_values?.forEach(value => {
|
|
338
|
+
console.log(value.option_type_label, value.label) // "Size", "Small"
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```typescript Admin SDK
|
|
343
|
+
const product = await adminClient.products.get('prod_k5nR8xLq', {
|
|
344
|
+
expand: ['variants'],
|
|
226
345
|
})
|
|
227
346
|
```
|
|
228
347
|
|
|
229
348
|
```bash cURL
|
|
230
|
-
curl 'https://api.mystore.com/api/v3/store/products/spree-tee?expand=option_types' \
|
|
349
|
+
curl 'https://api.mystore.com/api/v3/store/products/spree-tee?expand=option_types,option_values' \
|
|
231
350
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
232
351
|
```
|
|
233
352
|
|
|
234
353
|
|
|
235
|
-
> **INFO:** Option type `name` and `
|
|
354
|
+
> **INFO:** Option type `name` and `label` fields are translatable.
|
|
355
|
+
|
|
356
|
+
Create option types (and their values) via the Admin API. Sending `option_values` replaces the full set, so include every value you want to keep:
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
```typescript Admin SDK
|
|
360
|
+
const optionType = await client.optionTypes.create({
|
|
361
|
+
name: 'size',
|
|
362
|
+
label: 'Size',
|
|
363
|
+
option_values: [
|
|
364
|
+
{ name: 'small', label: 'Small', position: 1 },
|
|
365
|
+
{ name: 'medium', label: 'Medium', position: 2 },
|
|
366
|
+
{ name: 'large', label: 'Large', position: 3 },
|
|
367
|
+
],
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```bash CLI
|
|
372
|
+
spree api post /option_types -d '{
|
|
373
|
+
"name": "size",
|
|
374
|
+
"label": "Size",
|
|
375
|
+
"option_values": [
|
|
376
|
+
{ "name": "small", "label": "Small" },
|
|
377
|
+
{ "name": "medium", "label": "Medium" }
|
|
378
|
+
]
|
|
379
|
+
}'
|
|
380
|
+
```
|
|
381
|
+
|
|
236
382
|
|
|
237
383
|
## Media
|
|
238
384
|
|
|
@@ -245,7 +391,7 @@ Every product response includes a `thumbnail_url` field — the URL to the first
|
|
|
245
391
|
Use these fields for product listing pages to avoid loading all images:
|
|
246
392
|
|
|
247
393
|
|
|
248
|
-
```typescript SDK
|
|
394
|
+
```typescript Store SDK
|
|
249
395
|
// List products — thumbnail_url is always included
|
|
250
396
|
const { data: products } = await client.products.list({ limit: 12 })
|
|
251
397
|
|
|
@@ -254,6 +400,10 @@ products.forEach(product => {
|
|
|
254
400
|
})
|
|
255
401
|
```
|
|
256
402
|
|
|
403
|
+
```typescript Admin SDK
|
|
404
|
+
const { data: products } = await adminClient.products.list({ limit: 12 })
|
|
405
|
+
```
|
|
406
|
+
|
|
257
407
|
```bash cURL
|
|
258
408
|
# thumbnail_url is always in the response — no ?expand needed
|
|
259
409
|
curl 'https://api.mystore.com/api/v3/store/products?limit=12' \
|
|
@@ -268,22 +418,28 @@ curl 'https://api.mystore.com/api/v3/store/products?limit=12' \
|
|
|
268
418
|
On the product detail page, expand `media` and `variants` to get the full set of images. Images are ordered by `position`:
|
|
269
419
|
|
|
270
420
|
|
|
271
|
-
```typescript SDK
|
|
421
|
+
```typescript Store SDK
|
|
272
422
|
const product = await client.products.get('spree-tote', {
|
|
273
423
|
expand: ['media', 'variants'],
|
|
274
424
|
})
|
|
275
425
|
|
|
276
426
|
// Product-level images (from master variant)
|
|
277
|
-
product.media // [{
|
|
427
|
+
product.media // [{ original_url: "https://cdn.../tote-front.jpg", position: 1 }, ...]
|
|
278
428
|
|
|
279
429
|
// Each variant has its own thumbnail and media_count
|
|
280
430
|
product.variants?.forEach(variant => {
|
|
281
|
-
variant.
|
|
431
|
+
variant.thumbnail_url // "https://cdn.../tote-red.jpg" — always available
|
|
282
432
|
variant.media_count // 3 — quick check without loading media
|
|
283
433
|
variant.media // full image array (only when ?expand=media)
|
|
284
434
|
})
|
|
285
435
|
```
|
|
286
436
|
|
|
437
|
+
```typescript Admin SDK
|
|
438
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z', {
|
|
439
|
+
expand: ['media'],
|
|
440
|
+
})
|
|
441
|
+
```
|
|
442
|
+
|
|
287
443
|
```bash cURL
|
|
288
444
|
curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=media,variants' \
|
|
289
445
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -322,7 +478,7 @@ For example:
|
|
|
322
478
|
Products can belong to multiple categories.
|
|
323
479
|
|
|
324
480
|
|
|
325
|
-
```typescript SDK
|
|
481
|
+
```typescript Store SDK
|
|
326
482
|
// List categories
|
|
327
483
|
const { data: categories } = await client.categories.list()
|
|
328
484
|
|
|
@@ -335,6 +491,10 @@ const { data: products } = await client.categories.products.list('clothing/shirt
|
|
|
335
491
|
})
|
|
336
492
|
```
|
|
337
493
|
|
|
494
|
+
```typescript Admin SDK
|
|
495
|
+
const { data: categories } = await adminClient.categories.list()
|
|
496
|
+
```
|
|
497
|
+
|
|
338
498
|
```bash cURL
|
|
339
499
|
# List categories
|
|
340
500
|
curl 'https://api.mystore.com/api/v3/store/categories' \
|
|
@@ -371,7 +531,7 @@ Product `status` (`draft` / `active` / `archived`) is the **outer gate**: a Draf
|
|
|
371
531
|
Publications appear in the API under `product_publications` when expanded; the same data is available through the `channels` association as a flat list of joined channels.
|
|
372
532
|
|
|
373
533
|
|
|
374
|
-
```typescript SDK
|
|
534
|
+
```typescript Admin SDK
|
|
375
535
|
const product = await adminClient.products.get('prod_abc', {
|
|
376
536
|
expand: ['product_publications', 'channels'],
|
|
377
537
|
})
|
|
@@ -411,7 +571,7 @@ Two write surfaces serve different shapes:
|
|
|
411
571
|
|
|
412
572
|
|
|
413
573
|
|
|
414
|
-
```typescript SDK
|
|
574
|
+
```typescript Admin SDK
|
|
415
575
|
await adminClient.products.update('prod_abc', {
|
|
416
576
|
product_publications: [
|
|
417
577
|
{ channel_id: 'ch_online' },
|
|
@@ -438,7 +598,7 @@ Two write surfaces serve different shapes:
|
|
|
438
598
|
|
|
439
599
|
|
|
440
600
|
|
|
441
|
-
```typescript SDK
|
|
601
|
+
```typescript Admin SDK
|
|
442
602
|
await adminClient.channels.addProducts('ch_online', {
|
|
443
603
|
product_ids: ['prod_abc', 'prod_def'],
|
|
444
604
|
published_at: '2026-07-01T00:00:00Z',
|
|
@@ -473,7 +633,7 @@ The two surfaces converge on the same `spree_product_publications` table — pic
|
|
|
473
633
|
Storefronts and `client.products.list()` calls return only products published on the resolved channel (live within the publication window, with the product itself `active`). To scope a Store SDK request to a non-default channel — e.g. a POS app querying for the POS catalog — set the channel `code` on the client or per-request:
|
|
474
634
|
|
|
475
635
|
|
|
476
|
-
```typescript SDK
|
|
636
|
+
```typescript Store SDK
|
|
477
637
|
// Client-level default
|
|
478
638
|
const client = createClient({ baseUrl, publishableKey, channel: 'pos' })
|
|
479
639
|
|
|
@@ -481,6 +641,11 @@ const client = createClient({ baseUrl, publishableKey, channel: 'pos' })
|
|
|
481
641
|
const posProducts = await client.products.list({}, { channel: 'pos' })
|
|
482
642
|
```
|
|
483
643
|
|
|
644
|
+
```typescript Admin SDK
|
|
645
|
+
// filter by the channel's code via Ransack (q[channels_code_eq])
|
|
646
|
+
const { data: products } = await adminClient.products.list({ channels_code_eq: 'pos' })
|
|
647
|
+
```
|
|
648
|
+
|
|
484
649
|
```bash cURL
|
|
485
650
|
curl 'https://api.mystore.com/api/v3/store/products' \
|
|
486
651
|
-H 'X-Spree-API-Key: pk_xxx' \
|
|
@@ -504,4 +669,5 @@ See [Sales Channels](channels.md) for the full channel lifecycle, including defa
|
|
|
504
669
|
- [Media](media.md) — Image management
|
|
505
670
|
- [Translations](translations.md) — Translating product content
|
|
506
671
|
- [Search & Filtering](search-filtering.md) — Full-text search and Ransack filtering
|
|
672
|
+
- [Store SDK Products](../sdk/store/products.md) — Listing, fetching, filtering, and categories via `client.products`
|
|
507
673
|
- [Querying](../../api-reference/store-api/querying.md) — API filtering, sorting, and pagination
|