How to add Free Shipping (or Shipping %) to existing Product Discount coupons using the new Discount API — without breaking thousands of live coupons?

Hi Shopify Devs & Partners,

We’re a public app that has been live already powers product-level percentage/amount discounts for thousands of stores using the classic Product Discount Functions + discountCodeAppCreate (appDiscountType: PRODUCT).

All coupon configuration is stored in a single discountNodes metafield on the shop (JSON array of rules). Merchants have created hundreds/thousands of active automatic and code-based product discounts through our app.

Now the business wants to roll out a highly requested feature:
Allow the same coupon to give both

  • a %/amount off products (cart lines) AND
  • free shipping or a shipping % discount
    …all in one code.

We know this is only possible with the new 2024.07+ Discount APIs that support multiple discountClasses: [PRODUCT, SHIPPING] (or even ORDER) on the same discount node, powered by the new combined Discount Function.

The big constraints:

  1. Existing coupons must keep working exactly as before if the merchant doesn’t turn on the shipping option.
  2. When the merchant enables “also give free shipping”, the same code should apply both discounts.
  3. Zero forced data migration — we cannot ask merchants to delete/recreate thousands of live coupons.
  4. Must work for both automatic and code-based discounts.
  5. Gradual rollout: merchants should be able to enable the shipping feature on a per-coupon basis.

Current questions we’re trying to solve (would love real-world input from anyone who has already done this):

  1. Can we safely run both old product-only discount nodes and new combined (PRODUCT + SHIPPING) nodes side-by-side on the same shop without causing duplicate product discounts?

  2. If we go hybrid (some coupons stay on old product-only function, some migrate to new combined function), how do we reliably prevent double-dipping on the product discount when both nodes match the cart?

  3. Is there a clean way to update an existing product-only discount node to a combined one (changing functionId + adding discountClasses) or does Shopify block that?

  4. For code-based discounts (discountCodeAppCreate), does the new API fully support discountClasses: [PRODUCT, SHIPPING] the same way automatic discounts do?

  5. Anyone successfully running a mixed environment (old product functions + new combined functions) in production? Any gotchas with discount ordering, combinesWith, or performance?

  6. Bonus: what’s the smoothest way to evolve our metafield JSON schema to support optional shipping rules while staying 100% backward compatible?

Example of current stored rule (product only):

{
  "resourceType": ["all"],
  "discountType": ["percent"],
  "couponRules": [{"minimumValue":"100","discountValue":"20"}]
}

We’d love something like:

{
  ...existing fields,
  "shippingDiscount": {
    "type": "FREE_SHIPPING",  // or "PERCENT" with value
    "minimumValue": "150"
  }
}

Any battle-tested patterns or war stories would be gold! Especially from apps that have already shipped combined product + shipping discounts without forcing merchants to start over.

Thanks in advance!

Our migration guide is here. Note that you can and should test your migrated Function on your dev store before releasing it to the stores that have installed your app, and also that after you migrate your Function you will no longer be able to revert by deploying it using the deprecated APIs. The migration is a one-way door.

You can migrate your function to the new API so that it supports both product and shipping discounts, using a new app version. Existing discount nodes will start using your migrated Function as soon as the new app version is released. So long as your existing product-only discount nodes continue to be configured with the single discount class (discountClasses: [PRODUCT]), they will continue to work as before and only provide product discounts. The cart.delivery-options.discounts.generate.run target will not be called for your function unless the discountClasses for the discount node includes SHIPPING.

As soon as an existing discount node is updated to include both PRODUCT and SHIPPING discount classes (discountClasses: [PRODUCT, SHIPPING]), then the cart.delivery-options.discounts.generate.run target will be called on your Function and the discount can provide both effects. In this way you can achieve a gradual rollout as merchants enable the shipping feature (add the SHIPPING discount class) on each discount.

The new API supports both code-based and automatic discounts.