@spree/docs 0.1.240 → 0.1.242

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 (28) hide show
  1. package/dist/api-reference/store-api/metadata.md +6 -6
  2. package/dist/developer/core-concepts/architecture.md +15 -19
  3. package/dist/developer/core-concepts/carts.md +71 -1
  4. package/dist/developer/core-concepts/delivery-setup.md +3 -1
  5. package/dist/developer/core-concepts/freight.md +279 -0
  6. package/dist/developer/core-concepts/metafields.md +0 -2
  7. package/dist/developer/core-concepts/payments.md +123 -72
  8. package/dist/developer/core-concepts/sellers.md +50 -5
  9. package/dist/developer/create-spree-app/quickstart.md +22 -19
  10. package/dist/developer/customization/api.md +2 -2
  11. package/dist/developer/customization/checkout.md +117 -0
  12. package/dist/developer/customization/configuration.md +76 -175
  13. package/dist/developer/customization/decorators.md +120 -130
  14. package/dist/developer/customization/metadata.md +9 -9
  15. package/dist/developer/customization/model-preferences.md +34 -68
  16. package/dist/developer/customization/permissions.md +13 -28
  17. package/dist/developer/customization/validations.md +16 -43
  18. package/dist/developer/getting-started/quickstart.md +25 -24
  19. package/dist/developer/how-to/custom-promotion.md +1 -1
  20. package/dist/developer/how-to/custom-search-provider.md +2 -2
  21. package/dist/developer/how-to/custom-stock-splitter.md +2 -2
  22. package/dist/developer/sdk/admin/extending.md +1 -1
  23. package/dist/developer/sdk/admin/quickstart.md +1 -1
  24. package/dist/developer/sdk/admin/resources.md +1 -1
  25. package/dist/developer/sdk/configuration.md +1 -1
  26. package/dist/developer/sdk/extending.md +1 -1
  27. package/dist/developer/tutorial/testing.md +45 -91
  28. package/package.json +1 -1
@@ -6,7 +6,7 @@ description: "How to store and retrieve custom metadata on carts, orders, and li
6
6
 
7
7
  The Store API supports storing arbitrary key-value **metadata** on carts (orders) and line items. Metadata is useful for tracking custom information like gift messages, attribution data, or integration-specific fields.
8
8
 
9
- > **NOTE:** Metadata is different from [metafields](../../developer/core-concepts/metafields.md). Metadata is simple JSON storage (untyped, no schema), while metafields are structured, typed, and schema-defined. Use metadata for integration data and internal tracking. Use metafields for customer-facing custom attributes.
9
+ > **NOTE:** Metadata is different from [custom fields](../../developer/core-concepts/metafields.md). Metadata is simple JSON storage (untyped, no schema), while custom fields are structured, typed, and schema-defined. Use metadata for integration data and internal tracking. Use custom fields for customer-facing custom attributes.
10
10
 
11
11
  ## Cart Metadata
12
12
 
@@ -146,18 +146,18 @@ Metadata is stored as a flat JSON object. You can use any keys and values:
146
146
  }
147
147
  ```
148
148
 
149
- ## Metadata vs Metafields
149
+ ## Metadata vs Custom Fields
150
150
 
151
- Spree has two permanent, complementary systems for custom data — **metadata for machines, metafields for humans**.
151
+ Spree has two permanent, complementary systems for custom data — **metadata for machines, custom fields for humans**.
152
152
 
153
- | | Metadata | Metafields |
153
+ | | Metadata | Custom Fields |
154
154
  |-|----------|------------|
155
155
  | **Purpose** | Developer escape hatch — integration IDs, sync state | Merchant-defined structured attributes |
156
- | **Schema** | Schemaless JSON — no definition required | Defined via MetafieldDefinitions (typed, validated) |
156
+ | **Schema** | Schemaless JSON — no definition required | Defined via CustomFieldDefinitions (typed, validated) |
157
157
  | **Validation** | None | Type-specific validation |
158
158
  | **Visibility** | Write-only in Store API, readable in Admin API | Configurable (admin-only or public) |
159
159
  | **Admin UI** | JSON preview | Dedicated form fields |
160
160
  | **Best for** | Integration data, tracking, attribution, write-and-forget | Product specs, custom attributes, customer-facing data |
161
161
  | **API access** | Write via Store API, read via Admin API | Read/write via both APIs |
162
162
 
163
- Both systems are here to stay. For more details on metafields, see the [Metafields guide](../../developer/core-concepts/metafields.md).
163
+ Both systems are here to stay. For more details on custom fields, see the [Custom Fields guide](../../developer/core-concepts/metafields.md).
@@ -5,9 +5,10 @@ description: How Spree fits together — the catalog, the cart, the order, and f
5
5
 
6
6
  ## Overview
7
7
 
8
- Spree is a commerce engine you drive through a REST API. It holds the catalog, works out what a customer owes, takes the money, and tracks what was sent. You build the storefront, the mobile app, or the internal tool on top of it.
8
+ Spree is a commerce engine you drive through a REST API. It holds the catalog, works out what a customer owes, takes the money, and tracks what was delivered. You build the storefront, the mobile app, Point of Sale or an internal tool on top of it.
9
9
 
10
- There is no built-in storefront you have to accept. Everything a customer-facing app needs is on the Store API, and everything a back office needs is on the Admin API. Both ship as typed TypeScript clients.
10
+ There is no built-in storefront you have to accept, however we do provide a [reference one built in Next.js](../storefront/nextjs/quickstart.md).
11
+ Everything a customer-facing app needs is on the Store API, and everything a back office needs is on the Admin API. Both ship as fully typed TypeScript clients.
11
12
 
12
13
  ## The shape of a purchase
13
14
 
@@ -72,7 +73,7 @@ Three things in that diagram are worth calling out, because they're where Spree
72
73
 
73
74
  **Delivery is described by a profile, not a category.** A [delivery profile](fulfillments.md) says how a product travels — physically shipped, or digital — and which locations and methods serve it.
74
75
 
75
- **Stock lives per location.** A variant has a stock level at each [stock location](inventory.md), so availability is a real question about real warehouses rather than a single number.
76
+ **Stock lives per location.** A variant can have a stock level at each [stock location](inventory.md), so availability is a real question about real warehouses rather than a single number.
76
77
 
77
78
  ## The two APIs
78
79
 
@@ -103,6 +104,7 @@ Spree assumes you'll extend it, and gives you three ways in that survive an upgr
103
104
 
104
105
  | Approach | Use it for | Guide |
105
106
  |---|---|---|
107
+ | **Events** | Reacting inside your own app when something happens — indexing a record, calling an API with your credentials | [Events](events.md) |
106
108
  | **Webhooks** | Telling another system something happened — an ERP, a warehouse, an email tool | [Webhooks](webhooks.md) |
107
109
  | **Custom fields** | Storing your own data on a product, order or customer without changing the schema | [Custom Fields](metafields.md) |
108
110
  | **Providers** | Plugging in a real service for tax, delivery rates, payments or search | [Providers](../providers/overview.md) |
@@ -116,32 +118,26 @@ One Spree installation can run more than one business:
116
118
  - **[Stores](stores.md)** are separate businesses — their own catalog, orders, customers and settings. Data does not cross between them.
117
119
  - **[Channels](channels.md)** are the ways one store sells: a website, a mobile app, a retail till.
118
120
  - **[Markets](markets.md)** are the regions a store sells into, each with its currency, locale and tax treatment.
119
- - **[Sellers](products.md#seller-submissions)** let one store list goods from many vendors, for a marketplace.
121
+ - **[Sellers](sellers.md)** let one store list goods from many vendors, for a marketplace.
120
122
 
121
123
  ## What you install
122
124
 
123
- | Package | What it is |
124
- |---|---|
125
- | `spree` | The engine — catalog, checkout, payments, both APIs |
126
- | `spree_emails` | Transactional email, if you don't send your own |
127
- | `spree_dashboard` | The back-office dashboard |
128
-
129
- Providers for search, delivery rates, tax and payments install separately, so you only carry what you use.
130
-
131
- **TypeScript:**
132
-
133
125
  | Package | What it is |
134
126
  |---|---|
135
127
  | [`@spree/sdk`](../sdk/quickstart.md) | Store API client — for your storefront |
136
128
  | [`@spree/admin-sdk`](../sdk/admin/quickstart.md) | Admin API client — for back-office and integrations |
137
129
  | [`@spree/cli`](../cli/quickstart.md) | Command line for local setup and calling the Admin API |
130
+ | [`@spree/dashboard`](../dashboard/overview.md) | The admin interface, a React app you own and extend |
138
131
 
139
- > **INFO:** Spree runs on PostgreSQL, MySQL or SQLite. PostgreSQL is recommended for production — [database configuration](../deployment/database.md).
132
+ > **INFO:** Spree runs on PostgreSQL, MySQL or SQLite. Check [database configuration](../deployment/database.md) for more details.
140
133
 
141
134
  ## Where to go next
142
135
 
136
+ Two ways in, depending on how you prefer to learn.
137
+
138
+
139
+ - [Build something](../tutorial/introduction.md) — Add one feature end to end — a model with its own API, a screen in the dashboard, a page on the storefront, and the tests around it. Start here if you learn by doing.
140
+ - [Read the reference](products.md) — Every part of the platform, one page each: products and pricing, carts and orders, payments, fulfillment, promotions. Start here if you already know what you need.
141
+
143
142
 
144
- - [Quickstart](../getting-started/quickstart.md) — Get an installation running.
145
- - [Store SDK](../sdk/quickstart.md) — Build a storefront against the Store API.
146
- - [Products](products.md) — How the catalog is modelled.
147
- - [Carts](carts.md) — Cart, checkout, and what completion guarantees.
143
+ Building a storefront rather than extending the backend? Go straight to the [Spree Next.js Storefront](../storefront/nextjs/quickstart.md).
@@ -131,10 +131,80 @@ A cart tells you what it still needs before it can be completed:
131
131
  }
132
132
  ```
133
133
 
134
- An empty `requirements` array means the cart is ready.
134
+ Every entry has the same four fields. Branch on `code` — `message` is translated prose
135
+ meant for a person, and `step` is only a grouping hint.
135
136
 
136
137
  This is what lets you design your own checkout. Spree tells you what's missing; you decide how and when to ask for it — one long page, a few steps, or whatever suits your storefront.
137
138
 
139
+ Seven checks run on every cart read:
140
+
141
+ | `code` | `step` | Raised when |
142
+ |---|---|---|
143
+ | `line_items_required` | `cart` | The cart is empty |
144
+ | `email_required` | `address` | No email address |
145
+ | `ship_address_required` | `address` | Something needs shipping and there is no address |
146
+ | `delivery_method_required` | `delivery` | A delivery method hasn't been chosen |
147
+ | `payment_required` | `payment` | The total isn't covered by payments |
148
+ | `po_number_required` | `address` | A company requires a purchase-order number |
149
+ | `order_minimum_not_met` | `cart` | Below the order minimum, with the shortfall in the message |
150
+
151
+ > **WARNING:** **An empty `requirements` array does not guarantee completion will succeed.** What a
152
+ > cart reports is the *advisory* set. Completing one additionally checks stock
153
+ > (`out_of_stock`, `discontinued`), negotiated quantity rules
154
+ > (`quantity_rule_violated`) and the guest-checkout policy
155
+ > (`guest_checkout_not_allowed`) — each of which has to load line items, so they are
156
+ > held back to keep reading a cart cheap. Treat a failed completion as a normal path,
157
+ > not an exception.
158
+
159
+ ### Checkout steps
160
+
161
+ A cart also reports a step list, so you can render a progress indicator:
162
+
163
+
164
+ ```typescript Store SDK
165
+ const cart = await client.carts.get(cartId)
166
+
167
+ cart.current_step // "delivery"
168
+ cart.completed_steps // ["address"]
169
+ ```
170
+
171
+ ```json Response
172
+ {
173
+ "current_step": "delivery",
174
+ "completed_steps": ["address"]
175
+ }
176
+ ```
177
+
178
+
179
+ Spree has no checkout state machine. The list is computed from the cart's own data,
180
+ and nothing on the server refuses a write because of where the customer appears to be
181
+ — you can set an address, choose delivery and attach a payment in any order.
182
+ `current_step` is simply the step of the first unmet requirement.
183
+
184
+ Five steps are built in, and three only appear when they apply:
185
+
186
+ | Step | Present when |
187
+ |---|---|
188
+ | `address` | Always |
189
+ | `delivery` | The cart has items and isn't entirely digital |
190
+ | `payment` | The total is above zero |
191
+ | `confirm` | A payment method asks for one, or the store always wants one |
192
+ | `complete` | Always |
193
+
194
+ So a free digital order reports `address` then `complete`. A storefront that hardcodes
195
+ five steps will show two that never apply.
196
+
197
+ > **NOTE:** Requirements use one grouping name that is not a step: **`cart`**. Missing line
198
+ > items, the order minimum and stock problems are filed under it, and there is no
199
+ > `cart` entry in the step list — `current_step` reports those as `address`. If you
200
+ > group requirements by `step` to place them on screens, give `cart` a home.
201
+
202
+ Checkout introspection lives on the cart, not the order. An order has already been
203
+ placed, so `current_step` and `requirements` do not appear on it.
204
+
205
+ To add your own step or requirement, see
206
+ [Customizing checkout](../customization/checkout.md).
207
+
138
208
  ## Checkout
139
209
 
140
210
  **Step 1: Add the customer's details**
@@ -155,7 +155,8 @@ otherwise every parcel is quoted light.
155
155
 
156
156
  Package types are that vocabulary: the box parcels ship in, and for wholesale,
157
157
  the cartons products are packed into and the pallets or containers an order
158
- leaves on.
158
+ leaves on. How those cartons and pallets roll up into a quote is
159
+ [freight](freight.md).
159
160
 
160
161
 
161
162
  ```typescript Admin SDK
@@ -248,6 +249,7 @@ box of their own with all three dimensions and a weight recorded. See
248
249
 
249
250
  ## Related
250
251
 
252
+ - [Freight](freight.md) — cartons, pallets and wholesale shipping
251
253
  - [Fulfillments](fulfillments.md) — what happens to the parcel afterwards
252
254
  - [Calculators](calculators.md) — how a price is worked out
253
255
  - [EasyPost](../../integrations/shipping/easypost.md) — live carrier rates and labels
@@ -0,0 +1,279 @@
1
+ ---
2
+ title: Freight
3
+ description: How wholesale orders ship — the carton and pallet chain a product declares, the logistics rollup a forwarder quotes against, and rates that carry no price yet.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ A wholesale order does not leave the warehouse as parcels. It leaves as
9
+ cartons, a pallet, or a container, and for international freight the price is
10
+ quoted by a forwarder after someone looks at the order — not by an API at
11
+ checkout.
12
+
13
+ Four pieces make that work, and none of them disturb retail shipping:
14
+
15
+ | Piece | Question it answers |
16
+ |---|---|
17
+ | **Carton details** on a product | How do these units pack? |
18
+ | **Freight summary** on a cart or order | How big is the whole load? |
19
+ | **Volume and company rules** on a delivery method | Which shipment tier is this? |
20
+ | **Unpriced rates** | What does a buyer see before a price exists? |
21
+
22
+ One catalog serves both audiences. The same product ships parcel to a shopper
23
+ and carton to a trade buyer, because freight methods sit on the ordinary
24
+ shipping profile and are gated by who is buying rather than by what is bought.
25
+ See [delivery setup](delivery-setup.md) for profiles,
26
+ zones and methods, and [package types](delivery-setup.md#packaging)
27
+ for the packaging vocabulary this builds on.
28
+
29
+ ## How products pack
30
+
31
+ Freight numbers are not derivable from retail shipping data. A case of 48
32
+ bottles is one carton, not 48 parcels — so a product says how its units pack,
33
+ and the rest follows the chain **unit → carton → pallet → cubic meters and
34
+ weight**.
35
+
36
+ The geometry lives on a shared carton, because merchants reuse a handful of
37
+ standard sizes across hundreds of products and one edit should fix all of
38
+ them. What varies per product stays on the product:
39
+
40
+ | Field | What it says |
41
+ |---|---|
42
+ | `carton_package_type_id` | Which carton this is packed into. Must be a package type of kind `carton` |
43
+ | `units_per_carton` | How many units one carton holds |
44
+ | `carton_weight` | What a packed carton weighs, gross. Optional |
45
+ | `cartons_per_pallet` | How those cartons stack |
46
+ | `purchase_unit` | `unit` or `carton` — what a buyer is quoted in |
47
+
48
+
49
+ ```typescript Admin SDK
50
+ // The carton, once, shared by every product packed in it.
51
+ const carton = await adminClient.packageTypes.create({
52
+ name: 'Master carton',
53
+ kind: 'carton',
54
+ length: 40, width: 30, height: 25, dimensions_unit: 'cm',
55
+ weight: 0.4, max_weight: 20, weight_unit: 'kg',
56
+ })
57
+
58
+ // How this product fills it.
59
+ await adminClient.products.variants.update('prod_xxx', 'variant_xxx', {
60
+ carton_package_type_id: carton.id,
61
+ units_per_carton: 48,
62
+ carton_weight: 19.2,
63
+ cartons_per_pallet: 40,
64
+ purchase_unit: 'carton',
65
+ })
66
+ ```
67
+
68
+ ```bash cURL
69
+ curl -X POST 'https://api.mystore.com/api/v3/admin/package_types' \
70
+ -H 'X-Spree-API-Key: sk_xxx' \
71
+ -H 'Content-Type: application/json' \
72
+ -d '{
73
+ "name": "Master carton", "kind": "carton",
74
+ "length": 40, "width": 30, "height": 25, "dimensions_unit": "cm",
75
+ "weight": 0.4, "max_weight": 20, "weight_unit": "kg"
76
+ }'
77
+
78
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/products/prod_xxx/variants/variant_xxx' \
79
+ -H 'X-Spree-API-Key: sk_xxx' \
80
+ -H 'Content-Type: application/json' \
81
+ -d '{
82
+ "carton_package_type_id": "pkgtype_xxx",
83
+ "units_per_carton": 48,
84
+ "carton_weight": 19.2,
85
+ "cartons_per_pallet": 40,
86
+ "purchase_unit": "carton"
87
+ }'
88
+ ```
89
+
90
+
91
+ Stored quantities are always units, at every level. `purchase_unit` only
92
+ changes the vocabulary a storefront presents — a buyer shown "2 cartons" is
93
+ buying 96 units.
94
+
95
+ Two data errors are refused while the merchant is still editing rather than
96
+ discovered through a customer complaint: an `order_multiple` that straddles
97
+ carton boundaries can never ship whole, and quoting in cartons without saying
98
+ how many units one holds leaves a storefront no way to render the offer.
99
+
100
+ Reading a product back adds `units_per_pallet`, derived from the two divisors
101
+ so a merchant sees the far end of the chain without doing the arithmetic. It
102
+ is null unless both halves are recorded.
103
+
104
+ > **NOTE:** A product may only be packed into a package type of kind `carton`, and a
105
+ > carton with products packed into it cannot later become a pallet. Repack the
106
+ > products first.
107
+
108
+ ## The freight summary
109
+
110
+ The **freight summary** is the load as a forwarder reads it: how many units,
111
+ the cartons they fill, the pallets those stack onto, the cubic meters they
112
+ occupy and what the whole thing weighs.
113
+
114
+ ```json
115
+ {
116
+ "total_units": 480,
117
+ "total_cartons": 10,
118
+ "total_pallets": 1,
119
+ "total_volume": "0.3",
120
+ "total_weight": "192.0",
121
+ "complete": true
122
+ }
123
+ ```
124
+
125
+ `total_volume` is cubic meters and `total_weight` is kilograms, both as
126
+ decimal strings. `total_pallets` is null unless every carton-bearing line says
127
+ how it stacks — one silent product would understate the load, so no figure is
128
+ better than a wrong one.
129
+
130
+ **`complete` is the field to read before quoting from any of it.** False means
131
+ part of the catalog carries no carton data, so those products were measured
132
+ loose from their unit dimensions instead. The numbers are still the best
133
+ available and real enough to quote against, but they are a floor rather than a
134
+ total, and a surface using them should say so.
135
+
136
+ A cart of unmeasured goods has no freight summary at all rather than one full
137
+ of zeros, which would read as a shipment that takes up no space.
138
+
139
+ ### Where it appears
140
+
141
+ | Response | Shape |
142
+ |---|---|
143
+ | Store cart | Totals only |
144
+ | Store delivery rate | Totals only, on freight rates |
145
+ | Admin cart and order | Totals plus a `lines` breakdown |
146
+ | Admin delivery rate | Totals plus `lines` |
147
+
148
+ The per-product breakdown is back-office only. A buyer is told what their
149
+ shipment costs and how big it is; which SKU accounts for which part of it is
150
+ the warehouse's business. Each admin line carries `variant_id`, `sku`, `name`,
151
+ `units`, `cartons`, `pallets`, `units_per_carton`, `cartons_per_pallet`,
152
+ `weight_per_carton`, `volume`, `weight` and its own `complete` flag.
153
+
154
+
155
+ ```typescript SDK
156
+ const cart = await client.carts.get('cart_xxx')
157
+
158
+ if (cart.freight_summary) {
159
+ const { total_cartons, total_pallets, total_volume, complete } = cart.freight_summary
160
+ // `complete: false` — show these as a minimum, not a total.
161
+ }
162
+ ```
163
+
164
+ ```bash cURL
165
+ curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
166
+ -H 'X-Spree-API-Key: pk_xxx' \
167
+ -H 'X-Spree-Token: guest_token'
168
+ ```
169
+
170
+
171
+ > **NOTE:** A placed order's summary is never re-derived from the live catalog. The freight
172
+ > provider freezes a copy onto the rate it quoted, and the order reads that — so
173
+ > a carton size corrected next month does not rewrite what this container held.
174
+ > Two freight consignments on one order are merged into the one load the
175
+ > forwarder sees, and a canceled one is left out.
176
+
177
+ ## Freight rates
178
+
179
+ A freight method is an ordinary [delivery method](delivery-setup.md#delivery-methods-and-zones)
180
+ priced by the **Freight** rate provider. That provider quotes nothing and says
181
+ so: it returns a rate with no price, carrying the freight summary the merchant
182
+ will send to the forwarder in place of an amount.
183
+
184
+ Such a rate comes back with `unpriced: true`, and every money field on it reads
185
+ **"Quoted after review"** rather than a figure. That matters: a zero cost
186
+ rendering as "Free" over a container of goods is a promise the merchant cannot
187
+ keep. Unpriced rates are also sorted after every priced one and are never
188
+ preselected, so a buyer with a real parcel option still sees it first.
189
+
190
+
191
+ ```typescript SDK
192
+ const rates = fulfillment.delivery_rates
193
+
194
+ rates.forEach((rate) => {
195
+ if (rate.unpriced) {
196
+ rate.display_total // "Quoted after review"
197
+ rate.freight_summary // what the forwarder will quote against
198
+ } else {
199
+ rate.display_total // "$24.00"
200
+ }
201
+ })
202
+ ```
203
+
204
+ ```bash cURL
205
+ curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
206
+ -H 'X-Spree-API-Key: pk_xxx' \
207
+ -H 'X-Spree-Token: guest_token'
208
+ ```
209
+
210
+
211
+ Checkout completes on an unpriced rate. The shipping price arrives afterwards,
212
+ once the forwarder has quoted it.
213
+
214
+ > **NOTE:** How a wholesale buyer **pays** — deposits, net terms, credit limits — is a
215
+ > separate subject and is not part of freight in 6.0. Partial payments are
216
+ > generally available and predate this work. See
217
+ > [payments](payments.md).
218
+
219
+ ## Shipment tiers
220
+
221
+ Which freight method a load qualifies for is configuration, not code. Each
222
+ tier — "Cartons", "Pallet", "20ft container" — is an ordinary delivery method
223
+ bounded by a rule.
224
+
225
+ **Volume rule.** Bounds the method by packed volume in cubic meters: a pallet
226
+ method takes 1–15 CBM, a 20ft container 15–33, and the estimator offers
227
+ whichever one the order actually fills.
228
+
229
+ Its minimum is deliberately forgiving. A partly measured catalog understates
230
+ the load, so the figure is a floor rather than a measurement — it may raise a
231
+ shipment into a tier but never exclude it from one. A maximum still applies,
232
+ since passing one on too small a number errs toward offering the method.
233
+
234
+ **Company rule.** Splits freight from parcel by who is buying. On, the method
235
+ is offered only to orders placed for a [company](companies.md),
236
+ so a carton tier disappears from retail carts. Off, only to orders that are
237
+ not, which keeps parcel methods away from wholesale buyers. A method with no
238
+ company rule is offered to both, so the rule is only ever added to state a
239
+ split.
240
+
241
+
242
+ ```typescript Admin SDK
243
+ await adminClient.deliveryMethods.create({
244
+ name: 'Pallet freight',
245
+ rate_provider: 'Spree::DeliveryRateProvider::Freight',
246
+ rules: [
247
+ { type: 'volume_rule', preferences: { minimum_volume: 1, maximum_volume: 15 } },
248
+ { type: 'company_rule', preferences: { company_orders_only: true } },
249
+ ],
250
+ })
251
+ ```
252
+
253
+ ```bash cURL
254
+ curl -X POST 'https://api.mystore.com/api/v3/admin/delivery_methods' \
255
+ -H 'X-Spree-API-Key: sk_xxx' \
256
+ -H 'Content-Type: application/json' \
257
+ -d '{
258
+ "name": "Pallet freight",
259
+ "rate_provider": "Spree::DeliveryRateProvider::Freight",
260
+ "rules": [
261
+ { "type": "volume_rule", "preferences": { "minimum_volume": 1, "maximum_volume": 15 } },
262
+ { "type": "company_rule", "preferences": { "company_orders_only": true } }
263
+ ]
264
+ }'
265
+ ```
266
+
267
+
268
+ A freight method never consults a calculator — there is no price to work out —
269
+ so admin surfaces hide its pricing form. `GET /api/v3/admin/delivery_methods/rate_providers`
270
+ lists the providers installed on the store, which is what a picker should read
271
+ rather than a hardcoded list.
272
+
273
+ ## Related
274
+
275
+ - [Delivery setup](delivery-setup.md) — profiles, zones, methods and package types
276
+ - [Fulfillments](fulfillments.md) — what happens to the consignment afterwards
277
+ - [Companies](companies.md) — who a wholesale order is placed for
278
+ - [Build a B2B store](../how-to/build-a-b2b-store.md) — the wholesale setup end to end
279
+ - [Package types](/user/settings/package-types) — configuring packaging in the dashboard
@@ -9,8 +9,6 @@ Sooner or later you need to store something Spree doesn't have a field for. A fa
9
9
 
10
10
  Custom fields are how you do that without changing the database. You declare a field once — its name, its type, who can see it — and from then on it can be set on any record of that kind, edited in the dashboard, returned by the API, and searched or filtered like any built-in field.
11
11
 
12
- > **NOTE:** Custom fields are also known as **metafields** in the dashboard. The API and SDK call them custom fields throughout.
13
-
14
12
  ## Definitions and values
15
13
 
16
14
  There are two halves, and it helps to keep them straight: