- What Are Shopify Functions?
- Prerequisites
- Approach 1: Using Shopify CLI (Recommended)
- Approach 2: Custom Node App (Manual Setup)
- Understanding the Function Code
- Testing Your Function
- Troubleshooting
Shopify Functions are custom code that runs on Shopify's servers to modify checkout behavior in real-time. They allow you to:
- Apply custom discounts
- Customize shipping rates
- Hide/show payment methods
- Validate checkout data
Example We'll Build: "Buy 2 shoes, get 50% off the 3rd pair"
How it works:
- Customer adds products to cart
- Customer goes to checkout
- Shopify runs YOUR function
- Function calculates discount
- Discount appears automatically
# 1. Node.js (v18 or higher)
node --version # Should show v18.x.x or higher
# 2. npm (comes with Node.js)
npm --version
# 3. Shopify CLI
npm install -g @shopify/cli@latest
# Verify installation
shopify version- Development store (free) OR Shopify Partners account
- Staff/Admin access to the store
- Store must be on Shopify Plan or higher (not Basic)
- Go to https://partners.shopify.com
- Sign up for free
- Dashboard → Stores → "Add store" → "Create development store"
- Fill details and create
This is the easiest and official way. Shopify handles authentication, deployment, and configuration automatically.
# Navigate to your projects folder
cd ~/projects
# Initialize new Shopify app
npm init @shopify/app@latest
# You'll be prompted:
# → App name: my-discount-app
# → Template: Remix (choose this)
# → Language: JavaScriptWhat this creates:
my-discount-app/
├── extensions/ # Your functions go here
├── app/ # Admin UI (optional)
├── shopify.app.toml # App configuration
└── package.json
cd my-discount-app
# Generate function extension
shopify app generate extension
# You'll be prompted:
# → Extension type: Product discount
# → Name: buy-x-get-y-discount
# → Language: JavaScriptWhat this creates:
extensions/buy-x-get-y-discount/
├── src/
│ └── run.js # Your function code
├── shopify.extension.toml
└── package.json
Open extensions/buy-x-get-y-discount/src/run.js:
// @ts-check
/**
* Buy 2 Get 50% Off Third Item Discount Function
*
* This function runs every time a customer goes to checkout
* It checks if they have eligible products and applies discount
*/
// Input: Cart data from Shopify
// Output: Discount instructions
export function run(input) {
// Configuration - Easy to modify
const REQUIRED_QUANTITY = 2; // Need to buy this many
const DISCOUNT_PERCENT = 50; // Discount on next item
const ELIGIBLE_PRODUCT_IDS = [
"gid://shopify/Product/123456789", // Replace with your product IDs
// Add more product IDs here
];
// Get all cart lines (items in cart)
const cartLines = input.cart.lines;
// Filter only eligible products from cart
const eligibleLines = cartLines.filter(line => {
const productId = line.merchandise.product.id;
return ELIGIBLE_PRODUCT_IDS.includes(productId);
});
// Count total quantity of eligible products
let totalEligibleQuantity = 0;
eligibleLines.forEach(line => {
totalEligibleQuantity += line.quantity;
});
// Check if customer qualifies for discount
if (totalEligibleQuantity < REQUIRED_QUANTITY + 1) {
// Not enough items, no discount
return {
discounts: []
};
}
// Calculate how many items get discount
// Example: If they buy 5 items, 2 are required, 3 can get discount
const discountableQuantity = totalEligibleQuantity - REQUIRED_QUANTITY;
// Build discount targets (which items get discount)
const targets = eligibleLines.map(line => ({
productVariant: {
id: line.merchandise.id,
quantity: Math.min(line.quantity, discountableQuantity)
}
}));
// Return discount configuration
return {
discounts: [{
targets: targets,
value: {
percentage: {
value: DISCOUNT_PERCENT.toString()
}
},
message: `Buy ${REQUIRED_QUANTITY}, Get ${DISCOUNT_PERCENT}% Off!`
}]
};
}Open extensions/buy-x-get-y-discount/shopify.extension.toml:
api_version = "2024-01"
[[extensions]]
type = "product_discount"
name = "buy-x-get-y-discount"
handle = "buy-x-get-y-discount"
[extensions.build]
command = ""
path = "src/run.js"# Make sure you're in app directory
cd my-discount-app
# Login and connect to store
shopify app dev
# This will:
# 1. Ask you to login (opens browser)
# 2. Ask which store to use
# 3. Start development server
# 4. Give you a URL to install appImportant: When prompted:
- Choose your development store
- Click "Install app" in the browser window that opens
# Deploy to Shopify
shopify app deploy
# You'll be asked:
# → Include configuration? Yes
# → Release version? Yes- Go to your store admin:
yourstore.myshopify.com/admin - Navigate to: Settings → Discounts
- Click: Create discount → Automatic discount
- Select your function: buy-x-get-y-discount
- Configure:
- Name: "Buy 2 Get 50% Off Third"
- Start date: Today
- Status: Active
- Save
- Add 3 eligible products to cart
- Go to checkout
- Discount should appear automatically
- go to this link to get full understanding https://shopify.dev/docs/apps/build/functions
HERE IS THE FLOW
- inside extension folder you have a run.graphql file which will get data from shopify for your app. suppose if you want to get cart data from shopify you need to write a graphql query inside this file. This is for line items you can write for anything
- This is the main point which gives data in the form of input variable to the run function inside run.js file like run(input). This input variable will contains the output of RunInput query
query RunInput {
cart {
lines {
quantity
merchandise {
... on ProductVariant {
id
}
}
}
}
}- inside run.js you can write the code of your own choice with this data to define discounts and apply any other rules. The example function implement on the cart item when they increase the number 3. then we apply total of 10% to the amount
export function run(input) {
const cartLines = input.cart.lines;
let totalQuantityInCart = 0;
for (const item of cartLines) {
totalQuantityInCart += item.quantity;
}
if (totalQuantityInCart >= 3) {
const targets = cartLines.map((line) => ({
productVariant: {
id: line.merchandise.id,
},
}));
return {
discounts: [
{
targets,
value: {
percentage: {
value: "10.0",
},
},
},
],
discountApplicationStrategy: DiscountApplicationStrategy.First,
};
}
return EMPTY_DISCOUNT;
};- This funciton is hard coded you can send data with metafields to the function as well from graphql
- For this you need to create a metafield with the namespace and key from your app with the data filled by the user in your app UI like items = 3 and discount = 10%
mutation CreateDiscount {
discountAutomaticAppCreate(automaticAppDiscount: {
functionId: "f0c17828-da1a-4748-810d-3c3cab2bc977",
title: "My Discount",
startsAt: "2022-06-01T00:00:00Z",
metafields: [
{
namespace: "$app:my-function",
type: "json",
key: "function-configuration",
value: "{\"value\":\"42\"}"
}
]
}) {
automaticAppDiscount {
discountId
}
userErrors {
field
message
}
}
}- Inside run.graphql you can add this code to access this metafield
query {
discountNode {
metafield(namespace: "$app:my-function", key: "function-configuration") {
value
}
}
}- Inside run.js you can access this data as
const metafieldData = JSON.parse(query.dicountNode.metafield.value);
console.log("You can utilize this data: ", metafieldData)- NOW FINALLY the very important thing. after all these steps you will not see your discount as active in you shopify -> discounts tab inside your dashboard. Here is the trick. Now you have to enable it either from you app button click or whatever you want or you need to run graphql query based on your requirement.
mutation CreateDiscount {
discountAutomaticAppCreate(automaticAppDiscount: {
functionHandle: "your_function_handle -> find from shopify.extension.toml",
title:"TITLE -> users will see on checkout",
startsAt: "2026-05-01"
}) {
automaticAppDiscount {
discountId
}
userErrors {
extraInfo
code
}
}
}This query is in my case you can create yours accordingly
- you need to create a proper react app and then install other packages like shopify express app package and sessionstorage package to maintain all things by yourself.
This approach uses custom Node.js app with manual OAuth. More control but more setup.
⚠️ Important: Shopify Functions CANNOT run outside Shopify's infrastructure. You still need to deploy them via Shopify CLI or Partners Dashboard. This approach shows how to build the app structure manually and use API keys.
- Go to: https://partners.shopify.com
- Click: Apps → Create app
- Choose: Custom app
- Fill details:
- App name: "My Discount Function"
- App URL:
http://localhost:3000(for development)
- Click Create
After creating app:
- Click on your app
- Go to: Client credentials tab
- Copy and save:
- Client ID:
abc123... - Client Secret:
def456...(click "Show" first)
- Client ID:
# Create project folder
mkdir my-custom-shopify-function
cd my-custom-shopify-function
# Initialize Node.js project
npm init -y
# Install dependencies
npm install @shopify/shopify-api dotenv express
# Create folder structure
mkdir -p extensions/discount-function/src
touch .env
touch server.js
touch extensions/discount-function/src/run.js
touch extensions/discount-function/shopify.extension.tomlCreate .env file:
# Shopify App Credentials
SHOPIFY_API_KEY=your_client_id_here
SHOPIFY_API_SECRET=your_client_secret_here
# Your store details
SHOP_NAME=your-store.myshopify.com
SHOPIFY_API_VERSION=2024-01
# App URLs
APP_URL=http://localhost:3000
SCOPES=write_discounts,read_productsCreate server.js:
require('dotenv').config();
const express = require('express');
const { shopifyApi, ApiVersion } = require('@shopify/shopify-api');
const app = express();
const PORT = 3000;
// Initialize Shopify API
const shopify = shopifyApi({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET,
scopes: process.env.SCOPES.split(','),
hostName: process.env.APP_URL.replace(/https?:\/\//, ''),
apiVersion: ApiVersion.January24,
isEmbeddedApp: false,
});
app.get('/', (req, res) => {
res.send('Shopify Function App Running!');
});
// OAuth callback endpoint
app.get('/auth/callback', async (req, res) => {
// Handle OAuth here
res.send('Authentication successful!');
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});Create extensions/discount-function/src/run.js with the EXACT SAME CODE as Approach 1 (Step 3).
Create extensions/discount-function/shopify.extension.toml:
api_version = "2024-01"
[[extensions]]
type = "product_discount"
name = "custom-buy-x-get-y"
handle = "custom-buy-x-get-y"
[extensions.build]
command = ""
path = "src/run.js"Create shopify.app.toml in root:
# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration
name = "my-custom-shopify-function"
client_id = "YOUR_CLIENT_ID_HERE"
application_url = "http://localhost:3000"
embedded = false
[access_scopes]
scopes = "write_discounts,read_products"
[auth]
redirect_urls = [
"http://localhost:3000/auth/callback"
]
[webhooks]
api_version = "2024-01"
[pos]
embedded = falseEven with custom setup, you need Shopify CLI to deploy functions:
# Install Shopify CLI globally
npm install -g @shopify/cli
# Login to Shopify
shopify auth login
# Deploy the function
shopify app deploy# Generate installation URL
shopify app install
# Or manually create OAuth URL:
# https://YOUR_STORE.myshopify.com/admin/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=write_discounts,read_products&redirect_uri=http://localhost:3000/auth/callbackFollow the same activation steps from Approach 1.
Let's break down what the function does:
{
cart: {
lines: [
{
id: "gid://shopify/CartLine/1",
quantity: 2,
merchandise: {
id: "gid://shopify/ProductVariant/123",
product: {
id: "gid://shopify/Product/456"
}
}
}
]
}
}1. Get cart items
↓
2. Filter eligible products
↓
3. Count total quantity
↓
4. Check if qualifies (qty >= 3)
↓
5. Calculate discount amount
↓
6. Return discount targets
{
discounts: [{
targets: [
{
productVariant: {
id: "gid://shopify/ProductVariant/123",
quantity: 1 // How many get discount
}
}
],
value: {
percentage: {
value: "50" // 50% off
}
},
message: "Buy 2, Get 50% Off!"
}]
}# Run in development mode
shopify app dev
# Make changes to run.js
# Function auto-reloads
# Test in checkout-
Test Case 1: Not Enough Items
- Add 1 product → No discount ✓
- Add 2 products → No discount ✓
-
Test Case 2: Exactly Qualifying
- Add 3 products → 1 gets 50% off ✓
-
Test Case 3: More Items
- Add 5 products → 3 get 50% off ✓
-
Test Case 4: Mixed Cart
- Add 3 eligible + 2 non-eligible → Only eligible get discount ✓
Add console.logs to run.js:
export function run(input) {
console.log("Cart lines:", input.cart.lines.length);
console.log("Total eligible:", totalEligibleQuantity);
// ... rest of code
}View logs:
shopify app dev --reset # Shows function logsSolution:
- Check extension is deployed:
shopify app deploy - Verify in Partners Dashboard: Apps → Your App → Extensions
- Ensure store is on correct plan (not Basic)
Solution:
- Check product IDs are correct
- Verify function is activated in Admin
- Check date range (start/end dates)
- Clear browser cache and test in incognito
Solution:
- Check syntax errors in
run.js - Verify
shopify.extension.tomlconfiguration - Check Shopify CLI version:
shopify version - Redeploy:
shopify app deploy --force
Solution:
- Verify Client ID and Secret are correct
- Check redirect URLs match in app settings
- Ensure scopes include
write_discounts - Try re-authenticating:
shopify auth logoutthenshopify auth login
# Method 1: From Admin URL
# Go to Products → Click product
# URL: admin/products/8234567890
# ID: gid://shopify/Product/8234567890
# Method 2: Using GraphQL in Shopify Admin
# Admin → Settings → Apps → Develop apps → Create app
# Use GraphQL Explorer:
{
products(first: 10) {
edges {
node {
id
title
}
}
}
}| Feature | Approach 1 (CLI) | Approach 2 (Custom) |
|---|---|---|
| Setup Time | 5 minutes | 30 minutes |
| Authentication | Automatic | Manual OAuth |
| Deployment | One command | Manual setup + CLI |
| Best For | Quick start, standard apps | Custom integrations |
| Maintenance | Easy | More complex |
- ✅ You've built a product discount function
- 📝 Try modifying the discount logic
- 🎯 Explore other function types:
- Shipping discounts
- Payment customization
- Order validation
const DISCOUNT_PERCENT = 30; // 30% instead of 50%const REQUIRED_QUANTITY = 3;
const DISCOUNT_PERCENT = 100; // 100% = freeconst ELIGIBLE_PRODUCT_IDS = [
"gid://shopify/Product/YOUR_PRODUCT_ID"
];if (totalEligibleQuantity >= 5) {
discountPercent = 70; // 70% for 5+ items
} else if (totalEligibleQuantity >= 3) {
discountPercent = 50; // 50% for 3+ items
}Created with ❤️ for Shopify Developers
Need help? Check Shopify Community Forums or Shopify Partners Slack.