Discount for customers with a tag

A Shopify discount Function that takes a percentage off the order for signed-in customers with a tag such as VIP or wholesale. Tested JavaScript.

Function API
Discount
Target
cart.lines.discounts.generate.run
Discount class
ORDER
Plan
Any plan in a public app; Shopify Plus in a custom app
App scope
write_discounts
API version
2026-10
Last checked

In short

This discount Function takes a percentage off the order subtotal, 10% by default, for signed-in customers who have one of your tags, such as VIP. Tags, percentage and message live in the discount’s JSON metafield, and the tags reach the query as variables. It uses the cart.lines.discounts.generate.run target of the Discount Function API, version 2026-10, with the order class.

How to use it

  1. Create an app with Shopify CLI 4.9.0 or later and add the extension: shopify app generate extension --template discount, with JavaScript.
  2. Replace the generated shopify.extension.toml and src/ files with the files below.
  3. Run the fixtures with shopify app function run --input tests/tagged-customer.input.json.
  4. Deploy with shopify app deploy and create an automatic discount with discountAutomaticAppCreate, discountClasses: [ORDER].
  5. Set the discount’s $app:function-configuration metafield, for example {"customerTags": ["VIP"], "percentage": 15, "message": "VIP 15% off"}.

Code

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

[[extensions]]
name = "Discount by customer tag"
handle = "discount-by-customer-tag"
type = "function"

  [[extensions.targeting]]
  target = "cart.lines.discounts.generate.run"
  input_query = "src/cart_lines_discounts_generate_run.graphql"
  input_variables = { namespace = "$app", key = "function-configuration" }
  export = "cart-lines-discounts-generate-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
src/cart_lines_discounts_generate_run.graphql
query CartLinesDiscountsGenerateRunInput($customerTags: [String!]) {
  cart {
    buyerIdentity {
      customer {
        hasAnyTag(tags: $customerTags)
      }
    }
  }
  discount {
    discountClasses
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
src/cart_lines_discounts_generate_run.js
// @ts-check

/**
 * @typedef {import("../generated/api").CartLinesDiscountsGenerateRunInput} CartLinesDiscountsGenerateRunInput
 * @typedef {import("../generated/api").CartLinesDiscountsGenerateRunResult} CartLinesDiscountsGenerateRunResult
 */

/**
 * Settings stored as JSON in the discount's metafield. The same metafield
 * feeds the query's $customerTags variable (see input_variables in
 * shopify.extension.toml), so Shopify checks the tags.
 */
const DEFAULT_CONFIG = {
  percentage: 10,
  message: "Member discount",
};

/** @type {CartLinesDiscountsGenerateRunResult} */
const NO_DISCOUNT = { operations: [] };

/**
 * @param {CartLinesDiscountsGenerateRunInput} input
 * @returns {CartLinesDiscountsGenerateRunResult}
 */
export function cartLinesDiscountsGenerateRun(input) {
  // A Function may only return operations for the classes the discount was created with.
  if (!input.discount.discountClasses.includes("ORDER")) return NO_DISCOUNT;
  // Guests have no customer record; tags only apply after the customer signs in.
  if (!input.cart.buyerIdentity?.customer?.hasAnyTag) return NO_DISCOUNT;

  const config = { ...DEFAULT_CONFIG, ...input.discount.metafield?.jsonValue };
  return {
    operations: [
      {
        orderDiscountsAdd: {
          candidates: [
            {
              message: config.message,
              targets: [{ orderSubtotal: { excludedCartLineIds: [] } }],
              value: { percentage: { value: String(config.percentage) } },
            },
          ],
          selectionStrategy: "FIRST",
        },
      },
    ],
  };
}
Test input and expected output
tests/guest-checkout.input.json
{
  "cart": { "buyerIdentity": { "customer": null } },
  "discount": {
    "discountClasses": ["ORDER"],
    "metafield": { "jsonValue": { "customerTags": ["VIP", "wholesale"], "percentage": 15, "message": "VIP 15% off" } }
  }
}
tests/guest-checkout.output.json
{ "operations": [] }
tests/tagged-customer.input.json
{
  "cart": { "buyerIdentity": { "customer": { "hasAnyTag": true } } },
  "discount": {
    "discountClasses": ["ORDER"],
    "metafield": { "jsonValue": { "customerTags": ["VIP", "wholesale"], "percentage": 15, "message": "VIP 15% off" } }
  }
}
tests/tagged-customer.output.json
{
  "operations": [
    {
      "orderDiscountsAdd": {
        "candidates": [
          {
            "message": "VIP 15% off",
            "targets": [{ "orderSubtotal": { "excludedCartLineIds": [] } }],
            "value": { "percentage": { "value": "15" } }
          }
        ],
        "selectionStrategy": "FIRST"
      }
    }
  ]
}
tests/untagged-customer.input.json
{
  "cart": { "buyerIdentity": { "customer": { "hasAnyTag": false } } },
  "discount": {
    "discountClasses": ["ORDER"],
    "metafield": { "jsonValue": { "customerTags": ["VIP", "wholesale"], "percentage": 15, "message": "VIP 15% off" } }
  }
}
tests/untagged-customer.output.json
{ "operations": [] }

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

Limits and edge cases

  • The customer must be signed in; guests have no tags.
  • Tags are checked by Shopify with hasAnyTag and the $customerTags variable, so changing them doesn’t need a deploy.
  • Whether it combines with other discounts follows the discount’s combinesWith settings.

Questions

How do I give a discount to tagged customers on Shopify?
Create a discount backed by a Function that checks the customer’s tags with hasAnyTag and returns an order discount, as this example does.
What if the customer signs in during checkout?
The Function runs on the current cart. Once the customer is signed in, the tag check passes and the discount applies.
Can I exclude sale items from the VIP discount?
Yes. Add the cart lines to the query and put their IDs in excludedCartLineIds on the orderSubtotal target.
  • 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.