Disable discount codes for product with specific tag using by Product Discount Function API

Topic summary

A developer needs to migrate a Shopify Script that disables discount codes for products with specific tags to the Product Discount Function API, as Shopify Scripts are no longer available after moving to checkout extensibility.

Original Implementation:

  • Used Ruby-based Shopify Scripts
  • Disabled discount codes when specific products (identified by tags, type, vendor, product/variant ID, or subscription status) were in the cart
  • Supported exact or partial discount code matching
  • Displayed custom rejection messages

Current Challenge:
The developer seeks guidance on replicating this functionality using Shopify Functions with JavaScript.

Suggested Solution:
One respondent recommended exploring the Discount Allocator Functions API, which appears to offer the needed capabilities. However, this API is currently in developer preview and cannot yet be used on live stores.

Status: The discussion remains open with no confirmed implementation path for production environments.

Summarized with AI on November 9. AI used: claude-sonnet-4-5-20250929.

I want to be disable discount codes for product with specific tag using by Product Discount Function API(Shopify functions with Javascript)

The following is implemented using Shopify scripts.
Since I moved to checkout extensibility, Shopify scripts are no longer available, so I would like to implement it with Shopify functions.

Please tell me how do I if it is possible.

# ================================ Customizable Settings ================================
# ================================================================
# Disable Discount Code(s) For Products
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
#
# - 'discount_code_match_type' determines whether the below 

# strings should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'discount_codes' is a list of strings to identify discount
# codes
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'rejection_message' is the message to show when a discount
# code is rejected
# ================================================================

REJECT_DISCOUNT_CODE_FOR_PRODUCTS = [
  {
    discount_code_match_type: :exact,
    discount_codes: ["NOCOUPON"],
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["NOCOUPON"],
    rejection_message: "Error message"
  }
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
  def initialize(match_type, discount_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    _codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
  end

  def match?(discount_code)
    _codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    _type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    _type = selector_type
    @selectors = selectors
  end

  def match?(line_item)
    if self.respond_to?(@selector_type)
      self.send(@selector_type, line_item)
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  end

  def tag(line_item)
    product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@selectors & product_tags).send(@comparator)
  end

  def type(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
  end

  def vendor(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
  end

  def product_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
  end

  def variant_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.id)
  end

  def subscription(line_item)
    !line_item.selling_plan_id.nil?
  end

  def all(line_item)
    true
  end
end

# ================================================================
# DisableDiscountCodesForProductsCampaign
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
# ================================================================
class DisableDiscountCodesForProductsCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return if cart.discount_code.nil?

        @campaigns.each do |campaign|
        product_selector = ProductSelector.new(
            campaign[:product_selector_match_type],
            campaign[:product_selector_type],
            campaign[:product_selectors],
        )

        next unless cart.line_items.any? { |line_item| product_selector.match?(line_item) }
            cart.discount_code.reject(message: campaign[:rejection_message])
    end
  end
end

CAMPAIGNS = [
  DisableDiscountCodesForProductsCampaign.new(REJECT_DISCOUNT_CODE_FOR_PRODUCTS),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart)
end

Output.cart = Input.cart

You can try playing around with the Discount Allocator Functions API. It seems like it has what you’re looking for. However, it’s in developer preview right now, so you can’t use it on live stores yet.

https://shopify.dev/docs/api/functions/reference/discounts-allocator