How to create Shopify Functions for Custom Manual Discounts

I am trying to create Shopify Functions to create custom discounts. I know I can use other apps such as AIOD, but the manual discounts created in them dont work on default checkout page of Shopify and I need that functionality because I am creating Android app created in React Native consuming direct GraphQl APIs of Shopify and the discounts created in AIOD dont work in GraphQl functions such as CartCreate.

I created Shopify Functions using Shopify cli and this is the code of extensions created:

  1. Name - getmobi-discount

    Code of cart_lines_discounts_generate_run.js:

const COLLECTION_IDS = [
    "gid://shopify/Collection/11",
    "gid://shopify/Collection/22",
    "gid://shopify/Collection/33",
    "gid://shopify/Collection/44"
];

const RULES = {
  "gid://shopify/Collection/11": 1000,
  "gid://shopify/Collection/22": 500,
  "gid://shopify/Collection/33": 750,
  "gid://shopify/Collection/44": 1000
};

export function cartLinesDiscountsGenerateRun(input) {
  const discounts = [];

  for (const line of input.cart.lines) {
    const matches = line.merchandise.product.inCollections.map(c => c.isMember);

    for (let i = 0; i < COLLECTION_IDS.length; i++) {
      if (!matches[i]) continue;

      const colId = COLLECTION_IDS[i];
      const amount = RULES[colId];

      if (amount) {
        discounts.push({
          targets: [{ cartLine: { id: line.id } }],
          value: {
            fixedAmount: {
              amount: amount.toString()
            }
          }
        });
        break;
      }
    }
  }

  return {
    discounts,
    discountApplicationStrategy: "FIRST"
  };
}

Code of cart_lines_discounts_generate_run.graphql

query CartLinesDiscountsGenerateRunInput {
  cart {
    lines {
      id
      quantity
      cost {
        amountPerQuantity {
          amount
        }
      }
      merchandise {
        ... on ProductVariant {
          id
          product {
            inCollections(
              ids: [
                "gid://shopify/Collection/11"
                "gid://shopify/Collection/22"
                "gid://shopify/Collection/33"
        "gid://shopify/Collection/44"
              ]
            ) {
              isMember
            }
          }
        }
      }
    }
  }
}

Code of index.js

export * from './cart_lines_discounts_generate_run';

Code of shopify.extension.toml

api_version = "2026-04"

[[extensions]]
name = "getmobi-discount"
handle = "getmobi-discount"
type = "function"
uid = "GUID"
description = "GetMobi discount"

  [[extensions.targeting]]
  target = "cart.lines.discounts.generate.run"
  input_query = "src/cart_lines_discounts_generate_run.graphql"
  export = "cart-lines-discounts-generate-run"
  
  [extensions.capabilities]
  discount_classes = ["PRODUCT"]

When I run command “shopify app deploy”, it deploys the app successfully and when I install the app in my Shopify Store and go to Discounts screen to create a new discount, it shows there as well:

Hi @Xamaa_Xamaa You’re on the right track. A discount Function targeting cart.lines.discounts.generate.run with discount_classes = ["PRODUCT"] is exactly the way to build custom discounts that apply at the default checkout and also show up through the Storefront cart, which is why this works where the third-party app’s manual discounts don’t.

The likely reason nothing applies is the return shape. Your function returns { discounts, discountApplicationStrategy: "FIRST" }, which is the older Discount Function shape. The cart.lines.discounts.generate.run target expects an operations array of productDiscountsAdd operations, each holding candidates and a selectionStrategy. With the new target, the old shape produces no operations, so the discount shows in the Discounts list but never lands on the cart. The output your function needs to return looks like this:

{
  "operations": [
    {
      "productDiscountsAdd": {
        "candidates": [
          {
            "message": "Collection discount",
            "targets": [{ "cartLine": { "id": "gid://shopify/CartLine/..." } }],
            "value": { "fixedAmount": { "amount": "10.00" } }
          }
        ],
        "selectionStrategy": "FIRST"
      }
    }
  ]
}

So your per-collection loop should build one candidate per matching line (set targets to that cartLine.id, value to the fixed amount as a decimal string), then return them all under a single productDiscountsAdd. The Build a Discount Function guide has the full input/output schema plus runnable examples in JS and Rust if you want a language-specific template.

A couple of other things to check while you’re in there. Gate the logic on input.discount.discountClasses (add discount { discountClasses } to your input query and return an empty operations array when PRODUCT isn’t present), and make sure the discount instance you created is set to Active, not scheduled or expired. About discounts covers the discount classes.

For the React Native side, the discount applies on cart evaluation automatically, there’s no mutation to “apply” it (only discount codes use cartDiscountCodesUpdate). To see it, query the allocations on the cart rather than just the totals:

cart {
  cost { subtotalAmount { amount } totalAmount { amount } }
  lines(first: 50) {
    nodes {
      id
      cost { totalAmount { amount currencyCode } }
      discountAllocations { discountedAmount { amount currencyCode } }
    }
  }
}

If it still doesn’t apply after fixing the return shape, grab the function run logs (shopify app dev, or replay with app function replay) and post the output along with the exact symptom you’re seeing, and I’ll take a closer look.