Published on

Mistake - Building a Shopify Function for the Wrong Plan

Authors
  • avatar
    Name
    Ibrahim Motani

The Task: Hide a Payment Method Based on Cart Total

While working with a heritage luxury brand in Australia, I was given what seemed like a straightforward requirement: hide Afterpay from the checkout when the cart total exceeds $2,000. This is a common use case - Buy Now Pay Later services typically have spending limits, and showing an unavailable payment method creates a confusing experience for customers.

For this kind of checkout customization, Shopify Functions is the go-to solution, it lets you run custom logic at various points in the checkout process. Specifically, for payment method customization, you can use the Payment Customization Function API which targets cart.payment-methods.transform.run.

Building the Payment Customization Function

Since this was my first time developing a Shopify app with custom functions, I followed Shopify's official tutorial fairly closely. Let me walk you through the steps.

Prerequisites

Before jumping into development, you'll need:

npm install -g @shopify/cli@latest

Step 1: Create a New Shopify App

First, scaffold a new Shopify app. If you're starting fresh, run:

shopify app init

Follow the prompts to set up your app. Shopify CLI handles the boilerplate for you.

Step 2: Generate the Payment Customization Extension

Navigate to your app directory and generate a function extension:

shopify app generate extension

When prompted, select Shopify Functions → Payment customization. Give it a name like payment-customization. The CLI scaffolds the extension under extensions/payment-customization/.

You'll now have a directory structure like this:

extensions/
└── payment-customization/
    ├── shopify.extension.toml
    └── src/
        ├── cart_payment_methods_transform_run.graphql
        └── cart_payment_methods_transform_run.ts

Step 3: Configure the Extension

The shopify.extension.toml file configures your function extension. The CLI generates most of this, but here's what mine looked like:

api_version = "2025-07"

[[extensions]]
name = "t:name"
handle = "payment-customization"
type = "function"

  [[extensions.targeting]]
  target = "cart.payment-methods.transform.run"
  input_query = "src/cart_payment_methods_transform_run.graphql"
  export = "cart-payment-methods-transform-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"

The key configuration is the target, this tells Shopify when to run your function. For payment customization, we use cart.payment-methods.transform.run.

Step 4: Define the Input Query

Shopify Functions receive input data through a GraphQL query. You define what data you need in the .graphql file. For my use case, I needed the cart total and the list of available payment methods:

query CartPaymentMethodsTransformRunInput {
  cart {
    cost {
      totalAmount {
        amount
      }
    }
  }
  paymentMethods {
    id
    name
  }
}

This query tells Shopify to pass the cart's total amount and all available payment methods to my function.

Step 5: Write the Function Logic

Here's the TypeScript code that does the actual work:

import type {
  CartPaymentMethodsTransformRunInput,
  CartPaymentMethodsTransformRunResult,
  Operation,
} from "../generated/api";

const NO_CHANGES = {
  operations: [],
};

export function cartPaymentMethodsTransformRun(
  input: CartPaymentMethodsTransformRunInput
): CartPaymentMethodsTransformRunResult {
  // Parse cart total from string to number
  const amountStr = input.cart?.cost?.totalAmount?.amount ?? "0.0";
  const cartTotal = parseFloat(amountStr);

  // If total is <= 2000, do nothing
  if (isNaN(cartTotal) || cartTotal <= 2000) {
    return NO_CHANGES;
  }

  const methods = input.paymentMethods ?? [];
  const operations: Operation[] = [];

  for (let i = 0; i < methods.length; i++) {
    const method = methods[i];
    const nameLower = method.name.toLowerCase();

    // Hide any method whose name contains "afterpay"
    if (nameLower.includes("afterpay")) {
      operations.push({
        paymentMethodHide: {
          paymentMethodId: method.id,
        },
      });
    }
  }

  return { operations };
}

The logic is simple:

  1. Get the cart total amount
  2. If it's over $2,000, iterate through payment methods
  3. Find any method containing "afterpay" in the name
  4. Return a paymentMethodHide operation for each match

Step 6: Configure Access Scopes

Your app needs the write_payment_customizations scope to manage payment customizations. Add this to your shopify.app.toml:

[access_scopes]
scopes = "write_payment_customizations"

Step 7: Build and Run the Dev Server

Build the function:

cd extensions/payment-customization
shopify app function build

Then start the dev server from the app root:

shopify app dev

This starts a local development server. Follow the prompts to install or update the app on your development store.

Step 8: Create the Payment Customization

Here's where things get interesting. The function won't run until you create a Payment Customization resource on the store. While shopify app dev is running, press g to open the GraphiQL interface.

Run this mutation to activate your function:

mutation {
  paymentCustomizationCreate(paymentCustomization: {
    title: "Hide Afterpay over 2000",
    enabled: true,
    functionHandle: "payment-customization"
  }) {
    paymentCustomization {
      id
    }
    userErrors {
      message
    }
  }
}

I successfully created the payment customization. Got the ID back. No errors. Beautiful.

The Confusion: Function Found but Not Running

I was so excited. I went to the store's checkout with a cart over $2,000... and Afterpay was still there. Mocking me.

I double-checked everything. The function was built. The dev server was running. The payment customization was created and enabled.

To verify the function was actually deployed, I ran a query in GraphiQL to list all Shopify Functions on the store:

query {
  shopifyFunctions(first: 10) {
    nodes {
      id
      title
      apiType
    }
  }
}

And there it was! My function was in the list. apiType: "payment_customization". Everything looked correct.

But when I tried to query the payment customization, I kept getting errors about the function not being found. The function existed, but somehow the checkout wasn't using it.

The Mistake: Plan Restrictions

After hours of debugging and scouring the docs, I eventually found this buried in the Payment Customization API documentation:

Plan and geographical restrictions apply. When the Payment Customization API usage is restricted, the function input will still contain all payment methods, however output operations that target restricted payment methods will not take effect on the checkout.

And then the penny dropped.

I checked the store's Shopify plan - it was on the Advanced plan, not Shopify Plus.

Shopify Functions that customize checkout (like payment customization, delivery customization, etc.) require Shopify Plus. The store was on an Advanced plan, which is technically "unlimited" in many feature comparisons, but doesn't support running custom Shopify Functions at checkout.

This explains why:

  • The function could be deployed and listed
  • The payment customization could be created
  • But the function operations simply had no effect at checkout

What I Should Have Done

If I had done my research properly before building, I would have found that Shopify's own App Store has several apps that provide payment method customization using native Shopify features that work on lower plans

For a simple "hide payment method based on cart total" use case, these off-the-shelf solutions would have been:

  • Faster to implement
  • No ongoing development maintenance
  • Compatible with the store's actual plan

The lesson here is simple: check plan requirements before building custom solutions.

What I Learned

Despite the mistake, this project wasn't a waste. Building my first Shopify Function taught me a lot:

  1. Shopify Functions architecture - How functions receive input through GraphQL queries, process it, and return operations
  2. The extension system - How extensions are configured via TOML files, targeted to specific points in the checkout
  3. Shopify CLI workflow - Creating apps, generating extensions, building functions, running dev servers
  4. GraphQL Admin API - Using GraphiQL to create and query resources, debug function state

I'll definitely use this knowledge when a Plus merchant comes along with a customization need that genuinely requires custom code.

Quick Reference: Key Terminology

If you're exploring Shopify Functions, here are the correct terms:

TermDescription
Shopify FunctionsCustom server-side code that runs at specific points in Shopify's backend
Payment Customization FunctionA function type that modifies payment methods at checkout
Function ExtensionThe extension that contains your function code and configuration
TargetThe specific point where your function runs (e.g., cart.payment-methods.transform.run)
Input QueryThe GraphQL query defining what data Shopify passes to your function
OperationsThe actions your function returns (e.g., paymentMethodHide)
Shopify CLIThe command-line tool for developing Shopify apps (shopify app dev, shopify app function build)

Conclusion

I made a classic developer mistake: I got excited about the technology and jumped into building before fully understanding the constraints. The function worked perfectly in isolation, it just couldn't run on the store's plan.

Always check:

  1. Plan requirements - Does the feature require Plus?
  2. Existing solutions - Is there an app that already does this?
  3. Cost/benefit - Is custom development worth it for this use case?

I'll see you again with my next mistake! 👋