Hide express shipping for heavy carts

A Shopify delivery customization Function that hides express rates when a shipment weighs more than a limit, 30 kg by default. Tested JavaScript.

Function API
Delivery Customization
Target
cart.delivery-options.transform.run
Plan
Any plan in a public app; Shopify Plus in a custom app
App scope
write_delivery_customizations
API version
2026-10
Last checked

In short

This delivery customization Function adds up the product weight of each shipment and hides express options when it’s above a limit, 30 kg by default. The limit and the option names come from a JSON metafield. It uses the cart.delivery-options.transform.run target of the Delivery Customization API, version 2026-10, and works on any plan in a public app.

How to use it

  1. Create an app with the Shopify CLI, then add the extension: shopify app generate extension --template delivery_customization, with JavaScript.
  2. Replace the generated shopify.extension.toml and the two files in src/ with the files below.
  3. Run the fixtures with shopify app function run --input tests/heavy-cart-in-pounds.input.json.
  4. Deploy with shopify app deploy and create the customization with deliveryCustomizationCreate.
  5. Optionally set the $app:function-configuration metafield, for example {"maxWeightKg": 20, "optionTitles": ["Express", "Overnight"]}.

Code

shopify.extension.toml
api_version = "2026-10"

[[extensions]]
name = "Hide express shipping for heavy carts"
handle = "hide-express-heavy-carts"
type = "function"

  [[extensions.targeting]]
  target = "cart.delivery-options.transform.run"
  input_query = "src/cart_delivery_options_transform_run.graphql"
  export = "cart-delivery-options-transform-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
src/cart_delivery_options_transform_run.graphql
query CartDeliveryOptionsTransformRunInput {
  cart {
    deliveryGroups {
      cartLines {
        quantity
        merchandise {
          __typename
          ... on ProductVariant {
            weight
            weightUnit
          }
        }
      }
      deliveryOptions {
        handle
        title
      }
    }
  }
  deliveryCustomization {
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
src/cart_delivery_options_transform_run.js
// @ts-check

/**
 * @typedef {import("../generated/api").CartDeliveryOptionsTransformRunInput} CartDeliveryOptionsTransformRunInput
 * @typedef {import("../generated/api").CartDeliveryOptionsTransformRunResult} CartDeliveryOptionsTransformRunResult
 */

/** Settings stored as JSON in the customization's metafield. */
const DEFAULT_CONFIG = {
  // Total product weight per shipment, in kilograms, above which the options are hidden.
  maxWeightKg: 30,
  // Matched case-insensitively anywhere in the delivery option title.
  optionTitles: ["Express", "Ekspres"],
};

/** Kilograms per unit of Shopify's WeightUnit values. */
const KG_PER_UNIT = { GRAMS: 0.001, KILOGRAMS: 1, OUNCES: 0.028349523125, POUNDS: 0.45359237 };

/**
 * Product weight of one delivery group. Variants without a weight count as 0,
 * so missing data never hides an option by itself.
 * @param {CartDeliveryOptionsTransformRunInput["cart"]["deliveryGroups"][number]} group
 */
function groupWeightKg(group) {
  return group.cartLines.reduce((sum, line) => {
    if (line.merchandise.__typename !== "ProductVariant") return sum;
    const { weight, weightUnit } = line.merchandise;
    return sum + (weight ?? 0) * (KG_PER_UNIT[weightUnit] ?? 0) * line.quantity;
  }, 0);
}

/**
 * @param {CartDeliveryOptionsTransformRunInput} input
 * @returns {CartDeliveryOptionsTransformRunResult}
 */
export function cartDeliveryOptionsTransformRun(input) {
  const config = { ...DEFAULT_CONFIG, ...input.deliveryCustomization?.metafield?.jsonValue };
  const titles = config.optionTitles.map((title) => title.toLowerCase());

  // Each delivery group is a separate shipment, so weigh them one by one.
  const operations = input.cart.deliveryGroups
    .filter((group) => groupWeightKg(group) > config.maxWeightKg)
    .flatMap((group) => group.deliveryOptions)
    .filter((option) => titles.some((title) => (option.title ?? "").toLowerCase().includes(title)))
    .map((option) => ({ deliveryOptionHide: { deliveryOptionHandle: option.handle } }));

  return { operations };
}
Test input and expected output
tests/configured-limit.input.json
{
  "cart": {
    "deliveryGroups": [
      {
        "cartLines": [
          { "quantity": 12, "merchandise": { "__typename": "ProductVariant", "weight": 900, "weightUnit": "GRAMS" } }
        ],
        "deliveryOptions": [
          { "handle": "e5f6-standard", "title": "Standart Kargo" },
          { "handle": "e5f6-same-day", "title": "Aynı Gün Teslimat" }
        ]
      }
    ]
  },
  "deliveryCustomization": {
    "metafield": { "jsonValue": { "maxWeightKg": 10, "optionTitles": ["Aynı Gün"] } }
  }
}
tests/configured-limit.output.json
{ "operations": [ { "deliveryOptionHide": { "deliveryOptionHandle": "e5f6-same-day" } } ] }
tests/heavy-cart-in-pounds.input.json
{
  "cart": {
    "deliveryGroups": [
      {
        "cartLines": [
          { "quantity": 3, "merchandise": { "__typename": "ProductVariant", "weight": 25, "weightUnit": "POUNDS" } },
          { "quantity": 1, "merchandise": { "__typename": "ProductVariant", "weight": null, "weightUnit": "KILOGRAMS" } }
        ],
        "deliveryOptions": [
          { "handle": "a1b2c3d4-standard", "title": "Standard Shipping" },
          { "handle": "a1b2c3d4-express", "title": "Express Shipping" }
        ]
      }
    ]
  },
  "deliveryCustomization": { "metafield": null }
}
tests/heavy-cart-in-pounds.output.json
{ "operations": [ { "deliveryOptionHide": { "deliveryOptionHandle": "a1b2c3d4-express" } } ] }
tests/light-cart.input.json
{
  "cart": {
    "deliveryGroups": [
      {
        "cartLines": [
          { "quantity": 2, "merchandise": { "__typename": "ProductVariant", "weight": 4.5, "weightUnit": "KILOGRAMS" } }
        ],
        "deliveryOptions": [
          { "handle": "a1b2c3d4-standard", "title": "Standard Shipping" },
          { "handle": "a1b2c3d4-express", "title": "Express Shipping" }
        ]
      }
    ]
  },
  "deliveryCustomization": { "metafield": null }
}
tests/light-cart.output.json
{ "operations": [] }

Written by Lintel. MIT license: free to use in your own store and in client work.

Limits and edge cases

  • The weight is the variant weight from the admin times the quantity; packaging isn’t included. Variants without a weight count as zero.
  • Weights in grams, ounces and pounds are converted to kilograms before the comparison.
  • Each delivery group is weighed on its own, so in a split cart only the heavy shipment loses express.
  • If your carrier already refuses heavy parcels, check its rates first; the Function can only hide options that checkout offers.

Questions

Can Shopify hide a shipping rate based on weight?
Yes, with a delivery customization Function. It reads each variant’s weight and quantity, adds them up per shipment and hides the options you name when the total is over your limit.
Does the Function see the parcel weight with packaging?
No. It sees each variant’s weight and quantity. Add a margin to the limit if your packaging is heavy.
Can I hide express for oversized items instead of weight?
Yes, by product tag. See the example that hides shipping rates by product tag; both can run as separate customizations.
  • Written for Function API version 2026-10 and checked against Shopify’s reference on the date shown. Shopify releases a new version every quarter.
  • Custom apps that contain Functions only run on Shopify Plus stores; a public app with the same Function runs on any plan unless the page says otherwise.
  • Every example ships with test fixtures, and we run them before publishing. Run them again after you change the code: shopify app function run.
  • MIT license. Use the code in your own store or in client work.

Need this built, tested and maintained for your store? See Shopify Functions development.

Have something to build? Let’s talk it through.

Taking a limited number of projects at a time. Every brief is read by an engineer, not a salesperson.

Gencer KarakayaShopify Engineer

An engineer reads your brief, and we reply by email.