Litestore · Developer docs
Commerce

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.

Google Merchant CenterProduct feed
Meta CommerceProduct feed
AI shopping agentsCatalog feed
Your catalog
EmailWith consent tracking
SearchSitemap and structured data
ReviewsRatings on product pages
A rate serves every sales channel, or only the one you limit it to.

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.

SettingWhat it doesWhen empty
CountriesIt lists the country codes the rate serves.The rate serves every country.
LocationIt ties the rate to one warehouse.The rate is not tied to a particular warehouse.
ChannelIt limits the rate to one sales channel.The rate is global and every channel can use it.
minOrderAmountIt sets the smallest order that qualifies.No minimum applies.
maxOrderAmountIt 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
digital

The 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
dhl

Because 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 days

The 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 aboutWhere it goes
Where a rate appliesAdd it as a setting on the rate, next to countries, location and channel. Don't add a new branch to checkout.
When a parcel arrivesPut it in the estimate function or the fulfilment calendar, so every page that shows a date shows the same one.
A carrier's own systemsThe 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.

On this page