@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
|
@@ -194,7 +194,7 @@ Limits the promotion to orders in a specific currency.
|
|
|
194
194
|
Requires products with specific option values (e.g., size, color) to be in the order.
|
|
195
195
|
|
|
196
196
|
**Configuration:**
|
|
197
|
-
- `preferred_eligible_values` - Array of
|
|
197
|
+
- `preferred_eligible_values` - Array of `Spree::OptionValueVariant` IDs (the option-value↔variant join records the rule matches against the order's variants), not variant IDs
|
|
198
198
|
|
|
199
199
|
**Use case:** "10% off all red items", "Discount on size XL".
|
|
200
200
|
|
|
@@ -255,6 +255,61 @@ The action checks stock availability before adding items. Items are not automati
|
|
|
255
255
|
|
|
256
256
|
**Use case:** "Free gift with purchase", "Buy 2 get 1 free", "Spend $100 get free sample".
|
|
257
257
|
|
|
258
|
+
## Managing Promotions
|
|
259
|
+
|
|
260
|
+
Create and manage promotions via the [Admin API](../../api-reference/admin-api/introduction.md). Rules and actions can be supplied inline on create — each is a `{ type, preferences }` draft using the rule/action types described above:
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
```typescript Admin SDK
|
|
264
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
265
|
+
|
|
266
|
+
const client = createAdminClient({
|
|
267
|
+
baseUrl: 'https://store.example.com',
|
|
268
|
+
secretKey: 'sk_xxx',
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
// "20% off orders over $100 with code SUMMER20"
|
|
272
|
+
const promotion = await client.promotions.create({
|
|
273
|
+
name: 'Summer Sale',
|
|
274
|
+
code: 'SUMMER20',
|
|
275
|
+
kind: 'coupon_code',
|
|
276
|
+
rules: [{ type: 'item_total', preferences: { amount_min: 100 } }],
|
|
277
|
+
actions: [
|
|
278
|
+
{
|
|
279
|
+
type: 'create_adjustment',
|
|
280
|
+
calculator: { type: 'flat_percent_item_total', preferences: { flat_percent: 20 } },
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```bash CLI
|
|
287
|
+
spree api post /promotions -d '{
|
|
288
|
+
"name": "Summer Sale",
|
|
289
|
+
"code": "SUMMER20",
|
|
290
|
+
"kind": "coupon_code",
|
|
291
|
+
"rules": [{ "type": "item_total", "preferences": { "amount_min": 100 } }]
|
|
292
|
+
}'
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
You can also add rules and actions to an existing promotion, or update and remove the promotion itself:
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
```typescript Admin SDK
|
|
300
|
+
await client.promotions.rules.create('promo_xxx', {
|
|
301
|
+
type: 'first_order',
|
|
302
|
+
})
|
|
303
|
+
await client.promotions.update('promo_xxx', { expires_at: '2026-09-01T00:00:00Z' })
|
|
304
|
+
await client.promotions.delete('promo_xxx')
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```bash CLI
|
|
308
|
+
spree api post /promotions/promo_xxx/promotion_rules -d '{"type": "first_order"}'
|
|
309
|
+
spree api patch /promotions/promo_xxx -d '{"expires_at": "2026-09-01T00:00:00Z"}'
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
|
|
258
313
|
## Coupon Codes
|
|
259
314
|
|
|
260
315
|
Coupon codes track promotion usage and can be single-use or multi-use.
|
|
@@ -274,10 +329,10 @@ For bulk-generated codes, each code tracks its usage state:
|
|
|
274
329
|
|
|
275
330
|
## Applying Coupon Codes via Store API
|
|
276
331
|
|
|
277
|
-
Customers apply coupon codes during checkout via the Store API:
|
|
332
|
+
Customers [apply coupon codes during checkout](../sdk/store/cart-checkout.md) via the Store API:
|
|
278
333
|
|
|
279
334
|
|
|
280
|
-
```typescript SDK
|
|
335
|
+
```typescript Store SDK
|
|
281
336
|
// Apply a discount code to the cart
|
|
282
337
|
const cart = await client.carts.discountCodes.apply('cart_abc123', 'SUMMER20', {
|
|
283
338
|
spreeToken: '<token>',
|
|
@@ -291,7 +346,7 @@ await client.carts.discountCodes.remove('cart_abc123', 'SUMMER20', {
|
|
|
291
346
|
|
|
292
347
|
```bash cURL
|
|
293
348
|
# Apply a coupon code
|
|
294
|
-
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/
|
|
349
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/discount_codes' \
|
|
295
350
|
-H 'X-Spree-API-Key: pk_xxx' \
|
|
296
351
|
-H 'X-Spree-Token: <token>' \
|
|
297
352
|
-H 'Content-Type: application/json' \
|
|
@@ -320,3 +375,5 @@ How promotions are evaluated and applied:
|
|
|
320
375
|
- [Calculators](calculators.md) - Learn about promotion calculators
|
|
321
376
|
- [Orders](orders.md) - Understanding order processing
|
|
322
377
|
- [Customization Quickstart](../customization/quickstart.md) - Overview of customization options
|
|
378
|
+
- [Admin SDK Resources](../sdk/admin/resources.md) - The `client.promotions.*` resource-client pattern used in the examples above
|
|
379
|
+
- [Spree CLI Admin API](../cli/admin-api.md) - The `spree api` command reference for the CLI examples above
|
|
@@ -100,7 +100,7 @@ The Products Performance report aggregates sales metrics by product for the spec
|
|
|
100
100
|
### Generation Flow
|
|
101
101
|
|
|
102
102
|
1. **User creates report** via admin UI with date range, currency, and optional vendor
|
|
103
|
-
2. **Report is saved** to database
|
|
103
|
+
2. **Report is saved** to the database
|
|
104
104
|
3. **`report.created` event fires** via `publishes_lifecycle_events` concern
|
|
105
105
|
4. **`Spree::ReportSubscriber` catches event** and enqueues `GenerateJob`
|
|
106
106
|
5. **Background job runs `report.generate`**:
|
|
@@ -184,7 +184,7 @@ Spree::Admin::RuntimeConfig.reports_line_items_limit = 100
|
|
|
184
184
|
|
|
185
185
|
### Background Job Queue
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
By default, report jobs run on the `:default` queue. To route them to a dedicated queue, configure your job processor:
|
|
188
188
|
|
|
189
189
|
```ruby
|
|
190
190
|
# Sidekiq example
|
|
@@ -204,3 +204,5 @@ can :manage, Spree::Report
|
|
|
204
204
|
|
|
205
205
|
- [Build a Custom Report](../how-to/custom-report.md) - Step-by-step guide to creating custom reports
|
|
206
206
|
- [Events](events.md) - How report generation uses the events system
|
|
207
|
+
- [Imports & Exports](imports-exports.md) - The broader async-export, ActiveStorage-attachment pattern reports share
|
|
208
|
+
- [Admin API Endpoints](../../api-reference/admin-api/endpoints.md) - Endpoints for creating and downloading reports from an integration
|
|
@@ -9,7 +9,7 @@ import { Since } from '/snippets/since.mdx';
|
|
|
9
9
|
|
|
10
10
|
Spree provides powerful search, filtering, and sorting capabilities for products and other resources. The Store API supports:
|
|
11
11
|
|
|
12
|
-
- Full-text search across product names
|
|
12
|
+
- Full-text search across product names and SKUs
|
|
13
13
|
- Attribute-based filtering (price range, availability, stock status)
|
|
14
14
|
- Category and taxon filtering
|
|
15
15
|
- Faceted search with filter counts
|
|
@@ -20,13 +20,20 @@ Spree provides powerful search, filtering, and sorting capabilities for products
|
|
|
20
20
|
Use the `search` parameter for full-text search across product fields:
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
```typescript SDK
|
|
23
|
+
```typescript Store SDK
|
|
24
24
|
const { data: products } = await client.products.list({
|
|
25
25
|
search: 'tote bag',
|
|
26
26
|
limit: 12,
|
|
27
27
|
})
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
```typescript Admin SDK
|
|
31
|
+
const { data: products } = await adminClient.products.list({
|
|
32
|
+
search: 'tote bag',
|
|
33
|
+
limit: 12,
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
30
37
|
```bash cURL
|
|
31
38
|
curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag&limit=12' \
|
|
32
39
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -38,13 +45,20 @@ curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag&limit=12'
|
|
|
38
45
|
### By Price Range
|
|
39
46
|
|
|
40
47
|
|
|
41
|
-
```typescript SDK
|
|
48
|
+
```typescript Store SDK
|
|
42
49
|
const { data: products } = await client.products.list({
|
|
43
50
|
price_gte: 20,
|
|
44
51
|
price_lte: 100,
|
|
45
52
|
})
|
|
46
53
|
```
|
|
47
54
|
|
|
55
|
+
```typescript Admin SDK
|
|
56
|
+
const { data: products } = await adminClient.products.list({
|
|
57
|
+
price_gte: 20,
|
|
58
|
+
price_lte: 100,
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
48
62
|
```bash cURL
|
|
49
63
|
curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=20&q[price_lte]=100' \
|
|
50
64
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -54,12 +68,18 @@ curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=20&q[price_lte]
|
|
|
54
68
|
### By Availability
|
|
55
69
|
|
|
56
70
|
|
|
57
|
-
```typescript SDK
|
|
71
|
+
```typescript Store SDK
|
|
58
72
|
const { data: products } = await client.products.list({
|
|
59
73
|
in_stock: true,
|
|
60
74
|
})
|
|
61
75
|
```
|
|
62
76
|
|
|
77
|
+
```typescript Admin SDK
|
|
78
|
+
const { data: products } = await adminClient.products.list({
|
|
79
|
+
in_stock: true,
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
63
83
|
```bash cURL
|
|
64
84
|
curl 'https://api.mystore.com/api/v3/store/products?q[in_stock]=true' \
|
|
65
85
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -69,13 +89,8 @@ curl 'https://api.mystore.com/api/v3/store/products?q[in_stock]=true' \
|
|
|
69
89
|
### By Category
|
|
70
90
|
|
|
71
91
|
|
|
72
|
-
```typescript SDK
|
|
73
|
-
//
|
|
74
|
-
const { data: products } = await client.categories.products.list('clothing/shirts', {
|
|
75
|
-
limit: 12,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Or filter by category ID (includes descendants)
|
|
92
|
+
```typescript Store SDK
|
|
93
|
+
// Filter by category ID (includes descendants)
|
|
79
94
|
const { data: products } = await client.products.list({
|
|
80
95
|
in_category: 'ctg_xxx',
|
|
81
96
|
})
|
|
@@ -86,11 +101,19 @@ const { data: products } = await client.products.list({
|
|
|
86
101
|
})
|
|
87
102
|
```
|
|
88
103
|
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
```typescript Admin SDK
|
|
105
|
+
// Filter by category ID (includes descendants)
|
|
106
|
+
const { data: products } = await adminClient.products.list({
|
|
107
|
+
in_category: 'ctg_xxx',
|
|
108
|
+
})
|
|
93
109
|
|
|
110
|
+
// Multiple categories (OR logic)
|
|
111
|
+
const { data: products } = await adminClient.products.list({
|
|
112
|
+
in_categories: ['ctg_shirts', 'ctg_pants'],
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```bash cURL
|
|
94
117
|
# Filter by category ID (includes descendants)
|
|
95
118
|
curl 'https://api.mystore.com/api/v3/store/products?q[in_category]=ctg_xxx' \
|
|
96
119
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
@@ -104,14 +127,20 @@ curl 'https://api.mystore.com/api/v3/store/products?q[in_categories][]=ctg_shirt
|
|
|
104
127
|
### By Tags
|
|
105
128
|
|
|
106
129
|
|
|
107
|
-
```typescript SDK
|
|
130
|
+
```typescript Store SDK
|
|
108
131
|
const { data: products } = await client.products.list({
|
|
109
|
-
|
|
132
|
+
tags_name_cont: 'sale',
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```typescript Admin SDK
|
|
137
|
+
const { data: products } = await adminClient.products.list({
|
|
138
|
+
tags_name_cont: 'sale',
|
|
110
139
|
})
|
|
111
140
|
```
|
|
112
141
|
|
|
113
142
|
```bash cURL
|
|
114
|
-
curl 'https://api.mystore.com/api/v3/store/products?q[
|
|
143
|
+
curl 'https://api.mystore.com/api/v3/store/products?q[tags_name_cont]=sale' \
|
|
115
144
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
116
145
|
```
|
|
117
146
|
|
|
@@ -119,33 +148,45 @@ curl 'https://api.mystore.com/api/v3/store/products?q[tags_cont]=sale' \
|
|
|
119
148
|
## Sorting
|
|
120
149
|
|
|
121
150
|
|
|
122
|
-
```typescript SDK
|
|
151
|
+
```typescript Store SDK
|
|
123
152
|
// Sort by price (low to high)
|
|
124
153
|
const sorted = await client.products.list({
|
|
125
|
-
sort: '
|
|
154
|
+
sort: 'price',
|
|
126
155
|
})
|
|
127
156
|
|
|
128
|
-
// Available sort options:
|
|
129
|
-
//
|
|
157
|
+
// Available sort options (prefix '-' for descending; use '-available_on' for newest):
|
|
158
|
+
// price, -price, name, -name, available_on, -available_on, best_selling
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```typescript Admin SDK
|
|
162
|
+
const { data: products } = await adminClient.products.list({
|
|
163
|
+
sort: 'price',
|
|
164
|
+
})
|
|
130
165
|
```
|
|
131
166
|
|
|
132
167
|
```bash cURL
|
|
133
|
-
curl 'https://api.mystore.com/api/v3/store/products?sort=
|
|
168
|
+
curl 'https://api.mystore.com/api/v3/store/products?sort=price' \
|
|
134
169
|
-H 'X-Spree-API-Key: pk_xxx'
|
|
135
170
|
```
|
|
136
171
|
|
|
137
172
|
|
|
138
173
|
## Product Filters (Faceted Search)
|
|
139
174
|
|
|
140
|
-
Get available filter options for building a faceted search UI. Returns
|
|
175
|
+
Get available filter options for building a faceted search UI. Returns an array of typed filter objects (price range, availability, option types, categories) with counts, plus sort and pagination metadata:
|
|
141
176
|
|
|
142
177
|
|
|
143
|
-
```typescript SDK
|
|
178
|
+
```typescript Store SDK
|
|
144
179
|
const filters = await client.products.filters()
|
|
145
180
|
// {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
181
|
+
// filters: [
|
|
182
|
+
// { id: 'price', type: 'price_range', min: 9.99, max: 199.99, currency: 'USD' },
|
|
183
|
+
// { id: 'availability', type: 'availability', options: [{ id: 'in_stock', count: 42 }, { id: 'out_of_stock', count: 3 }] },
|
|
184
|
+
// { id: 'optt_xxx', type: 'option', name: 'size', label: 'Size', kind: 'dropdown', options: [{ id: 'optval_xxx', name: 'Small', label: 'Small', position: 1, color_code: null, image_url: null, count: 12 }] },
|
|
185
|
+
// { id: 'categories', type: 'category', options: [{ id: 'ctg_xxx', name: 'Clothing', permalink: 'clothing', count: 45 }] },
|
|
186
|
+
// ],
|
|
187
|
+
// sort_options: [{ id: 'manual' }, { id: 'price' }, { id: '-price' }],
|
|
188
|
+
// default_sort: 'manual',
|
|
189
|
+
// total_count: 120,
|
|
149
190
|
// }
|
|
150
191
|
|
|
151
192
|
// Scoped to a specific category
|
|
@@ -210,13 +251,20 @@ The Store API uses query parameters prefixed with `q[]` for filtering any resour
|
|
|
210
251
|
All list endpoints support pagination:
|
|
211
252
|
|
|
212
253
|
|
|
213
|
-
```typescript SDK
|
|
254
|
+
```typescript Store SDK
|
|
214
255
|
const { data: products, meta } = await client.products.list({
|
|
215
256
|
page: 1,
|
|
216
257
|
limit: 24,
|
|
217
258
|
})
|
|
218
|
-
// meta.
|
|
219
|
-
// meta.
|
|
259
|
+
// meta.count => 150
|
|
260
|
+
// meta.pages => 7
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```typescript Admin SDK
|
|
264
|
+
const { data: products, meta } = await adminClient.products.list({
|
|
265
|
+
page: 1,
|
|
266
|
+
limit: 24,
|
|
267
|
+
})
|
|
220
268
|
```
|
|
221
269
|
|
|
222
270
|
```bash cURL
|
|
@@ -243,6 +291,8 @@ See [Build a Custom Search Provider](../how-to/custom-search-provider.md) for ar
|
|
|
243
291
|
## Related Documentation
|
|
244
292
|
|
|
245
293
|
- [Products](products.md) — Product catalog and listing
|
|
246
|
-
- [
|
|
294
|
+
- [Store SDK Products](../sdk/store/products.md) — `client.products.list()` options and methods
|
|
295
|
+
- [Querying](../../api-reference/store-api/querying.md) — Store API filtering, sorting, and pagination reference
|
|
296
|
+
- [Admin API querying](../../api-reference/admin-api/querying.md) — Admin API filtering, sorting, and pagination reference
|
|
247
297
|
- [Build a Custom Search Provider](../how-to/custom-search-provider.md) — Step-by-step guide
|
|
248
298
|
- [Meilisearch Integration](../../integrations/search/meilisearch.md) — Setup guide
|
|
@@ -61,9 +61,9 @@ The Store API/SDK exposes shipments as `fulfillments` with these attributes:
|
|
|
61
61
|
| `fulfillment_type` | `shipping` or `digital` | `shipping` |
|
|
62
62
|
| `cost` | Delivery cost | `9.99` |
|
|
63
63
|
| `fulfilled_at` | When the fulfillment was shipped | `2025-07-21T14:36:00Z` |
|
|
64
|
-
| `stock_location` | Where items ship from | `Warehouse NYC` |
|
|
64
|
+
| `stock_location` | Where items ship from | `{ id: "sloc_xxx", name: "Warehouse NYC" }` |
|
|
65
65
|
| `delivery_method` | The selected delivery method | `{ id: "dm_xxx", name: "UPS Ground" }` |
|
|
66
|
-
| `delivery_rates` | Available rates for the customer to pick from | `[{ id: "
|
|
66
|
+
| `delivery_rates` | Available rates for the customer to pick from | `[{ id: "dr_xxx", cost: "9.99", selected: true, ... }]` |
|
|
67
67
|
|
|
68
68
|
## Shipment States
|
|
69
69
|
|
|
@@ -86,28 +86,30 @@ The shipment was canceled. All items are restocked.
|
|
|
86
86
|
|
|
87
87
|
## Selecting Shipping Rates
|
|
88
88
|
|
|
89
|
-
During checkout, after the customer provides a shipping address, Spree calculates available shipping rates for each shipment. The customer must select a rate before proceeding.
|
|
89
|
+
During checkout, after the customer provides a shipping address, Spree calculates available shipping rates for each shipment. The customer must select a rate before proceeding. See the [Cart & Checkout SDK](../sdk/store/cart-checkout.md) guide for the full storefront cart and fulfillment flow.
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
```typescript SDK
|
|
93
|
-
// Get
|
|
92
|
+
```typescript Store SDK
|
|
93
|
+
// Get the cart with its fulfillments and available delivery rates
|
|
94
94
|
// (the Store API/SDK exposes shipments as `fulfillments`)
|
|
95
|
-
const
|
|
96
|
-
expand: ['fulfillments'],
|
|
97
|
-
})
|
|
95
|
+
const cart = await client.carts.get(cartId)
|
|
98
96
|
|
|
99
97
|
// Each fulfillment has available delivery rates
|
|
100
|
-
|
|
98
|
+
cart.fulfillments?.forEach(fulfillment => {
|
|
101
99
|
console.log(fulfillment.number) // "H12345678901"
|
|
102
|
-
console.log(fulfillment.delivery_rates) // [{ id: "
|
|
100
|
+
console.log(fulfillment.delivery_rates) // [{ id: "dr_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
|
|
103
101
|
})
|
|
104
102
|
|
|
105
103
|
// Select a delivery rate
|
|
106
104
|
await client.carts.fulfillments.update(cartId, fulfillment.id, {
|
|
107
|
-
selected_delivery_rate_id: '
|
|
105
|
+
selected_delivery_rate_id: 'dr_xxx',
|
|
108
106
|
})
|
|
109
107
|
```
|
|
110
108
|
|
|
109
|
+
```typescript Admin SDK
|
|
110
|
+
const order = await adminClient.orders.get(orderId)
|
|
111
|
+
```
|
|
112
|
+
|
|
111
113
|
```bash cURL
|
|
112
114
|
# Get fulfillments
|
|
113
115
|
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=fulfillments' \
|
|
@@ -119,7 +121,7 @@ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/
|
|
|
119
121
|
-H 'X-Spree-API-Key: pk_xxx' \
|
|
120
122
|
-H 'X-Spree-Token: abc123' \
|
|
121
123
|
-H 'Content-Type: application/json' \
|
|
122
|
-
-d '{ "selected_delivery_rate_id": "
|
|
124
|
+
-d '{ "selected_delivery_rate_id": "dr_xxx" }'
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
|
|
@@ -195,7 +197,7 @@ A channel can also override the routing **strategy** entirely — useful when on
|
|
|
195
197
|
|
|
196
198
|
### When it fires
|
|
197
199
|
|
|
198
|
-
Routing fires once, when the order transitions from `
|
|
200
|
+
Routing fires once, when the order transitions into the `delivery` step of checkout (from `address`). The `cart`→`address` transition only assigns default addresses; routing runs on the next step. It produces one or more `Shipment`s, each tied to a chosen stock location. The decision is sticky — once the shipments are created, their locations stay fixed unless the merchant edits them in the admin or the cart is cleared and re-routed.
|
|
199
201
|
|
|
200
202
|
Routing happens **after** stock reservations: by the time routing runs, the cart has already reserved the units it needs. Reservations and routing make their decisions at different layers — reservations protect the variant's total inventory across all locations, routing picks which location ships. They coexist correctly today, with a small inefficiency around location-pinning that's planned to be tightened in 6.0.
|
|
201
203
|
|
|
@@ -302,3 +304,4 @@ A store shipping from 2 locations (New York, Los Angeles) with 3 carriers and 3
|
|
|
302
304
|
- [Addresses](addresses.md) — Shipping address and zones
|
|
303
305
|
- [Build Custom Order Routing](../how-to/custom-order-routing.md) — Custom rules and strategies for choosing the fulfillment location
|
|
304
306
|
- [Events](events.md) — Subscribe to shipment events (e.g., `shipment.shipped`)
|
|
307
|
+
- [Admin API](../../api-reference/admin-api/introduction.md) — Inspect and manage shipments, shipping methods, and categories from the back office
|
|
@@ -7,10 +7,10 @@ description: How Spree generates SEO-friendly URL slugs for products, categories
|
|
|
7
7
|
|
|
8
8
|
Spree generates SEO-friendly URL slugs for resources like products, categories, and stores. Instead of accessing resources by ID, you can use clean, readable URLs based on resource names.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
The Store API accepts slugs or prefixed IDs interchangeably for resource lookups. The Admin API resolves resources by prefixed ID only — slugs are not accepted.
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
```typescript SDK
|
|
13
|
+
```typescript Store SDK
|
|
14
14
|
// Both work — slug or ID
|
|
15
15
|
const product = await client.products.get('spree-tote')
|
|
16
16
|
const product = await client.products.get('prod_86Rf07xd4z')
|
|
@@ -19,6 +19,11 @@ const product = await client.products.get('prod_86Rf07xd4z')
|
|
|
19
19
|
const category = await client.categories.get('clothing/shirts')
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
```typescript Admin SDK
|
|
23
|
+
// Admin SDK accepts prefixed IDs only — not slugs
|
|
24
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z')
|
|
25
|
+
```
|
|
26
|
+
|
|
22
27
|
```bash cURL
|
|
23
28
|
# By slug
|
|
24
29
|
curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
|
|
@@ -49,7 +54,6 @@ If a slug already exists, Spree appends the SKU or a unique identifier to ensure
|
|
|
49
54
|
| Product | `slug` | Yes | No |
|
|
50
55
|
| Category | `permalink` | Yes | Yes |
|
|
51
56
|
| Store | `code` | No | No |
|
|
52
|
-
| Post | `slug` | Yes | No |
|
|
53
57
|
|
|
54
58
|
### Category Permalinks
|
|
55
59
|
|
|
@@ -65,27 +69,31 @@ When a parent category is renamed, all child permalinks update automatically.
|
|
|
65
69
|
|
|
66
70
|
## Slug History
|
|
67
71
|
|
|
68
|
-
When a slug changes (e.g., a product is renamed), Spree
|
|
72
|
+
When a slug changes (e.g., a product is renamed), Spree records the old slug in its history table (`friendly_id_slugs`).
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
- **Bookmark compatibility** — saved URLs keep working
|
|
72
|
-
- **Graceful URL migration** — old links resolve automatically
|
|
74
|
+
Note: the Store API product and category endpoints look up the *current* slug only (`find_by!` on the slug/permalink column) and do not automatically resolve retired slugs — a request using an old slug returns `404`. To preserve SEO and avoid broken links, issue HTTP 301 redirects from old slugs to the current one in your storefront or application layer.
|
|
73
75
|
|
|
74
76
|
## Internationalization
|
|
75
77
|
|
|
76
|
-
Products and categories support localized slugs — a different slug per locale:
|
|
78
|
+
Products and categories support localized slugs — a different slug per locale. The locale is resolved from the request's [`X-Spree-Locale` header](../../api-reference/store-api/localization.md):
|
|
77
79
|
|
|
78
80
|
|
|
79
|
-
```typescript SDK
|
|
81
|
+
```typescript Store SDK
|
|
80
82
|
// English
|
|
81
83
|
const product = await client.products.get('red-shoes')
|
|
82
84
|
|
|
83
|
-
// French
|
|
84
|
-
|
|
85
|
+
// French — locale goes in the third (request options) argument,
|
|
86
|
+
// which sends the X-Spree-Locale header
|
|
87
|
+
const product = await client.products.get('chaussures-rouges', undefined, {
|
|
85
88
|
locale: 'fr',
|
|
86
89
|
})
|
|
87
90
|
```
|
|
88
91
|
|
|
92
|
+
```typescript Admin SDK
|
|
93
|
+
// Admin SDK accepts prefixed IDs only — not slugs
|
|
94
|
+
const product = await adminClient.products.get('prod_86Rf07xd4z')
|
|
95
|
+
```
|
|
96
|
+
|
|
89
97
|
```bash cURL
|
|
90
98
|
# English
|
|
91
99
|
curl 'https://api.mystore.com/api/v3/store/products/red-shoes' \
|
|
@@ -109,3 +117,4 @@ Spree prevents certain words from being used as slugs to avoid route conflicts:
|
|
|
109
117
|
- [Products](products.md) — Product catalog
|
|
110
118
|
- [Translations](translations.md) — Multi-language content
|
|
111
119
|
- [Querying](../../api-reference/store-api/querying.md) — API resource lookup
|
|
120
|
+
- [Store SDK Products](../sdk/store/products.md) — `client.products.get()` slug/ID lookup
|
|
@@ -63,6 +63,35 @@ spree user create --email admin@example.com --password secret123
|
|
|
63
63
|
|
|
64
64
|
The created user gets the `admin` role on the default store.
|
|
65
65
|
|
|
66
|
+
## Authentication & identity providers
|
|
67
|
+
|
|
68
|
+
Staff authenticate against the Admin API, which issues a short-lived JWT used for subsequent requests. How a staff member proves who they are is **pluggable** — Spree ships email/password out of the box, and you can plug in any external identity provider (Okta, Microsoft Entra ID, Google Workspace, a custom JWT issuer, SAML, etc.) without changing the rest of the API.
|
|
69
|
+
|
|
70
|
+
### How admin login works
|
|
71
|
+
|
|
72
|
+
A staff member logs in via the `POST /api/v3/admin/auth/login` endpoint (see [Admin API Authentication](../../api-reference/admin-api/authentication.md)). The request's `provider` field selects a registered **authentication strategy**. When `provider` is omitted it defaults to `email`, the built-in email/password strategy (which you can also disable and restrict the admin to your preferred SSO provider).
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart TB
|
|
76
|
+
A["POST /api/v3/admin/auth/login"] --> B{"provider field"}
|
|
77
|
+
B -->|"omitted or email"| C["Built-in EmailPasswordStrategy"]
|
|
78
|
+
B -->|"okta, saml, ..."| D["Your custom strategy"]
|
|
79
|
+
C --> E["Strategy authenticates, returns the staff user"]
|
|
80
|
+
D --> E
|
|
81
|
+
E --> F["Spree issues a JWT (aud=admin_api) + HttpOnly refresh cookie"]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Whichever strategy authenticates the request, Spree issues the **same** credentials in return, so downstream code and the admin SPA never need to know which provider was used:
|
|
85
|
+
|
|
86
|
+
- a JWT access token (`aud: admin_api`), short-lived by design;
|
|
87
|
+
- a rotating refresh token, set as an `HttpOnly` cookie scoped to `/api/v3/admin/auth` (the admin flow keeps it out of the response body — see [Admin Auth & Cookie Refresh](../../api-reference/admin-api/authentication.md)).
|
|
88
|
+
|
|
89
|
+
The same strategy registry exists on the customer side, so storefront sign-in is pluggable the same way — the only difference is the user class and that the Store API returns the refresh token in the body rather than a cookie.
|
|
90
|
+
|
|
91
|
+
### Registering a custom identity provider
|
|
92
|
+
|
|
93
|
+
Follow the [Custom API Authentication how-to](../how-to/custom-api-authentication.md) for details how to create a custom authentication strategy and register it with the admin API. Once registered, you can use it from the admin SPA or any API client by passing its name in the `provider` field of the login request.
|
|
94
|
+
|
|
66
95
|
## Inviting Admin Users
|
|
67
96
|
|
|
68
97
|
You can invite new admins through the Admin Panel or programmatically.
|
|
@@ -74,7 +103,25 @@ You can invite new admins through the Admin Panel or programmatically.
|
|
|
74
103
|
3. Enter the email address and select a role
|
|
75
104
|
4. Click **Send Invitation**
|
|
76
105
|
|
|
77
|
-
|
|
106
|
+
**Programmatically:**
|
|
107
|
+
|
|
108
|
+
Using the [Admin SDK](../sdk/admin/quickstart.md), call `client.invitations.create`:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
112
|
+
|
|
113
|
+
const client = createAdminClient({
|
|
114
|
+
baseUrl: 'https://store.example.com',
|
|
115
|
+
secretKey: 'sk_xxx',
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const invitation = await client.invitations.create({
|
|
119
|
+
email: 'new-admin@example.com',
|
|
120
|
+
role_id: 'role_xxx',
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Creating an invitation publishes `invitation.created`, which sends the email. Either way, the invitee receives an email with an invitation link. If they already have an account, they log in to accept. Otherwise, they create an account first.
|
|
78
125
|
|
|
79
126
|
```mermaid
|
|
80
127
|
flowchart TB
|
|
@@ -117,6 +164,9 @@ See the [Customize Permissions guide](../customization/permissions.md) for detai
|
|
|
117
164
|
|
|
118
165
|
## Related Documentation
|
|
119
166
|
|
|
167
|
+
- [Admin SDK](../sdk/admin/quickstart.md) — the TypeScript client for back-office automation
|
|
168
|
+
- [Custom API Authentication](../how-to/custom-api-authentication.md) — implement a custom identity-provider strategy (the full guide)
|
|
169
|
+
- [Admin API Authentication](../../api-reference/admin-api/authentication.md) — keys, JWTs, scopes, and the refresh-cookie flow
|
|
120
170
|
- [Customers](customers.md) — Customer accounts and authentication
|
|
121
171
|
- [Stores](stores.md) — Multi-store setup
|
|
122
172
|
- [Permissions](../customization/permissions.md) — Roles and authorization
|