Possible bug in Shopify Function Discount API

Topic summary

Core Issue:
Developers are encountering a bug in Shopify’s Product Discount Function API where cart.deliveryGroups contains data on the cart page but returns an empty array on the checkout page. This causes discounts based on delivery address (zip/postal codes) or cart attributes to fail at checkout.

Technical Details:

  • The function works correctly when viewing the cart, with deliveryGroups populated
  • Upon navigating to checkout, the array becomes empty, breaking discount logic
  • Similar issues reported with cart attributes becoming null during checkout
  • The function gets called multiple times (3x) during checkout transition, with the last two calls showing empty data

Current Status:

  • Multiple developers have reported this issue and submitted bug reports to Shopify support
  • Support acknowledged the problem and created a ticket
  • No resolution or updates provided yet despite reports dating back several months
  • A related community post from 3 months ago remains unanswered

Affected Use Cases:

  • Zip/postal code-based discounts
  • Country code-based product discounts
  • Cart attribute-dependent pricing
Summarized with AI on November 17. AI used: claude-sonnet-4-5-20250929.

Hello,

I have run into what I think is a Shopify Cart API bug in Shopify Functions (Product Discount). I am trying to write a function that discounts items depending on a matching zip code and product variant sku. I have noticed that my function works as expected when viewing the cart, however, once I continue to the checkout page my discounts are no longer applied to the cart.

I have noticed a few things here. When I view the logs for the function run while on the cart page, the cart.deliveryGroups has values in the array. Once I navigate to the checkout, the array is empty. This is where I think the bug lies.

My input.graphql code is as follows:


query Input {
cart {
cost {
subtotalAmount {
amount
}
totalAmount {
amount
}
}
deliveryGroups {
deliveryAddress {
zip
}
}
lines {
quantity
merchandise {
__typename
... on ProductVariant {
id
sku
}
}
cost {
subtotalAmount {
amount
}
totalAmount {
amount
}
amountPerQuantity {
amount
}
compareAtAmountPerQuantity {
amount
}
}
}
}
discountNode {
metafield(namespace: "postalCodes", key: "codes") {
value
}
}
}

My function code is as follows:


// @TS -check
import { DiscountApplicationStrategy } from "../generated/api";

/**
* @typedef {import("../generated/api").InputQuery} InputQuery
* @typedef {import("../generated/api").FunctionResult} FunctionResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/

/**
* @type {FunctionResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.Maximum,
discounts: [],
};

export default /**
* @Anonymous {InputQuery} input
* @returns {FunctionResult}
*/
(input) => {
/**
* @type {[{
* code: string
* skuDiscountPairs: {[key: string]: number}
* }]}
*/
const postalCodes = JSON.parse(input?.discountNode?.metafield?.value ?? "{}");

if (!postalCodes) {
return EMPTY_DISCOUNT;
}

if (!input.cart.deliveryGroups[0]) {
return EMPTY_DISCOUNT;
}

const cartPostalCode = input.cart.deliveryGroups[0].deliveryAddress?.zip;
let found;

for (const postalCode of postalCodes) {
if (postalCode.code === cartPostalCode) {
found = postalCode;
break;
}
}

if (!found) {
return EMPTY_DISCOUNT;
}

const discounts = input.cart.lines
.filter((line) => {
if (line.merchandise.__typename == "ProductVariant") {
const variant = /** @type {ProductVariant} */ (line.merchandise);
if (variant.sku && found.skuDiscountPairs[variant.sku]) {
return true;
} else {
return false;
}
}
})
.map((line) => {
const variant = /** @type {ProductVariant} */ (line.merchandise);
return {
targets: [
{
productVariant: {
id: variant.id,
},
},
],
value: {
percentage: {
// @TS -ignore
value: found.skuDiscountPairs[variant.sku].toString(),
},
},
};
});

console.log(discounts, "DISCOUNTS");

if (!discounts.length) {
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}

return {
discounts,
discountApplicationStrategy: DiscountApplicationStrategy.Maximum,
};
};

Here is the deliverGroups array and function output while on the cart page:

"deliveryGroups": [      {
        "deliveryAddress": {          "zip": "K4P 0E4"
        }
      }
    ],
{  "discounts": [    {
      "targets": [        {
          "productVariant": {            "id": "gid://shopify/ProductVariant/45022386159917"
          }
        }
      ],
      "value": {        "percentage": {          "value": "10"
        }
      }
    }
  ],
  "discountApplicationStrategy": "MAXIMUM"
}

Here are the deliveryGroups array and output on the checkout page:

"deliveryGroups": [],
{  "discountApplicationStrategy": "MAXIMUM",  "discounts": []}

Please let me know if you have any questions about this bug report.

2 Likes

Hi, I was wondering if you found a solution, because I have a similar issue.

I am calculating my discount based on cart attributes. These attributes are passed just fine to function’s Input on cart page. Once I proceed to checkout page, the function is called 3 times, and in the last two calls, attributes on cart are all null, which removes the discount.

I am using the typescript template for product discount

Hey, I have been in contact with support and they have made a ticket to fix
this bug. The delivery groups should definitely be a part of the checkout
page

2 Likes

@DevAOC just curious what your postalCodes metafield definition looks like.

Its an object with objects in it.

Did you get a reply about this issue? I’m having a similar thing where I’m trying to create a product discount based on country code but deliveryGroups is always empty.

I unfortunately have not gotten any information after submitting a bug
report. They said they would look into it but no news yet!

1 Like

Thanks for the reply! I’ve submitted a similar post with my issue too. Nearly three months without a reply doesn’t look promising though. :downcast_face_with_sweat: