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
- Create an app with the Shopify CLI, then add the extension: shopify app generate extension --template delivery_customization, with JavaScript.
- Replace the generated shopify.extension.toml and the two files in src/ with the files below.
- Run the fixtures with shopify app function run --input tests/heavy-cart-in-pounds.input.json.
- Deploy with shopify app deploy and create the customization with deliveryCustomizationCreate.
- Optionally set the $app:function-configuration metafield, for example {"maxWeightKg": 20, "optionTitles": ["Express", "Overnight"]}.
Code
shopify.extension.tomlapi_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.graphqlquery 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.
Shopify documentation
- 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.
