docs/developer/core-concepts/shipments.mdx
A shipment represents a package being sent to a customer from a Stock Location. Each order can have one or more shipments — Spree automatically splits orders into multiple shipments when items need to ship from different locations or require different shipping methods.
erDiagram
Order ||--o{ Shipment : "has many"
Shipment ||--o{ ShippingRate : "has many"
Shipment ||--o{ InventoryUnit : "has many"
Shipment }o--|| StockLocation : "ships from"
Shipment }o--|| ShippingMethod : "selected method"
ShippingMethod ||--o{ ShippingRate : "has many"
ShippingMethod }o--o{ Zone : "serves"
ShippingMethod }o--o{ ShippingCategory : "handles"
ShippingMethod }o--o{ Store : "available in"
Product }o--|| ShippingCategory : "belongs to"
Shipment {
string number
string tracking
string state
decimal cost
datetime shipped_at
}
ShippingMethod {
string name
string tracking_url
integer position
}
ShippingRate {
decimal cost
boolean selected
}
Key relationships:
| Attribute | Description | Example |
|---|---|---|
number | Unique shipment identifier | H12345678901 |
tracking | Carrier tracking number | 1Z999AA10123456784 |
state | Current shipment state | shipped |
cost | Shipping cost | 9.99 |
shipped_at | When the shipment was shipped | 2025-07-21T14:36:00Z |
stock_location | Where items ship from | Warehouse NYC |
selected_shipping_rate | The rate chosen by the customer | { cost: 9.99, name: "UPS Ground" } |
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.
<CodeGroup>// Get shipments with available shipping rates
const order = await client.orders.get(orderId, {
expand: ['shipments'],
})
// Each shipment has available shipping rates
order.shipments?.forEach(shipment => {
console.log(shipment.number) // "H12345678901"
console.log(shipment.shipping_rates) // [{ id: "sr_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
})
// Select a shipping rate
await client.carts.shipments.update(cartId, shipment.id, {
selected_shipping_rate_id: 'sr_xxx',
})
# Get shipments
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=shipments' \
-H 'Authorization: Bearer pk_xxx' \
-H 'X-Spree-Token: abc123'
# Select a shipping rate
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/shipments/shp_xxx' \
-H 'Authorization: Bearer pk_xxx' \
-H 'X-Spree-Token: abc123' \
-H 'Content-Type: application/json' \
-d '{ "selected_shipping_rate_id": "sr_xxx" }'
Shipping methods represent the carrier services available to customers (e.g., UPS Ground, FedEx Overnight, DHL International). Each shipping method is scoped to:
Only methods whose zone matches the customer's shipping address are offered at checkout.
Shipping categories group products with similar shipping requirements. For example:
Each product is assigned a shipping category. Shipping methods can be restricted to handle only certain categories, and the shipping cost calculator uses the category to determine pricing.
Each shipping method uses a Calculator to determine the cost. Spree includes these built-in calculators:
| Calculator | Description |
|---|---|
| Flat rate per order | Same cost regardless of items |
| Flat rate per item | Fixed cost per item |
| Flat percent | Percentage of the order total |
| Flexible rate | One rate for the first item, another for each additional |
| Price sack | Tiered pricing based on order total |
You can create custom calculators for more complex pricing. See the Calculators guide.
When items in an order ship from different stock locations or have different shipping categories, Spree automatically splits the order into multiple shipments.
flowchart TB
A[Order with 3 items] --> B{Stock check}
B --> C["Item A & B: NYC Warehouse"]
B --> D["Item C: LA Warehouse"]
C --> E["Shipment 1: UPS Ground"]
D --> F["Shipment 2: FedEx 2Day"]
By default, Spree also splits shipments when a package exceeds a weight threshold (default: 150 units). This prevents individual packages from being too heavy.
<Info> Split shipment behavior is customizable. See the [Customization Quickstart](/developer/customization/quickstart) for details on creating custom splitting rules. </Info>A store selling T-shirts to the US and Europe with 2 carriers:
| Method | Zone | Pricing |
|---|---|---|
| USPS Ground | US | $5 first item + $2 each additional |
| FedEx | EU | $10 per item |
This requires:
A store shipping from 2 locations (New York, Los Angeles) with 3 carriers and 3 shipping categories:
| Category / Method | DHL | FedEx | USPS |
|---|---|---|---|
| Light | $5/item | $10 flat | $8/item |
| Regular | $5/item | $2/item | $8/item |
| Heavy | $50/item | $20 + $15/add'l | $20/item |
shipment.shipped)