Sale Type
This document covers the product-level Sale Type classification - the umbrella concept over the existing dual-sale logic plus the single-sale variants (SELL_THRU_RTS, MOD_ONLY).
Overviewโ
Sale Type is a product-level classification that determines two things at once:
- Whether the PDP shows the Estimated Arrival radio selector
- Whether a rush fee line is added to the cart when Rush Production is the active variant
The classification lives on a single metafield (custom.sale_type) and is read at both render time (PDP and add-on UI) and add-to-cart time (line item attributes).
Metafieldsโ
custom.sale_type (Product Level)โ
Single-line text enum, defaults to DUAL_SALE when unset, empty, or unrecognized.
| Value | PDP Behavior | Cart Behavior | Rush Fee |
|---|---|---|---|
DUAL_SALE (default) | Standard / Rush radio selector + shipping alert render as today | RTS or MTO attributes based on the user's selection; rush fee line added when Rush is selected | Charged via separate rush fee line item |
SELL_THRU_RTS | Selector hidden; heading shows Rush timeline when any Rush variant has stock, else Standard date range | Variant resolved by inventory: Rush while in stock, Standard once Rush sells out - attributes match the resolved variant | Suppressed |
MOD_ONLY | Selector hidden; heading always shows the Standard date range | Resolved variant is always Standard Production; MTO attributes | Suppressed |
custom.rush_fee (Companion Rule)โ
Single-sale products must leave custom.rush_fee unset - not $0.
The $0 GID convention is a dual-sale-only signal that hides Standard and forces free Rush (see Rush Production). On a single-sale product, the explicit absence of any rush fee variant communicates "no rush fee path exists at all." Setting it to $0 on a single-sale product would be ambiguous and is treated as a merch configuration error.
PDP Behaviorโ
Dual sale (DUAL_SALE)โ
No change from prior behavior. The Estimated Arrival fieldset and the gray "Shipping rates still applyโฆ" alert both render. Customers pick Standard or Rush, and the cart line picks up the corresponding attributes.
Single sale (SELL_THRU_RTS and MOD_ONLY)โ
The Estimated Arrival selector fieldset and the shipping alert are both hidden. The "Estimated Arrival: {date}" heading and its info tooltip continue to render - the heading reads from the resolved variant, so customers still see the right date / business-day copy.
For products that don't carry an Estimated Arrival option at all (single-variant accessories such as scarves or ties), the cart treats them the same as today's RTS default - no MTO attributes, no rush fee.
Add-on QuickViewโ
For single-sale add-on products opened in the QuickView drawer:
- The Estimated Arrival option list is filtered out of the rendered options
selectedOptionsis pre-populated with the resolved Estimated Arrival value so the variant resolves correctly when the user saves- The shipping notice (tooltip + alert) reads off the resolved variant
Add-on Upsell tileโ
The "You May Also Need" tile shows a "Get it ..." delivery line only for single-sale add-ons:
MOD_ONLY-> Standard date range (e.g.,Get it Apr 14 - Apr 21)SELL_THRU_RTSwith any Rush variant in stock -> Rush copy (e.g.,Get it in ~5 business days)SELL_THRU_RTSwith no Rush variants in stock -> Standard date rangeDUAL_SALE-> no copy on the tile (unchanged)
Known limitation: the Rush check on the tile is aggregate - it asks "does any Rush variant have stock?" If only some sizes have Rush stock, the tile may advertise the Rush timeline while the resolver later assigns Standard for the size the customer picks. The QuickView and final cart line always show the correct per-size timeline.
Inventory Cutover (SELL_THRU_RTS)โ
The cutover from Rush -> Standard happens per variant, not per product.
For a given (size, color) combination, the resolver assigns the Rush Production variant while it has stock (availableForSale && quantityAvailable > 0). When that specific Rush variant goes out of stock, the resolver falls back to the Standard Production variant for the same combination. Other (size, color) combinations keep their own Rush/Standard state independently.
This means it's normal for a SELL_THRU_RTS product to be partially Rush and partially Standard across sizes during a sell-through.
Cart Attributesโ
Cart attributes are written by the same helpers that handle dual-sale today - prepareReadyToShipProperty (RTS) and prepareMTOProperties (MTO) - but selected by sale type instead of by user choice. For attribute key/value details, see:
The difference for single-sale products is purely in what triggers each branch:
MOD_ONLY-> always MTO attrsSELL_THRU_RTSwith Rush variant resolved -> RTS attrsSELL_THRU_RTSwith Standard fallback (Rush OOS) -> MTO attrs- No rush fee line item is created for any single-sale variant
- No
_rush_fee_amountattribute is added to the main line for any single-sale variant
Resolution Logicโ
The sale type and the resolved variant are computed by two pure helpers in app/lib/saleType.ts:
// Reads custom.sale_type, defaults to DUAL_SALE when missing/unrecognized
getSaleType(product) -> SaleType
// True for MOD_ONLY and SELL_THRU_RTS, false for DUAL_SALE
isSingleSale(saleType) -> boolean
// Picks the variant for a single-sale product:
// MOD_ONLY -> Standard variant matching selectedOptions
// SELL_THRU_RTS -> Rush if in stock, else Standard fallback
// DUAL_SALE -> undefined (caller picks)
resolveSingleSaleVariant({ variants, selectedOptions, saleType }) -> variant | undefined
The PDP's EstimatedArrivalOptionGroup calls resolveSingleSaleVariant() to compute the right Estimated Arrival value and dispatches it through the reducer when the customer picks size. The cart-side logic in addToCart.component.tsx then reads the resolved variant's options to pick MTO vs RTS attributes and to skip the rush fee path.
The Add to Bag click handler in addToCart.component.tsx also calls resolveSingleSaleVariant() directly as a fallback, in case the PDP's useEffect hasn't finished dispatching the resolved arrival into state by the time the customer clicks. This keeps the click self-sufficient and prevents an undefined variant from reaching the cart line builder.
Related Filesโ
Hydrogen Storefrontโ
app/lib/saleType.ts-getSaleType,isSingleSale,resolveSingleSaleVariant,hasInStockRushVariantapp/components/EstimatedArrivalOptionGroup/estimatedArrivalOptionGroup.component.tsx- Hides selector + alert for single-sale; dispatches resolved arrivalapp/components/AddToCart/addToCart.component.tsx- Skips rush fee path for single-sale; routes MTO vs RTS per resolved variantapp/components/AddOnUpsell/addOnUpsell.component.tsx- Renders "Get it ..." line on the upsell tile for single-sale add-onsapp/components/AddOnQuickView/addOnQuickView.component.tsx- Filters Estimated Arrival option list and pre-fills selectedOptions for single-sale add-onsapp/components/ProductOptions/utils.tsx-resolveArrivalDatesshared helperapp/graphql/storefront/ProductQuery.ts-saleTypemetafield queried onPRODUCT_BASIC_FRAGMENTapp/types/pdp.d.ts-SaleTypeunion type
Builder.io Site Settingsโ
Same defaults as dual-sale - see Standard Production and Rush Production. The single-sale paths reuse siteSettings.dualSale.standardProductionDays, mtoDeliveryWindow, productionDays, and productionWeeks for date copy.
Related Documentationโ
- Standard Production - MTO production timeline and metafields
- Rush Production - Rush production timeline, rush fee logic, and the
$0free-rush convention