Shipping
Change which shipping rates shoppers see, how tracking links are built and how Litestore estimates arrival dates.
Each shipping rate in Litestore lists the countries, location and sales channel it serves. Read this page when you change how rates are offered at checkout, how tracking links work, or how delivery dates are estimated.
Litestore has no shipping zones, and the codebase has no zone model, field or route.
In many systems, rates belong to zones and zones contain countries, so finding out where a rate applies takes two lookups. In Litestore, each rate stores its own list of countries.
The trade-off is that two rates for the same twenty countries each repeat the list, but any rate shows right away where it applies.
Rate coverage
A rate has a list of country codes, and it can also name one location and one channel. These three settings filter independently, and each one has a clear meaning when you leave it empty.
| Setting | What it does | When empty |
|---|---|---|
| Countries | It lists the country codes the rate serves. | The rate serves every country. |
| Location | It ties the rate to one warehouse. | The rate is not tied to a particular warehouse. |
| Channel | It limits the rate to one sales channel. | The rate is global and every channel can use it. |
minOrderAmount | It sets the smallest order that qualifies. | No minimum applies. |
maxOrderAmount | It sets the largest order that qualifies. | No maximum applies. |
Free shipping doesn't need a separate feature. You set it up as a rate with a price of zero and a minimum order amount, so there's no free-shipping flag to keep in sync with a threshold and no separate code path that removes the charge. deriveFreeShipping in modules/shipping/free-shipping.ts works out the "free over $X" threshold from those rates, so the storefront and the assistant always quote the same number.
Fulfilment methods
A rate's type is one of these values:
delivery
pickup
digitalThe type is a string, not a database enum, so a new method, such as lockers or a courier, doesn't need a migration. The list of valid values is in platform/db/enums.ts and the matching zod enum.
Each rate has an explicit position. The shopper sees rates at checkout in the order the operator arranged them, not in the order they happened to be created.
Currency
Each rate has its own currency, and Litestore only shows rates whose currency matches the checkout session's currency.
This filter is necessary because Stripe rejects a session if any of its shipping_options uses a different currency from the session. For a store that sells in several currencies, one rate in the wrong currency would stop checkout from working at all.
Litestore also stores each price as an amount and its currency together, so no code can read the number without the currency that goes with it.
Rates in other currencies are hidden
Litestore doesn't show a rate priced in another currency, even as an approximate option. If a market shows no delivery options, check the rate currencies before you check the country lists.
Tracking
Litestore builds tracking URLs automatically for four carriers, and these four make up the carrier dropdown in the editor.
usps
ups
fedex
dhlBecause the list is short and fixed, Litestore can build the customer's "Track your package" link from the carrier and the tracking number. The operator doesn't have to paste a full URL for every shipment. For any other carrier, Litestore saves the tracking number without a link.
buildTrackingUrl returns null when the carrier is unknown or internal, and also when the tracking number is missing. The caller then uses a URL that the operator entered by hand, or leaves the link empty. A shipment with an unknown carrier still saves its tracking number and still shows it.
Arrival estimates
Litestore estimates arrival dates from a fulfilment calendar of working days and holiday closures. It doesn't call any carrier API to find out when a parcel will arrive.
Litestore calculates each estimate when it is needed and never stores it:
handling (processing) time
+
the rate's transit days
↓
counted in the store's working daysThe rate provides minDays and maxDays. Either one can be null when the rate doesn't give that number, and then that end of the estimate is null too. Litestore reports a missing number as unknown instead of guessing.
Litestore reads the working days from the store's fulfilment calendar in modules/shipping/fulfillment-calendar.ts, which lists the weekdays the store ships and its own holiday closures. Both are constants in platform/config/store-policies.ts (SHIPPING_WORKING_DAYS, default Monday to Friday, and SHIPPING_HOLIDAYS), together with DEFAULT_HANDLING_DAYS. You change them in code. A store with a Friday and Saturday weekend, a dropshipper that ships seven days a week, and a maker who only ships on Mondays, Wednesdays and Fridays all get accurate estimates.
Litestore does this calculation in one place, so the product page, the cart and the order page always show the same arrival date.
Adding shipping rules
| The rule is about | Where it goes |
|---|---|
| Where a rate applies | Add it as a setting on the rate, next to countries, location and channel. Don't add a new branch to checkout. |
| When a parcel arrives | Put it in the estimate function or the fulfilment calendar, so every page that shows a date shows the same one. |
| A carrier's own systems | The delivery estimates shoppers see at checkout should stay conservative. Litestore builds a tracking link from a number the operator typed in, and it doesn't receive the parcel's actual status. |
Before you add a rule, check whether it's based on something the store configured or on information only the carrier has, and be clear about which of the two the customer sees.
Related
Tax
Litestore works out a tax rate through a provider seam, Stripe Tax falls back to the flat channel rate, and some tax features are left out on purpose.
Checkout
Litestore turns a cart into a Stripe payment and holds stock for the shopper while they pay.
Inventory
Litestore adds up stock across locations, the database limits stock changes, and concurrent checkouts can't sell the same unit.