- Published on
Mistake - Building a Shopify Function for the Wrong Plan
- Authors
- 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:
- A Shopify Partner account and a development store
- Node.js 20.10+
- Shopify CLI installed globally:
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:
- Get the cart total amount
- If it's over $2,000, iterate through payment methods
- Find any method containing "afterpay" in the name
- Return a
paymentMethodHideoperation 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:
- Shopify Functions architecture - How functions receive input through GraphQL queries, process it, and return operations
- The extension system - How extensions are configured via TOML files, targeted to specific points in the checkout
- Shopify CLI workflow - Creating apps, generating extensions, building functions, running dev servers
- 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:
| Term | Description |
|---|---|
| Shopify Functions | Custom server-side code that runs at specific points in Shopify's backend |
| Payment Customization Function | A function type that modifies payment methods at checkout |
| Function Extension | The extension that contains your function code and configuration |
| Target | The specific point where your function runs (e.g., cart.payment-methods.transform.run) |
| Input Query | The GraphQL query defining what data Shopify passes to your function |
| Operations | The actions your function returns (e.g., paymentMethodHide) |
| Shopify CLI | The 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:
- Plan requirements - Does the feature require Plus?
- Existing solutions - Is there an app that already does this?
- Cost/benefit - Is custom development worth it for this use case?
I'll see you again with my next mistake! 👋