# Shipping options in the checkout

Offer delivery choices in Kustom Checkout: live options from your TMS through the Shipping Assistant, or a fixed shipping_options list sent with the order.

The checkout asks the customer how they want their order delivered. The options come live from
your transport management system (TMS), through the [Shipping Assistant](#shipping-assistant),
or from a fixed list you send with the order, known as
[basic shipping options](#basic-shipping-options). This page is the merchant's side of both. If
you're building the TMS side, start at
[Build a TMS integration](/checkout/shipping/shipping-assistant/).

## What both share

- **The order asks for a delivery.** Set
  [`options.allow_separate_shipping_address`](/api/checkout/create-order-merchant/#body.options.allow_separate_shipping_address)
  to `true`, or send a non-empty
  [`shipping_options`](/api/checkout/create-order-merchant/#body.shipping_options) list, when you
  [create the order](/checkout/integrate/create-order/). A list holding a single option with
  `shipping_method: "Digital"` doesn't count, because it tells Kustom nothing needs shipping.
- **Only physical goods are shipped.** Lines with `type: "physical"`, the default, need a
  delivery. A cart with nothing physical gets no delivery choice.
- **The choice comes back on the order** as `selected_shipping_option`, when you
  [read the order](/checkout/integrate/confirmation-and-push/).
- **The browser hears about it.** The `shipping_option_change` client-side event fires when the
  customer picks an option, with its name, price and tax. See
  [Client-side events](/checkout/client-side-events/) for how to listen for it.

### Who adds the shipping fee

The fee for the chosen option reaches the order in one of two ways.

- **Kustom adds it (the default).** When the customer picks an option, Kustom adds a
  `shipping_fee` line, a zero-amount one for a free option, and raises `order_amount` and
  `order_tax_amount` to match. Don't add it yourself or the fee is counted twice.
- **You add it, through a callback.** Register `merchant_urls.shipping_option_update` and Kustom
  stops adding the fee. It calls you whenever the choice changes instead, and the cart your
  callback answers with must carry the fee. Choose this when you need to control how tax is
  allocated on the shipping fee, for example when a cart mixes VAT rates. See
  [Server-side callbacks](/checkout/callbacks/).

## Which to use

| | Shipping Assistant | Basic shipping options |
| --- | --- | --- |
| Options come from | Your TMS, asked live | A fixed list on the order |
| Change with the address and cart | Yes | No |
| Pickup points on a map, time slots, add-ons | Yes | No |
| Shipment reserved in your TMS | Yes, its id arrives as `tms_reference` | No |
| What you set up | A TMS and a profile in the Kustom Portal | Nothing beyond the order |

**Ingrid passes some additional fields.** When your TMS is Ingrid, the order carries more than
the fields on this page, such as `tos_id`. See the [Ingrid guide](/partners/ingrid/).

Use the Shipping Assistant when what you can offer depends on where the customer is or what
they're buying, or when you want the shipment booked in your TMS as the purchase happens. Use
basic shipping options when one list fits every customer. You'll send basic options with the
Shipping Assistant too: they're what the checkout shows when your TMS can't answer.

## Shipping Assistant

You need a TMS that implements the Shipping Assistant API. Most merchants use one of the
[shipping partners](/partners/shipping-partners/), which already have a connection. Your
carriers, prices and rules live in the TMS, not at Kustom, and you never call the TMS from your
own server: Kustom does, and hands you the result on the order.

**1. Connect your TMS in the Portal.** In the [Kustom Portal](https://portal.kustom.co), add a
Shipping Assistant profile for your TMS to your merchant account, with two values from your TMS
provider:

- **Identifier** — your account's identifier at the TMS.
- **Key** — the secret that goes with it.

A profile applies to the countries you select on it, matched against the customer's shipping
country. A customer shipping to a country no profile covers gets your basic options instead.
Playground and production are separate accounts, so set the profile up in each. Changes can
take a few minutes to reach the checkout.

**2. Send basic options as the fallback.** The `shipping_options` on the order are shown only
when your TMS can't answer: it's down, too slow, or returns something Kustom can't use. Send
them anyway, shaped as in [Basic shipping options](#basic-shipping-options). Without them, a TMS
outage still lets the customer buy, but with an address and no delivery option at all, and the
order arrives with no `selected_shipping_option`.

**3. Describe what's being shipped.** It's optional, but it's what your TMS prices on. Add
`shipping_attributes` to each line, and `tags` to the order for rules that apply to the whole
cart:

```json
{
  "tags": ["member"],
  "order_lines": [
    {
      "type": "physical",
      "reference": "sku-1234",
      "name": "Wool sweater",
      "quantity": 1,
      "unit_price": 79900,
      "tax_rate": 2500,
      "total_amount": 79900,
      "total_tax_amount": 15980,
      "shipping_attributes": {
        "weight": 890,
        "dimensions": { "height": 60, "width": 300, "length": 400 },
        "tags": ["fragile"]
      }
    }
  ]
}
```

`weight` is in grams and `dimensions` in millimeters. Tags are passed through unchanged, so
agree their meaning with your TMS provider.

**4. Read the delivery from the order.** `selected_shipping_option` carries more than a basic
option does:

| Field | What it holds |
| --- | --- |
| `id` | The TMS's id for the option, not one of yours |
| `name`, `price`, `tax_amount`, `tax_rate` | As shown to the customer. `price` includes any time slot, pickup point and add-on surcharge |
| `shipping_method` | The kind of delivery: `Home`, `PickUpPoint`, `BoxUnreg` and so on |
| `delivery_details` | `carrier`, `class`, the carrier `product`, and the chosen `pickup_location` or `timeslot` |
| `selected_addons` | Extras the customer picked, with what they typed, such as a door code |
| `tms_reference` | The shipment your TMS reserved. **Book and print labels against this** |

The `shipping_option_change` event carries neither the carrier nor
`tms_reference`, so read the order for those.

To skip the TMS for a single order without touching the profile, set
`options.tms_configuration_override.disabled` to `true` when you create the order. That
checkout uses your basic options. It only works on create: setting it on an update has no
effect.

## Basic shipping options

Put the choices straight in the order. Kustom shows them in the checkout as they are, for every
customer.

```json title="shipping_options on the order"
{
  "shipping_options": [
    {
      "id": "express",
      "name": "Express 1–2 days",
      "description": "Delivered before 17:00",
      "price": 9900,
      "tax_amount": 1980,
      "tax_rate": 2500,
      "shipping_method": "Home",
      "preselected": false
    },
    {
      "id": "standard",
      "name": "Standard 3–5 days",
      "price": 4900,
      "tax_amount": 980,
      "tax_rate": 2500,
      "shipping_method": "PickUpPoint",
      "preselected": true
    },
    {
      "id": "store",
      "name": "Pick up in store",
      "price": 0,
      "tax_amount": 0,
      "tax_rate": 2500,
      "shipping_method": "PickUpStore"
    }
  ]
}
```

That's the order running in the pane beside this page. `price` includes tax, and `tax_amount` on
an option is the *amount* of tax, not a rate. To change the list once the checkout is open, for
example when the address changes, update the order — see
[Render the checkout snippet](/checkout/integrate/render-checkout-snippet/).

## Check it works

- **Basic options:** the pane beside this page is a live checkout with the three options above.
  Pick one and watch the total follow.
- **Shipping Assistant:** in playground, open a checkout with a shipping address in a country
  your profile covers. The delivery step shows your TMS's options, not your basic list. Complete
  the purchase, read the order and check `selected_shipping_option.tms_reference` is set.
- **The fallback:** repeat with a customer email containing `+fallback`, such as
  `jane+fallback@example.com`. In playground that skips the TMS, so you see what customers get
  when it's unavailable.

## Troubleshooting

| You see | Why |
| --- | --- |
| No delivery step at all | The order didn't ask for one, or the cart had nothing physical |
| Your basic options instead of the TMS's | No profile covers the shipping country, the credentials are wrong, or the TMS failed. Once a checkout falls back, it stays on your basic options |
| "We cannot ship to the given address" | The TMS answered, with no options for that address. That's the TMS's rules, not an error |
| "Your shipping option is unavailable" after Buy | The TMS refused the reservation. The customer picks again and retries |
| An order with no `selected_shipping_option` | The checkout fell back and you sent no basic options |

## Related

- [Shipping partners](/partners/shipping-partners/) — the TMS providers with a connection, and what each needs in the Portal
- [Build a TMS integration](/checkout/shipping/shipping-assistant/) — how Kustom and a TMS talk, for the team building the TMS side
- [Ingrid](/partners/ingrid/)
