The death of legacy shipping scripts and client DOM hacks
For over a decade, complex Shopify Plus logistics relied on Script Editor. Developers wrote small snippets of Ruby that iterated over shipping rates, inspecting cart attributes and deleting air couriers or standard post whenever heavy items were detected.
Merchants on standard plans or those attempting to cut corners frequently resorted to theme JavaScript: listening for address field changes on the cart page and attempting to hide shipping methods via CSS selectors or redirecting buyers before checkout reached the payment step.
Both mechanisms are dead or dying: legacy Shopify Scripts are permanently retired under Checkout Extensibility, executing in an isolated sandbox where Ruby scripts do not run, while the Checkout Extensibility DOM is entirely closed to external script tags, jQuery listeners, and CSS hacks.
If shipping options must adapt dynamically based on line-item characteristics, weight distributions, or destination address constraints, the rule must execute server-side directly within Shopify's checkout engine.
How Delivery Customization Functions execute
Shopify Functions replace legacy scripts with compiled WebAssembly binaries executed directly on Shopify's distributed infrastructure. When a customer inputs or updates their shipping address during checkout, Shopify gathers candidate delivery rates from your configured Shipping Profiles and external Carrier Service APIs.
Before those shipping methods are presented to the buyer, Shopify triggers your registered Delivery Customization Function (purchase.delivery-customization.run).
This execution pipeline imposes strict system constraints: a deterministic execution budget of under 5 milliseconds and 256 KB memory, zero network I/O (cannot query external carrier APIs at runtime), declarative mutations only (hide, rename, move), and a limit of 5 active delivery customization functions per store.
Evaluating cart lines: hazmat, freight tiers, and address geometry
To enforce accurate logistics rules, your checkout function must evaluate three intersecting data planes:
1. Line-Item Merchandise Metafields: Standard Shopify variants provide only gross weight in grams. A 20-kilogram steel recovery winch and a 20-kilogram container of shock damper fluid represent completely distinct logistics risks. By binding structured metafields (custom.requires_freight, custom.is_hazardous) to variants, the checkout function can identify whether items require LTL freight or face civil aviation cargo embargoes.
2. Destination Address Geometry: Parcel carriers deliver to Post Office boxes, but commercial freight carriers operating 18-tonne lorries require physical roadside access or commercial loading docks. A delivery customization rule must inspect address lines using robust international regex patterns that recognise standard PO Boxes, German Postfach, Dutch Postbus, and French Boîte Postale entries.
3. Aggregate Weight Floors: While an individual line item may be small, a customer purchasing ten cast-iron brake rotors creates an aggregate shipment weight exceeding 70 kilograms, necessitating an automatic switch to palletised freight.
Pure WebAssembly delivery rules without checkout freezes
The delivery customization function must be pure and deterministic. It evaluates line-item hazmat classifications, detects PO Box address entries, suppresses ineligible shipping methods, prioritises freight services, and enforces a defensive fallback to ensure checkout never freezes:
export interface DeliveryOption {
handle: string;
title: string;
description?: string;
cost: number;
}
export interface DeliveryAddress {
address1?: string;
address2?: string;
city?: string;
provinceCode?: string;
countryCode: string;
zip?: string;
}
export interface CartLineItem {
id: string;
sku: string;
quantity: number;
weightGrams: number;
requiresFreight?: boolean;
isHazardous?: boolean; // Lithium batteries, aerosols, winches, damper fluid
}
export interface DeliveryCustomizationInput {
deliveryOptions: DeliveryOption[];
deliveryAddress?: DeliveryAddress;
lines: CartLineItem[];
}
export type Operation =
| { hide: { deliveryOptionHandle: string } }
| { rename: { deliveryOptionHandle: string; title: string } }
| { move: { deliveryOptionHandle: string; index: number } };
export interface DeliveryCustomizationResult {
operations: Operation[];
errors: string[];
warnings: string[];
retainedHandles: string[];
}
const PO_BOX_REGEX = /\b(p[\s.]*o[\s.]*box|post[\s.]*office[\s.]*box|p\.?o\.?b\.?|box\s+\d+|postfach|postbus|bo[iî]te\s+postale)\b/i;
const AIR_EXPRESS_REGEX = /\b(air|express|overnight|next\s*day|priority|aviation)\b/i;
const PARCEL_GROUND_REGEX = /\b(standard|ground|economy|parcel|royal\s*mail)\b/i;
const FREIGHT_REGEX = /\b(freight|ltl|pallet|heavy|cargo|curbside)\b/i;
/**
* Pure WebAssembly function handler for Shopify Checkout Extensibility Delivery Customization.
* Evaluates cart line hazmat/freight properties and destination address rules,
* emitting hide, rename, and move operations while strictly guarding against
* the "all options hidden" checkout freeze trap.
*/
export function deliveryCustomizationRun(input: DeliveryCustomizationInput): DeliveryCustomizationResult {
const operations: Operation[] = [];
const errors: string[] = [];
const warnings: string[] = [];
const { deliveryOptions = [], deliveryAddress, lines = [] } = input;
if (deliveryOptions.length === 0) {
return {
operations: [],
errors: ["No delivery options provided by carrier services or shipping profiles."],
warnings: [],
retainedHandles: [],
};
}
// 1. Analyze cart contents
let totalWeightGrams = 0;
let hasFreightLines = false;
let hasHazmatLines = false;
for (const line of lines) {
totalWeightGrams += line.weightGrams * line.quantity;
if (line.requiresFreight || line.weightGrams >= 31500) {
hasFreightLines = true;
}
if (line.isHazardous) {
hasHazmatLines = true;
}
}
const isOverweight = totalWeightGrams > 70000; // 70 kg freight threshold
const requiresCommercialFreight = hasFreightLines || isOverweight;
// 2. Analyze destination address
const fullAddressString = [deliveryAddress?.address1, deliveryAddress?.address2].filter(Boolean).join(" ");
const isPoBox = PO_BOX_REGEX.test(fullAddressString);
// 3. Track candidates for hiding, renaming, and moving
const handlesToHide = new Set<string>();
const renames = new Map<string, string>();
let moveToTopHandle: string | null = null;
// Rule A: Hazardous goods cannot fly via Air / Express due to IATA / aviation safety regulations
if (hasHazmatLines) {
for (const option of deliveryOptions) {
if (AIR_EXPRESS_REGEX.test(option.title)) {
handlesToHide.add(option.handle);
}
}
}
// Rule B: Heavy Freight / Pallets cannot be delivered to PO Boxes
if (requiresCommercialFreight) {
if (isPoBox) {
errors.push("PO Box addresses cannot accept heavy freight or pallet shipments. Physical street address required.");
} else {
// For heavy freight on valid street addresses, hide standard parcel methods in favour of freight
for (const option of deliveryOptions) {
if (PARCEL_GROUND_REGEX.test(option.title) && !FREIGHT_REGEX.test(option.title)) {
handlesToHide.add(option.handle);
}
}
}
// Prioritize and annotate Freight options
const freightOption = deliveryOptions.find((opt) => FREIGHT_REGEX.test(opt.title));
if (freightOption && !handlesToHide.has(freightOption.handle)) {
moveToTopHandle = freightOption.handle;
if (!freightOption.title.includes("Liftgate")) {
renames.set(freightOption.handle, `${freightOption.title} (Curbside Pallet & Liftgate)`);
}
}
}
// 4. Defensive Guard: The "All Options Hidden" Checkout Freeze Trap
// If hiding candidates would eliminate every single shipping rate,
// checkout completely freezes with an opaque error. We must preserve at least one viable rate.
let remainingOptions = deliveryOptions.filter((opt) => !handlesToHide.has(opt.handle));
if (remainingOptions.length === 0) {
warnings.push("All delivery options matched suppression criteria. Safety fallback engaged to prevent checkout freeze.");
// Find the safest fallback option (preferably ground or freight, avoiding air hazmat violations if possible)
const fallbackOption =
deliveryOptions.find((opt) => !AIR_EXPRESS_REGEX.test(opt.title)) || deliveryOptions[0];
handlesToHide.delete(fallbackOption.handle);
renames.set(fallbackOption.handle, `${fallbackOption.title} (Manual Logistics Review Required)`);
remainingOptions = [fallbackOption];
}
// 5. Construct declarative operations payload
for (const handle of handlesToHide) {
operations.push({ hide: { deliveryOptionHandle: handle } });
}
for (const [handle, newTitle] of renames.entries()) {
if (!handlesToHide.has(handle)) {
operations.push({ rename: { deliveryOptionHandle: handle, title: newTitle } });
}
}
if (moveToTopHandle && !handlesToHide.has(moveToTopHandle)) {
operations.push({ move: { deliveryOptionHandle: moveToTopHandle, index: 0 } });
}
const retainedHandles = remainingOptions.map((opt) => opt.handle);
return {
operations,
errors,
warnings,
retainedHandles,
};
}The 'All Options Hidden' crash and defensive fallback design
The single most dangerous failure in Delivery Customization Functions is over-suppression. When you configure Shopify Shipping Profiles, you define rates per shipping zone. If a merchant sets up standard ground and express air profiles, but forgets to configure a dedicated freight rate for remote regional destinations, what happens when a customer orders a heavy pallet?
If your function blindly emits hide operations for both parcel methods because the order exceeds 70kg, the remaining options array becomes empty. In Shopify checkout, an empty delivery options array causes checkout to freeze with a generic error banner: 'There are no shipping methods available for your address.' The customer cannot select a delivery method and cannot complete payment.
As illustrated in our implementation of Shopify checkout validation functions and B2B payment customization rules, checkout extensions must fail gracefully.
Our defensive architecture inspects candidate deletions against the full set of provided delivery options. If the suppression logic would zero out all rates, it cancels the hide operation on the safest fallback, appends '(Manual Logistics Review Required)' via rename, and permits checkout to proceed so revenue is captured while alerting warehouse dispatch.
What native Shopify delivery customization cannot do
While Delivery Customization Functions provide high-speed, server-side rule execution, they operate within strict platform boundaries:
- Cannot calculate real-time shipping prices: Delivery Customization Functions cannot compute dynamic currency amounts or adjust shipping charges. They can only hide, rename, or move rates that already exist. Dynamic dimensional pricing requires an external Carrier Service API or Shopify Shipping Profiles.
- Cannot query external logistics APIs: Functions execute in a zero-network sandbox and cannot call DPD, FedEx, or Royal Mail APIs at checkout runtime to check live route disruptions or localized postal strikes.
- Cannot enforce post-purchase split shipments: If a customer orders one parcel item and one freight pallet, splitting the order into separate shipments with individual dispatch tracking numbers must be handled downstream in ERP or WMS software.
- Cannot inject interactive address pickers: Delivery Customization is a backend decision function, not a user interface. Interactive parcel locker maps or pickup point selectors must be built as Checkout UI Extensions rendered alongside delivery options.
When this needs an engineer
You do not need a custom engineering build if your catalogue consists of homogeneous consumer goods that fit comfortably into standard courier satchels. Standard Shopify Shipping Profiles and native weight-tier tables handle simple threshold shipping reliably.
You need an engineer when your products introduce physical transportation boundaries: when high-capacity lithium batteries, chemicals, or aerosols face strict civil aviation shipping bans; when heavy machinery, bumpers, or furniture require commercial pallet freight that cannot be delivered to PO Boxes; and when complex freight rules must execute inside Shopify Plus Checkout Extensibility without freezing conversion.
Explore our custom Shopify applications case studies to see how brands architect dedicated purchasing workflows, or inspect the Forged 4x4 Shopify Plus case study to see how custom checkout calculations and business rules were integrated into enterprise commerce. You can also explore our Shopify Plus development services.
Send us the store and the symptom. We will audit your shipping profile architecture, review your hazmat metadata, and build a deterministic delivery customization function that protects your logistics margins without stranding customers at checkout.
