@spree/docs 0.1.81 → 0.1.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/api-reference/store-api/errors.md +3 -3
  2. package/dist/api-reference/store-api/idempotency.md +1 -1
  3. package/dist/developer/cli/quickstart.md +149 -1
  4. package/dist/developer/contributing/creating-an-extension.md +1 -1
  5. package/dist/developer/contributing/developing-spree.md +167 -76
  6. package/dist/developer/core-concepts/addresses.md +10 -10
  7. package/dist/developer/core-concepts/adjustments.md +1 -1
  8. package/dist/developer/core-concepts/customers.md +1 -1
  9. package/dist/developer/core-concepts/events.md +12 -6
  10. package/dist/developer/core-concepts/markets.md +10 -10
  11. package/dist/developer/core-concepts/media.md +2 -2
  12. package/dist/developer/core-concepts/orders.md +10 -10
  13. package/dist/developer/core-concepts/pricing.md +2 -2
  14. package/dist/developer/core-concepts/products.md +12 -12
  15. package/dist/developer/core-concepts/promotions.md +1 -1
  16. package/dist/developer/core-concepts/search-filtering.md +11 -11
  17. package/dist/developer/core-concepts/shipments.md +2 -2
  18. package/dist/developer/core-concepts/slugs.md +4 -4
  19. package/dist/developer/core-concepts/stores.md +1 -1
  20. package/dist/developer/core-concepts/translations.md +1 -1
  21. package/dist/developer/core-concepts/users.md +1 -1
  22. package/dist/developer/create-spree-app/quickstart.md +8 -7
  23. package/dist/developer/customization/checkout.md +1 -1
  24. package/dist/developer/customization/decorators.md +10 -8
  25. package/dist/developer/customization/quickstart.md +1 -1
  26. package/dist/developer/deployment/environment_variables.md +1 -1
  27. package/dist/developer/sdk/store/markets.md +5 -5
  28. package/dist/developer/tutorial/extending-models.md +1 -1
  29. package/dist/developer/upgrades/5.4-to-5.5.md +84 -17
  30. package/package.json +1 -1
@@ -84,7 +84,7 @@ const { token, user } = await client.auth.login({
84
84
 
85
85
  ```bash cURL
86
86
  curl -X POST 'https://api.mystore.com/api/v3/store/auth/login' \
87
- -H 'Authorization: Bearer pk_xxx' \
87
+ -H 'X-Spree-API-Key: pk_xxx' \
88
88
  -H 'Content-Type: application/json' \
89
89
  -d '{
90
90
  "email": "john@example.com",
@@ -65,7 +65,7 @@ module MyApp
65
65
 
66
66
  def handle(event)
67
67
  order_id = event.payload['id']
68
- order = Spree::Order.find_by(id: order_id)
68
+ order = Spree::Order.find_by_prefix_id(order_id)
69
69
  return unless order
70
70
 
71
71
  # Your custom logic here
@@ -75,6 +75,14 @@ module MyApp
75
75
  end
76
76
  ```
77
77
 
78
+ Then register it in an initializer — subscribers are not auto-discovered (see [Registering Subscribers](#registering-subscribers)):
79
+
80
+ ```ruby config/initializers/event_subscribers.rb
81
+ Rails.application.config.after_initialize do
82
+ Spree.subscribers << MyApp::OrderCompletedSubscriber
83
+ end
84
+ ```
85
+
78
86
  ### Subscriber DSL
79
87
 
80
88
  The `Spree::Subscriber` class provides a clean DSL for declaring subscriptions:
@@ -164,7 +172,7 @@ The payload contains serialized attributes, not the actual record. To get the re
164
172
  ```ruby
165
173
  def handle(event)
166
174
  record_id = event.payload['id']
167
- record = Spree::Order.find_by(id: record_id)
175
+ record = Spree::Order.find_by_prefix_id(record_id)
168
176
  return unless record
169
177
 
170
178
  # Work with the record
@@ -395,9 +403,7 @@ Models without a matching serializer will use a minimal fallback payload contain
395
403
 
396
404
  ## Registering Subscribers
397
405
 
398
- Subscribers in `app/subscribers/` are automatically registered during application initialization.
399
-
400
- For subscribers in other locations, add them to the `Spree.subscribers` array in an initializer:
406
+ Subscribers are not auto-discovered every subscriber must be registered explicitly, regardless of where the class lives. Add it to the `Spree.subscribers` array in an initializer:
401
407
 
402
408
  ```ruby config/initializers/event_subscribers.rb
403
409
  Rails.application.config.after_initialize do
@@ -521,7 +527,7 @@ module MyApp
521
527
  private
522
528
 
523
529
  def find_stock_item(event)
524
- Spree::StockItem.find_by(id: event.payload['id'])
530
+ Spree::StockItem.find_by_prefix_id(event.payload['id'])
525
531
  end
526
532
 
527
533
  def stock_dropped_below_threshold?(event, stock_item)
@@ -96,7 +96,7 @@ const { data: markets } = await client.markets.list()
96
96
 
97
97
  ```bash cURL
98
98
  curl 'https://api.mystore.com/api/v3/store/markets' \
99
- -H 'Authorization: Bearer pk_xxx'
99
+ -H 'X-Spree-API-Key: pk_xxx'
100
100
  ```
101
101
 
102
102
 
@@ -112,7 +112,7 @@ const market = await client.markets.resolve('DE')
112
112
 
113
113
  ```bash cURL
114
114
  curl 'https://api.mystore.com/api/v3/store/markets/resolve?country=DE' \
115
- -H 'Authorization: Bearer pk_xxx'
115
+ -H 'X-Spree-API-Key: pk_xxx'
116
116
  ```
117
117
 
118
118
 
@@ -141,11 +141,11 @@ const usa = await client.markets.countries.get('mkt_k5nR8xLq', 'US', {
141
141
  ```bash cURL
142
142
  # List countries in a market
143
143
  curl 'https://api.mystore.com/api/v3/store/markets/mkt_k5nR8xLq/countries' \
144
- -H 'Authorization: Bearer pk_xxx'
144
+ -H 'X-Spree-API-Key: pk_xxx'
145
145
 
146
146
  # Get a country with its states
147
- curl 'https://api.mystore.com/api/v3/store/markets/mkt_k5nR8xLq/countries/US?include=states' \
148
- -H 'Authorization: Bearer pk_xxx'
147
+ curl 'https://api.mystore.com/api/v3/store/markets/mkt_k5nR8xLq/countries/US?expand=states' \
148
+ -H 'X-Spree-API-Key: pk_xxx'
149
149
  ```
150
150
 
151
151
 
@@ -164,11 +164,11 @@ const germany = await client.countries.get('DE', { include: 'market' })
164
164
  ```bash cURL
165
165
  # All countries across all markets
166
166
  curl 'https://api.mystore.com/api/v3/store/countries' \
167
- -H 'Authorization: Bearer pk_xxx'
167
+ -H 'X-Spree-API-Key: pk_xxx'
168
168
 
169
169
  # Get a country with its market
170
- curl 'https://api.mystore.com/api/v3/store/countries/DE?include=market' \
171
- -H 'Authorization: Bearer pk_xxx'
170
+ curl 'https://api.mystore.com/api/v3/store/countries/DE?expand=market' \
171
+ -H 'X-Spree-API-Key: pk_xxx'
172
172
  ```
173
173
 
174
174
 
@@ -189,10 +189,10 @@ const { data: locales } = await client.locales.list()
189
189
 
190
190
  ```bash cURL
191
191
  curl 'https://api.mystore.com/api/v3/store/currencies' \
192
- -H 'Authorization: Bearer pk_xxx'
192
+ -H 'X-Spree-API-Key: pk_xxx'
193
193
 
194
194
  curl 'https://api.mystore.com/api/v3/store/locales' \
195
- -H 'Authorization: Bearer pk_xxx'
195
+ -H 'X-Spree-API-Key: pk_xxx'
196
196
  ```
197
197
 
198
198
 
@@ -114,7 +114,7 @@ products.forEach(product => {
114
114
  ```bash cURL
115
115
  # thumbnail_url is always in the response — no ?expand needed
116
116
  curl 'https://api.mystore.com/api/v3/store/products?limit=12' \
117
- -H 'Authorization: Bearer pk_xxx'
117
+ -H 'X-Spree-API-Key: pk_xxx'
118
118
  ```
119
119
 
120
120
 
@@ -143,7 +143,7 @@ product.variants?.forEach(variant => {
143
143
 
144
144
  ```bash cURL
145
145
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=media,variants' \
146
- -H 'Authorization: Bearer pk_xxx'
146
+ -H 'X-Spree-API-Key: pk_xxx'
147
147
  ```
148
148
 
149
149
 
@@ -109,30 +109,30 @@ await client.carts.items.delete(cart.id, 'li_xxx')
109
109
  ```bash cURL
110
110
  # Create a cart
111
111
  curl -X POST 'https://api.mystore.com/api/v3/store/carts' \
112
- -H 'Authorization: Bearer pk_xxx'
112
+ -H 'X-Spree-API-Key: pk_xxx'
113
113
 
114
114
  # Get existing cart
115
115
  curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
116
- -H 'Authorization: Bearer pk_xxx' \
116
+ -H 'X-Spree-API-Key: pk_xxx' \
117
117
  -H 'X-Spree-Token: abc123'
118
118
 
119
119
  # Add an item
120
120
  curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/items' \
121
- -H 'Authorization: Bearer pk_xxx' \
121
+ -H 'X-Spree-API-Key: pk_xxx' \
122
122
  -H 'X-Spree-Token: abc123' \
123
123
  -H 'Content-Type: application/json' \
124
124
  -d '{ "variant_id": "var_xxx", "quantity": 2 }'
125
125
 
126
126
  # Update quantity
127
127
  curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/items/li_xxx' \
128
- -H 'Authorization: Bearer pk_xxx' \
128
+ -H 'X-Spree-API-Key: pk_xxx' \
129
129
  -H 'X-Spree-Token: abc123' \
130
130
  -H 'Content-Type: application/json' \
131
131
  -d '{ "quantity": 3 }'
132
132
 
133
133
  # Remove an item
134
134
  curl -X DELETE 'https://api.mystore.com/api/v3/store/carts/cart_xxx/items/li_xxx' \
135
- -H 'Authorization: Bearer pk_xxx' \
135
+ -H 'X-Spree-API-Key: pk_xxx' \
136
136
  -H 'X-Spree-Token: abc123'
137
137
  ```
138
138
 
@@ -206,7 +206,7 @@ await client.carts.complete(cartId)
206
206
  ```bash cURL
207
207
  # Set addresses
208
208
  curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
209
- -H 'Authorization: Bearer pk_xxx' \
209
+ -H 'X-Spree-API-Key: pk_xxx' \
210
210
  -H 'X-Spree-Token: abc123' \
211
211
  -H 'Content-Type: application/json' \
212
212
  -d '{
@@ -221,14 +221,14 @@ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
221
221
 
222
222
  # Select a delivery rate
223
223
  curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
224
- -H 'Authorization: Bearer pk_xxx' \
224
+ -H 'X-Spree-API-Key: pk_xxx' \
225
225
  -H 'X-Spree-Token: abc123' \
226
226
  -H 'Content-Type: application/json' \
227
227
  -d '{ "selected_delivery_rate_id": "rate_xxx" }'
228
228
 
229
229
  # Complete the order
230
230
  curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/complete' \
231
- -H 'Authorization: Bearer pk_xxx' \
231
+ -H 'X-Spree-API-Key: pk_xxx' \
232
232
  -H 'X-Spree-Token: abc123'
233
233
  ```
234
234
 
@@ -249,14 +249,14 @@ await client.carts.discountCodes.remove(cartId, 'SAVE20')
249
249
  ```bash cURL
250
250
  # Apply a discount code
251
251
  curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/discount_codes' \
252
- -H 'Authorization: Bearer pk_xxx' \
252
+ -H 'X-Spree-API-Key: pk_xxx' \
253
253
  -H 'X-Spree-Token: abc123' \
254
254
  -H 'Content-Type: application/json' \
255
255
  -d '{ "code": "SAVE20" }'
256
256
 
257
257
  # Remove a discount code
258
258
  curl -X DELETE 'https://api.mystore.com/api/v3/store/carts/cart_xxx/discount_codes/SAVE20' \
259
- -H 'Authorization: Bearer pk_xxx' \
259
+ -H 'X-Spree-API-Key: pk_xxx' \
260
260
  -H 'X-Spree-Token: abc123'
261
261
  ```
262
262
 
@@ -45,11 +45,11 @@ detailed.variants?.forEach(variant => {
45
45
  ```bash cURL
46
46
  # Product price is always in the response
47
47
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
48
- -H 'Authorization: Bearer pk_xxx'
48
+ -H 'X-Spree-API-Key: pk_xxx'
49
49
 
50
50
  # With variants
51
51
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=variants' \
52
- -H 'Authorization: Bearer pk_xxx'
52
+ -H 'X-Spree-API-Key: pk_xxx'
53
53
  ```
54
54
 
55
55
 
@@ -107,15 +107,15 @@ const sorted = await client.products.list({
107
107
  ```bash cURL
108
108
  # List products
109
109
  curl 'https://api.mystore.com/api/v3/store/products?limit=12&page=1' \
110
- -H 'Authorization: Bearer pk_xxx'
110
+ -H 'X-Spree-API-Key: pk_xxx'
111
111
 
112
112
  # Filter by price and stock
113
113
  curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=10&q[price_lte]=50&q[in_stock]=true' \
114
- -H 'Authorization: Bearer pk_xxx'
114
+ -H 'X-Spree-API-Key: pk_xxx'
115
115
 
116
116
  # Search
117
117
  curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag' \
118
- -H 'Authorization: Bearer pk_xxx'
118
+ -H 'X-Spree-API-Key: pk_xxx'
119
119
  ```
120
120
 
121
121
 
@@ -139,7 +139,7 @@ const detailed = await client.products.get('spree-tote', {
139
139
 
140
140
  ```bash cURL
141
141
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=variants,media,option_types,categories' \
142
- -H 'Authorization: Bearer pk_xxx'
142
+ -H 'X-Spree-API-Key: pk_xxx'
143
143
  ```
144
144
 
145
145
 
@@ -164,11 +164,11 @@ const categoryFilters = await client.products.filters({
164
164
 
165
165
  ```bash cURL
166
166
  curl 'https://api.mystore.com/api/v3/store/products/filters' \
167
- -H 'Authorization: Bearer pk_xxx'
167
+ -H 'X-Spree-API-Key: pk_xxx'
168
168
 
169
169
  # Scoped to a category
170
170
  curl 'https://api.mystore.com/api/v3/store/products/filters?category_id=ctg_xxx' \
171
- -H 'Authorization: Bearer pk_xxx'
171
+ -H 'X-Spree-API-Key: pk_xxx'
172
172
  ```
173
173
 
174
174
 
@@ -228,7 +228,7 @@ product.option_types?.forEach(optionType => {
228
228
 
229
229
  ```bash cURL
230
230
  curl 'https://api.mystore.com/api/v3/store/products/spree-tee?expand=option_types' \
231
- -H 'Authorization: Bearer pk_xxx'
231
+ -H 'X-Spree-API-Key: pk_xxx'
232
232
  ```
233
233
 
234
234
 
@@ -257,7 +257,7 @@ products.forEach(product => {
257
257
  ```bash cURL
258
258
  # thumbnail_url is always in the response — no ?expand needed
259
259
  curl 'https://api.mystore.com/api/v3/store/products?limit=12' \
260
- -H 'Authorization: Bearer pk_xxx'
260
+ -H 'X-Spree-API-Key: pk_xxx'
261
261
  ```
262
262
 
263
263
 
@@ -286,7 +286,7 @@ product.variants?.forEach(variant => {
286
286
 
287
287
  ```bash cURL
288
288
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=media,variants' \
289
- -H 'Authorization: Bearer pk_xxx'
289
+ -H 'X-Spree-API-Key: pk_xxx'
290
290
  ```
291
291
 
292
292
 
@@ -338,15 +338,15 @@ const { data: products } = await client.categories.products.list('clothing/shirt
338
338
  ```bash cURL
339
339
  # List categories
340
340
  curl 'https://api.mystore.com/api/v3/store/categories' \
341
- -H 'Authorization: Bearer pk_xxx'
341
+ -H 'X-Spree-API-Key: pk_xxx'
342
342
 
343
343
  # Get a category by permalink
344
344
  curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts' \
345
- -H 'Authorization: Bearer pk_xxx'
345
+ -H 'X-Spree-API-Key: pk_xxx'
346
346
 
347
347
  # List products in a category
348
348
  curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?limit=12' \
349
- -H 'Authorization: Bearer pk_xxx'
349
+ -H 'X-Spree-API-Key: pk_xxx'
350
350
  ```
351
351
 
352
352
 
@@ -292,7 +292,7 @@ await client.carts.discountCodes.remove('cart_abc123', 'SUMMER20', {
292
292
  ```bash cURL
293
293
  # Apply a coupon code
294
294
  curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/coupon_codes' \
295
- -H 'Authorization: Bearer pk_xxx' \
295
+ -H 'X-Spree-API-Key: pk_xxx' \
296
296
  -H 'X-Spree-Token: <token>' \
297
297
  -H 'Content-Type: application/json' \
298
298
  -d '{ "code": "SUMMER20" }'
@@ -29,7 +29,7 @@ const { data: products } = await client.products.list({
29
29
 
30
30
  ```bash cURL
31
31
  curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag&limit=12' \
32
- -H 'Authorization: Bearer pk_xxx'
32
+ -H 'X-Spree-API-Key: pk_xxx'
33
33
  ```
34
34
 
35
35
 
@@ -47,7 +47,7 @@ const { data: products } = await client.products.list({
47
47
 
48
48
  ```bash cURL
49
49
  curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=20&q[price_lte]=100' \
50
- -H 'Authorization: Bearer pk_xxx'
50
+ -H 'X-Spree-API-Key: pk_xxx'
51
51
  ```
52
52
 
53
53
 
@@ -62,7 +62,7 @@ const { data: products } = await client.products.list({
62
62
 
63
63
  ```bash cURL
64
64
  curl 'https://api.mystore.com/api/v3/store/products?q[in_stock]=true' \
65
- -H 'Authorization: Bearer pk_xxx'
65
+ -H 'X-Spree-API-Key: pk_xxx'
66
66
  ```
67
67
 
68
68
 
@@ -89,15 +89,15 @@ const { data: products } = await client.products.list({
89
89
  ```bash cURL
90
90
  # Products in a category
91
91
  curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?limit=12' \
92
- -H 'Authorization: Bearer pk_xxx'
92
+ -H 'X-Spree-API-Key: pk_xxx'
93
93
 
94
94
  # Filter by category ID (includes descendants)
95
95
  curl 'https://api.mystore.com/api/v3/store/products?q[in_category]=ctg_xxx' \
96
- -H 'Authorization: Bearer pk_xxx'
96
+ -H 'X-Spree-API-Key: pk_xxx'
97
97
 
98
98
  # Multiple categories (OR logic)
99
99
  curl 'https://api.mystore.com/api/v3/store/products?q[in_categories][]=ctg_shirts&q[in_categories][]=ctg_pants' \
100
- -H 'Authorization: Bearer pk_xxx'
100
+ -H 'X-Spree-API-Key: pk_xxx'
101
101
  ```
102
102
 
103
103
 
@@ -112,7 +112,7 @@ const { data: products } = await client.products.list({
112
112
 
113
113
  ```bash cURL
114
114
  curl 'https://api.mystore.com/api/v3/store/products?q[tags_cont]=sale' \
115
- -H 'Authorization: Bearer pk_xxx'
115
+ -H 'X-Spree-API-Key: pk_xxx'
116
116
  ```
117
117
 
118
118
 
@@ -131,7 +131,7 @@ const sorted = await client.products.list({
131
131
 
132
132
  ```bash cURL
133
133
  curl 'https://api.mystore.com/api/v3/store/products?sort=price_low_to_high' \
134
- -H 'Authorization: Bearer pk_xxx'
134
+ -H 'X-Spree-API-Key: pk_xxx'
135
135
  ```
136
136
 
137
137
 
@@ -157,11 +157,11 @@ const categoryFilters = await client.products.filters({
157
157
  ```bash cURL
158
158
  # All filters
159
159
  curl 'https://api.mystore.com/api/v3/store/products/filters' \
160
- -H 'Authorization: Bearer pk_xxx'
160
+ -H 'X-Spree-API-Key: pk_xxx'
161
161
 
162
162
  # Scoped to a category
163
163
  curl 'https://api.mystore.com/api/v3/store/products/filters?category_id=ctg_xxx' \
164
- -H 'Authorization: Bearer pk_xxx'
164
+ -H 'X-Spree-API-Key: pk_xxx'
165
165
  ```
166
166
 
167
167
 
@@ -221,7 +221,7 @@ const { data: products, meta } = await client.products.list({
221
221
 
222
222
  ```bash cURL
223
223
  curl 'https://api.mystore.com/api/v3/store/products?page=1&limit=24' \
224
- -H 'Authorization: Bearer pk_xxx'
224
+ -H 'X-Spree-API-Key: pk_xxx'
225
225
  ```
226
226
 
227
227
 
@@ -111,12 +111,12 @@ await client.carts.fulfillments.update(cartId, fulfillment.id, {
111
111
  ```bash cURL
112
112
  # Get fulfillments
113
113
  curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=fulfillments' \
114
- -H 'Authorization: Bearer pk_xxx' \
114
+ -H 'X-Spree-API-Key: pk_xxx' \
115
115
  -H 'X-Spree-Token: abc123'
116
116
 
117
117
  # Select a delivery rate
118
118
  curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
119
- -H 'Authorization: Bearer pk_xxx' \
119
+ -H 'X-Spree-API-Key: pk_xxx' \
120
120
  -H 'X-Spree-Token: abc123' \
121
121
  -H 'Content-Type: application/json' \
122
122
  -d '{ "selected_delivery_rate_id": "rate_xxx" }'
@@ -22,11 +22,11 @@ const category = await client.categories.get('clothing/shirts')
22
22
  ```bash cURL
23
23
  # By slug
24
24
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
25
- -H 'Authorization: Bearer pk_xxx'
25
+ -H 'X-Spree-API-Key: pk_xxx'
26
26
 
27
27
  # By ID
28
28
  curl 'https://api.mystore.com/api/v3/store/products/prod_86Rf07xd4z' \
29
- -H 'Authorization: Bearer pk_xxx'
29
+ -H 'X-Spree-API-Key: pk_xxx'
30
30
  ```
31
31
 
32
32
 
@@ -89,11 +89,11 @@ const product = await client.products.get('chaussures-rouges', {
89
89
  ```bash cURL
90
90
  # English
91
91
  curl 'https://api.mystore.com/api/v3/store/products/red-shoes' \
92
- -H 'Authorization: Bearer pk_xxx'
92
+ -H 'X-Spree-API-Key: pk_xxx'
93
93
 
94
94
  # French
95
95
  curl 'https://api.mystore.com/api/v3/store/products/chaussures-rouges' \
96
- -H 'Authorization: Bearer pk_xxx' \
96
+ -H 'X-Spree-API-Key: pk_xxx' \
97
97
  -H 'X-Spree-Locale: fr'
98
98
  ```
99
99
 
@@ -42,7 +42,7 @@ const store = await client.store.get()
42
42
 
43
43
  ```bash cURL
44
44
  curl 'https://api.mystore.com/api/v3/store/store' \
45
- -H 'Authorization: Bearer pk_xxx'
45
+ -H 'X-Spree-API-Key: pk_xxx'
46
46
  ```
47
47
 
48
48
 
@@ -85,7 +85,7 @@ const { data: categories } = await client.categories.list({}, {
85
85
  ```bash cURL
86
86
  # Fetch product in French
87
87
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
88
- -H 'Authorization: Bearer pk_xxx' \
88
+ -H 'X-Spree-API-Key: pk_xxx' \
89
89
  -H 'X-Spree-Locale: fr'
90
90
  ```
91
91
 
@@ -96,7 +96,7 @@ const { token, user } = await client.auth.login({
96
96
 
97
97
  ```bash cURL
98
98
  curl -X POST 'https://api.mystore.com/api/v3/store/auth/login' \
99
- -H 'Authorization: Bearer pk_xxx' \
99
+ -H 'X-Spree-API-Key: pk_xxx' \
100
100
  -H 'Content-Type: application/json' \
101
101
  -d '{
102
102
  "email": "john@example.com",
@@ -21,7 +21,7 @@ Once complete, your store is running at [http://localhost:3000](http://localhost
21
21
  ## Prerequisites
22
22
 
23
23
  - [Node.js](https://nodejs.org/) 20 or later
24
- - [Docker](https://docs.docker.com/get-docker/) (for running the Spree backend, PostgreSQL, and Redis)
24
+ - [Docker](https://docs.docker.com/get-docker/) (for running the Spree backend, PostgreSQL, Redis, and Meilisearch)
25
25
 
26
26
  ## CLI Flags
27
27
 
@@ -49,9 +49,9 @@ npx create-spree-app@latest my-store --no-storefront --no-sample-data --no-start
49
49
 
50
50
  ```text
51
51
  my-store/
52
- ├── docker-compose.yml # Spree backend (prebuilt image) + Postgres + Redis
52
+ ├── docker-compose.yml # Spree backend (prebuilt image) + Postgres + Redis + Meilisearch
53
53
  ├── docker-compose.dev.yml # Alternative: build from local backend/
54
- ├── .env # SECRET_KEY_BASE, SPREE_PORT
54
+ ├── .env # SECRET_KEY_BASE, SPREE_PORT, SPREE_VERSION_TAG
55
55
  ├── .gitignore
56
56
  ├── package.json # Convenience scripts
57
57
  ├── README.md
@@ -69,10 +69,11 @@ my-store/
69
69
 
70
70
  ### What's in docker-compose.yml
71
71
 
72
- - **Spree** — runs the `ghcr.io/spree/spree:latest` image on the configured port (default `3000`)
72
+ - **Spree** — `web` (Rails) + `worker` (Sidekiq) running the `ghcr.io/spree/spree:latest` image on the configured port (default `3000`)
73
73
  - **PostgreSQL 18** — database with persistent volume
74
74
  - **Redis 7** — caching, background jobs, and Action Cable
75
- - Health checks on all services
75
+ - **Meilisearch** search engine
76
+ - Health checks on postgres, redis, meilisearch, and web
76
77
 
77
78
  ## Customizing the Backend
78
79
 
@@ -88,7 +89,7 @@ This replaces `docker-compose.yml` with a version that builds from `backend/`, r
88
89
  - **Override models** with decorators in `backend/app/models/`
89
90
  - **Add controllers** in `backend/app/controllers/`
90
91
  - **Configure Spree** in `backend/config/initializers/spree.rb`
91
- - **Add migrations** with `cd backend && bin/rails generate migration`
92
+ - **Add migrations** with `spree generate migration AddFooToSpreeBars foo:string` (runs inside the container)
92
93
 
93
94
  See the [Customization Guide](../customization.md) for more details.
94
95
 
@@ -98,7 +99,7 @@ The project includes [@spree/cli](../cli/quickstart.md) for managing your Spree
98
99
 
99
100
  | Command | Description |
100
101
  |---------|-------------|
101
- | `spree dev` | Start backend services and stream logs |
102
+ | `spree dev` | Run the backend in the foreground — streams logs, Ctrl+C stops it |
102
103
  | `spree stop` | Stop backend services |
103
104
  | `spree update` | Pull latest Spree image and restart (runs migrations automatically) |
104
105
  | `spree eject` | Switch from prebuilt image to building from `backend/` |
@@ -108,7 +108,7 @@ module MyApp
108
108
  subscribes_to 'order.completed'
109
109
 
110
110
  def handle(event)
111
- order = Spree::Order.find_by(id: event.payload['id'])
111
+ order = Spree::Order.find_by_prefix_id(event.payload['id'])
112
112
  return unless order
113
113
 
114
114
  # Sync to fulfillment system, notify warehouse, etc.
@@ -113,19 +113,21 @@ bin/rails g spree:controller_decorator Spree::Admin::ProductsController
113
113
  This creates `app/controllers/spree/admin/products_controller_decorator.rb`:
114
114
 
115
115
  ```ruby
116
- module Spree
117
- module Admin
118
- module ProductsControllerDecorator
119
- def self.prepended(base)
120
- # Class-level configurations go here
121
- end
116
+ module Spree::Admin
117
+ module ProductsControllerDecorator
118
+ def self.prepended(base)
119
+ # base.before_action :my_filter
122
120
  end
123
121
 
124
- ProductsController.prepend(ProductsControllerDecorator)
122
+ # add custom methods here
125
123
  end
126
124
  end
125
+
126
+ Spree::Admin::ProductsController.prepend Spree::Admin::ProductsControllerDecorator
127
127
  ```
128
128
 
129
+ The generator accepts any namespace depth. `Spree::ProductsController` produces a top-level `module Spree` wrapper; `Spree::Api::V3::Store::ProductsController` produces a `module Spree::Api::V3::Store` wrapper. The final `.prepend` line is always fully qualified.
130
+
129
131
  ## Decorating Models
130
132
 
131
133
  ### Changing Behavior of Existing Methods
@@ -487,7 +489,7 @@ module MyApp
487
489
  subscribes_to 'product.updated'
488
490
 
489
491
  def handle(event)
490
- product = Spree::Product.find_by(id: event.payload['id'])
492
+ product = Spree::Product.find_by_prefix_id(event.payload['id'])
491
493
  return unless product
492
494
 
493
495
  # The payload includes changes, check if name changed
@@ -62,7 +62,7 @@ Spree is a flexible platform allowing you to customize every part of it to suit
62
62
  subscribes_to 'order.completed'
63
63
 
64
64
  def handle(event)
65
- order = Spree::Order.find_by(id: event.payload['id'])
65
+ order = Spree::Order.find_by_prefix_id(event.payload['id'])
66
66
  return unless order
67
67
 
68
68
  # Sync to external service, send notification, etc.
@@ -19,7 +19,7 @@ These variables are required to run Spree in production.
19
19
 
20
20
  > **TIP:** This configuration is used for system emails (e.g. staff invitations, report ready, export complete). Customer facing emails are handled by the [storefront via webhooks](../storefront/nextjs/customization.md#transactional-emails).
21
21
 
22
- Spree works with any SMTP provider (Resend, Postmark, Mailgun, SendGrid, Amazon SES, etc.). Set `SMTP_HOST` to enable email delivery — when not set, emails are logged to stdout.
22
+ Spree works with any SMTP provider (Resend, Postmark, Mailgun, SendGrid, Amazon SES, etc.). Set `SMTP_HOST` to enable email delivery — when not set, no delivery method is configured and production email delivery fails (development opens emails in the browser via `letter_opener`).
23
23
 
24
24
  | Variable | Default | Description |
25
25
  | --- | --- | --- |
@@ -25,7 +25,7 @@ markets.forEach(market => {
25
25
 
26
26
  ```bash cURL
27
27
  curl 'https://api.mystore.com/api/v3/store/markets' \
28
- -H 'Authorization: Bearer pk_xxx'
28
+ -H 'X-Spree-API-Key: pk_xxx'
29
29
  ```
30
30
 
31
31
 
@@ -76,7 +76,7 @@ market.countries // [{ iso: "DE", ... }, { iso: "FR", ... }]
76
76
 
77
77
  ```bash cURL
78
78
  curl 'https://api.mystore.com/api/v3/store/markets/mkt_gbHJdmfrXB' \
79
- -H 'Authorization: Bearer pk_xxx'
79
+ -H 'X-Spree-API-Key: pk_xxx'
80
80
  ```
81
81
 
82
82
 
@@ -95,7 +95,7 @@ market.currency // "EUR"
95
95
 
96
96
  ```bash cURL
97
97
  curl 'https://api.mystore.com/api/v3/store/markets/resolve?country=DE' \
98
- -H 'Authorization: Bearer pk_xxx'
98
+ -H 'X-Spree-API-Key: pk_xxx'
99
99
  ```
100
100
 
101
101
 
@@ -119,7 +119,7 @@ countries.forEach(country => {
119
119
 
120
120
  ```bash cURL
121
121
  curl 'https://api.mystore.com/api/v3/store/markets/mkt_gbHJdmfrXB/countries' \
122
- -H 'Authorization: Bearer pk_xxx'
122
+ -H 'X-Spree-API-Key: pk_xxx'
123
123
  ```
124
124
 
125
125
 
@@ -140,7 +140,7 @@ country.states // [{ id: "st_xxx", name: "New York", abbr: "NY" }, ...]
140
140
 
141
141
  ```bash cURL
142
142
  curl 'https://api.mystore.com/api/v3/store/markets/mkt_gbHJdmfrXB/countries/US?expand=states' \
143
- -H 'Authorization: Bearer pk_xxx'
143
+ -H 'X-Spree-API-Key: pk_xxx'
144
144
  ```
145
145
 
146
146