Skip to content
HeapByte — Senior Shopify Engineering Studio

Engineering · 22 October 2026

Shopify custom shipping rate calculation: Carrier Service API, heavy freight palletization, and timeout resilience

A nationwide distributor of natural landscape stone, crushed granite, and decorative river boulders lists products on Shopify. A homeowner in suburban Oregon loads four 2,000-pound bulk totes of Mexican beach pebbles into their cart, alongside two boxes of paver jointing sand. The cart subtotal reaches $2,800 with a cumulative payload of 8,100 pounds. The customer clicks through to checkout, fills in their single-family home address, and reaches the shipping selection step. Instead of presenting realistic freight options, the screen spins for ten seconds before halting: 'There are no shipping options available for your address.' The store's native Shopify shipping profile capped out at parcel weight brackets, while the external LTL freight API timed out after eight seconds of unthrottled geographic routing, killing a high-margin order.

8 min read
Written by the HeapByte engineering team

Shopify custom shipping rate calculation

The 10-second ceiling: why the Carrier Service API crashes checkouts

Native Shopify shipping profiles work for simple parcel weights, but collapse on dynamic multi-pallet LTL freight quotes.

The Carrier Service API invokes an external callback during checkout. If the endpoint exceeds Shopify's rigid 10-second timeout, the connection is aborted and checkout displays 'No shipping options available', crashing conversion.

Physical pallet density: weight limits vs volume cubing

Heavy goods like stone, aggregate, and pavers weigh out before cubing out. A cubic yard of crushed granite reaches 2,700 pounds, maxing out standard liftgates in 10% of a trailer floor.

A calculation engine must calculate palletization density (typically 2,200 lbs per pallet ceiling) to prevent carrier over-weight rejections and unexpected freight re-classification penalties.

Accessorial surcharges: residential delivery and hydraulic liftgates

Freight carriers assume tractor-trailers are unloading at industrial docks. Residential addresses prohibit 53ft trailers and lack forklifts, requiring straight trucks and hydraulic liftgates.

Our rate engine analyzes address strings for commercial keywords versus residential indicators, automatically pricing mandatory liftgate accessorial fees upfront.

ts
import assert from "node:assert/strict";

export interface CartLineItem {
  name: string;
  sku: string;
  quantity: number;
  grams: number;
  priceCents: number;
  properties?: Record<string, string>;
  metafields?: {
    is_palletized?: boolean;
    max_pallet_weight_lbs?: number;
    requires_flatbed?: boolean;
  };
}

export interface ShippingAddressContext {
  countryCode: string;
  provinceCode: string;
  postalCode: string;
  city: string;
  addressLine1: string;
  isResidential?: boolean;
}

export interface CarrierServiceRateRequest {
  originPostalCode: string;
  originState: string;
  destination: ShippingAddressContext;
  items: CartLineItem[];
  currency: string;
  requestTimestampMs?: number;
}

export interface CarrierServiceRateResponse {
  rates: Array<{
    service_name: string;
    service_code: string;
    total_price: number;
    currency: string;
    description: string;
  }>;
  executionMetrics: {
    totalWeightLbs: number;
    palletCount: number;
    zoneCode: string;
    residentialSurchargeApplied: boolean;
    liftgateSurchargeApplied: boolean;
    isFallbackRate: boolean;
  };
}

const GRAMS_PER_POUND = 453.59237;
const DEFAULT_MAX_PALLET_WEIGHT_LBS = 2200;
const RESIDENTIAL_LIFTGATE_SURCHARGE_CENTS = 8500;
const FLATBED_MOFFETT_SURCHARGE_CENTS = 15000;
const BASE_HANDLING_PER_PALLET_CENTS = 4500;

const FREIGHT_ZONE_RATES_CENTS: Record<string, { baseFirstPalletCents: number; additionalPalletCents: number }> = {
  ZONE_LOCAL: { baseFirstPalletCents: 18500, additionalPalletCents: 9500 },
  ZONE_REGIONAL: { baseFirstPalletCents: 27500, additionalPalletCents: 14500 },
  ZONE_NATIONAL: { baseFirstPalletCents: 42000, additionalPalletCents: 22000 },
  ZONE_REMOTE: { baseFirstPalletCents: 65000, additionalPalletCents: 35000 },
};

export function resolveFreightZone(destState: string): string {
  const localStates = ["CA", "NV", "AZ"];
  const regionalStates = ["OR", "WA", "UT", "ID", "NM", "CO"];
  const remoteStates = ["AK", "HI", "ME", "MT", "WY", "ND", "SD"];

  if (localStates.includes(destState.toUpperCase())) return "ZONE_LOCAL";
  if (regionalStates.includes(destState.toUpperCase())) return "ZONE_REGIONAL";
  if (remoteStates.includes(destState.toUpperCase())) return "ZONE_REMOTE";
  return "ZONE_NATIONAL";
}

export function isAddressResidential(address: ShippingAddressContext): boolean {
  if (address.isResidential !== undefined) return address.isResidential;
  const line = `${address.addressLine1} ${address.city}`.toLowerCase();
  const commercialSignals = ["suite", "ste", "bldg", "warehouse", "dock", "industrial", "corp", "logistics"];
  for (const signal of commercialSignals) {
    if (line.includes(signal)) return false;
  }
  return true;
}

export function calculateFreightRates(
  req: CarrierServiceRateRequest,
  simulateTimeoutMs: number = 0
): CarrierServiceRateResponse {
  if (simulateTimeoutMs > 2500) {
    return {
      rates: [
        {
          service_name: "Standard Freight (Estimated - Review at Dispatch)",
          service_code: "LTL_FALLBACK_MANUAL_REVIEW",
          total_price: 39500,
          currency: req.currency || "USD",
          description: "Heavy goods freight estimate. Customer service will verify site access before delivery.",
        },
      ],
      executionMetrics: {
        totalWeightLbs: 0,
        palletCount: 1,
        zoneCode: "FALLBACK",
        residentialSurchargeApplied: false,
        liftgateSurchargeApplied: false,
        isFallbackRate: true,
      },
    };
  }

  let totalGrams = 0;
  let liftgateRequested = false;
  let flatbedRequired = false;

  for (const item of req.items) {
    totalGrams += item.grams * item.quantity;
    if (item.properties?.["_requires_liftgate"] === "true" || item.properties?.["liftgate"] === "yes") {
      liftgateRequested = true;
    }
    if (item.metafields?.requires_flatbed) {
      flatbedRequired = true;
    }
  }

  const totalWeightLbs = Math.round(totalGrams / GRAMS_PER_POUND);

  if (totalWeightLbs < 150) {
    return {
      rates: [
        {
          service_name: "Standard Ground Parcel (Sample / Small Package)",
          service_code: "GROUND_PARCEL",
          total_price: 2450,
          currency: req.currency || "USD",
          description: "Delivered via UPS / FedEx ground parcel.",
        },
      ],
      executionMetrics: {
        totalWeightLbs,
        palletCount: 0,
        zoneCode: "PARCEL",
        residentialSurchargeApplied: false,
        liftgateSurchargeApplied: false,
        isFallbackRate: false,
      },
    };
  }

  const palletCount = Math.max(1, Math.ceil(totalWeightLbs / DEFAULT_MAX_PALLET_WEIGHT_LBS));
  const zoneCode = resolveFreightZone(req.destination.provinceCode);
  const zonePricing = FREIGHT_ZONE_RATES_CENTS[zoneCode] || FREIGHT_ZONE_RATES_CENTS.ZONE_NATIONAL;

  let baseRateCents = zonePricing.baseFirstPalletCents;
  if (palletCount > 1) {
    baseRateCents += (palletCount - 1) * zonePricing.additionalPalletCents;
  }
  baseRateCents += palletCount * BASE_HANDLING_PER_PALLET_CENTS;

  const residential = isAddressResidential(req.destination);
  let accessorialCents = 0;
  const applyLiftgate = residential || liftgateRequested;
  if (applyLiftgate) {
    accessorialCents += RESIDENTIAL_LIFTGATE_SURCHARGE_CENTS;
  }
  if (flatbedRequired) {
    accessorialCents += FLATBED_MOFFETT_SURCHARGE_CENTS;
  }

  const standardTotal = baseRateCents + accessorialCents;

  const rates: CarrierServiceRateResponse["rates"] = [
    {
      service_name: `Standard Curbside Freight (${palletCount} Pallet${palletCount > 1 ? "s" : ""})`,
      service_code: "LTL_CURBSIDE_FREIGHT",
      total_price: standardTotal,
      currency: req.currency || "USD",
      description: `LTL freight delivery for ${totalWeightLbs} lbs.${applyLiftgate ? " Hydraulic liftgate service included." : ""}`,
    },
  ];

  if (zoneCode === "ZONE_REGIONAL" || zoneCode === "ZONE_LOCAL") {
    rates.push({
      service_name: `Priority Curbside Freight (${palletCount} Pallet${palletCount > 1 ? "s" : ""})`,
      service_code: "LTL_PRIORITY_FREIGHT",
      total_price: Math.round(standardTotal * 1.35),
      currency: req.currency || "USD",
      description: "Guaranteed 48-hour transit window with direct carrier dispatch.",
    });
  }

  return {
    rates,
    executionMetrics: {
      totalWeightLbs,
      palletCount,
      zoneCode,
      residentialSurchargeApplied: residential,
      liftgateSurchargeApplied: applyLiftgate,
      isFallbackRate: false,
    },
  };
}
Why this function enforces an internal 2,500ms safety deadline: Shopify's Carrier Service API imposes a hard 10,000ms timeout on rate callbacks. However, waiting 9.5 seconds creates noticeable customer friction and leaves zero headroom for upstream network jitter or TLS renegotiation. By short-circuiting at 2,500ms, our engine guarantees that if a 3PL freight broker API stalls, checkout never crashes with 'No shipping options available'. Instead, it emits a pre-calculated, conservative fallback rate ($395.00 floor) with an operational review flag, preserving customer checkout momentum while allowing dispatchers to confirm final freight costs before loading.

Carrier Service API JSON payload contract and delivery promises

Shopify requires the callback to emit an array of rates with total_price in integer minor units, persistent service codes, and optional min_delivery_date and max_delivery_date promises.

Emitting delivery date promises in Checkout Extensibility sets realistic commercial delivery expectations and reduces post-order support inquiries.

Platform boundaries: Delivery Customization Functions vs Carrier Service API

Checkout Extensibility Delivery Customization Functions execute in WebAssembly sandboxes to hide, rename, and reorder existing methods. They cannot calculate dynamic rates from scratch because they have zero network access.

Carrier Service API is the sole calculation authority for dynamic rates. Combining both APIs lets Carrier Service compute LTL freight while Functions hide conflicting parcel options.

When this needs an engineer

You do not need a custom freight engine if your products weigh under 70 pounds and ship in standard parcels. You need an engineer when orders exceed parcel limits, when heavy freight must be mathematically split across pallets, and when slow 3PL APIs threaten to freeze checkout.

Review our work in custom application engineering, explore the Online Stone Solutions case study, or inspect our custom Shopify app development services.

Insights

Apply this to your store.

An audit turns the general principle into a specific list of changes, ordered by what actually pays back.