Skip to content

Ahmad-shopify-dev/shopify-functions-api

Repository files navigation

Shopify Functions Complete Guide - Product Discount (Buy X Get Y)

Table of Contents

  1. What Are Shopify Functions?
  2. Prerequisites
  3. Approach 1: Using Shopify CLI (Recommended)
  4. Approach 2: Custom Node App (Manual Setup)
  5. Understanding the Function Code
  6. Testing Your Function
  7. Troubleshooting

What Are Shopify Functions?

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:

  1. Customer adds products to cart
  2. Customer goes to checkout
  3. Shopify runs YOUR function
  4. Function calculates discount
  5. Discount appears automatically

Prerequisites

Required Tools

# 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

Required Shopify Access

  • Development store (free) OR Shopify Partners account
  • Staff/Admin access to the store
  • Store must be on Shopify Plan or higher (not Basic)

How to Get Development Store

  1. Go to https://partners.shopify.com
  2. Sign up for free
  3. Dashboard → Stores → "Add store" → "Create development store"
  4. Fill details and create

Approach 1: Using Shopify CLI (Recommended)

This is the easiest and official way. Shopify handles authentication, deployment, and configuration automatically.

Step 1: Create New Shopify App

# 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: JavaScript

What this creates:

my-discount-app/
├── extensions/          # Your functions go here
├── app/                 # Admin UI (optional)
├── shopify.app.toml     # App configuration
└── package.json

Step 2: Create Discount Function Extension

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: JavaScript

What this creates:

extensions/buy-x-get-y-discount/
├── src/
│   └── run.js           # Your function code
├── shopify.extension.toml
└── package.json

Step 3: Write the Function Code

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!`
    }]
  };
}

Step 4: Configure the Extension

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"

Step 5: Connect to Your Shopify Store

# 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 app

Important: When prompted:

  • Choose your development store
  • Click "Install app" in the browser window that opens

Step 6: Deploy the Function

# Deploy to Shopify
shopify app deploy

# You'll be asked:
# → Include configuration? Yes
# → Release version? Yes

Step 7: Activate in Shopify Admin

  1. Go to your store admin: yourstore.myshopify.com/admin
  2. Navigate to: Settings → Discounts
  3. Click: Create discountAutomatic discount
  4. Select your function: buy-x-get-y-discount
  5. Configure:
    • Name: "Buy 2 Get 50% Off Third"
    • Start date: Today
    • Status: Active
  6. Save

Step 8: Test the Function

  1. Add 3 eligible products to cart
  2. Go to checkout
  3. Discount should appear automatically

⚠️ NOTE (very important)

HERE IS THE FLOW

  1. 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
  2. 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
          }
        }
      }
    }
  }
  1. 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;
  };
  1. This funciton is hard coded you can send data with metafields to the function as well from graphql
  2. 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
        }
      }
    }
  1. Inside run.graphql you can add this code to access this metafield
    query {
      discountNode {
        metafield(namespace: "$app:my-function", key: "function-configuration") {
          value
        }
      }
    }
  1. 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)
  1. 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


THIS THING IS NOT POSSIBLE OUTSIDE USING SIMPLE (NPM INIT -Y) METHOD

  • 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.

Approach 2: Custom Node App (Manual Setup)

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.

Step 1: Create Shopify App in Partners Dashboard

  1. Go to: https://partners.shopify.com
  2. Click: AppsCreate app
  3. Choose: Custom app
  4. Fill details:
    • App name: "My Discount Function"
    • App URL: http://localhost:3000 (for development)
  5. Click Create

Step 2: Get API Credentials

After creating app:

  1. Click on your app
  2. Go to: Client credentials tab
  3. Copy and save:
    • Client ID: abc123...
    • Client Secret: def456... (click "Show" first)

Step 3: Setup Project Structure

# 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.toml

Step 4: Configure Environment Variables

Create .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_products

Step 5: Create Basic Server (Optional)

Create 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}`);
});

Step 6: Create the Same Function Code

Create extensions/discount-function/src/run.js with the EXACT SAME CODE as Approach 1 (Step 3).

Step 7: Create Extension Configuration

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"

Step 8: Create App Configuration

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 = false

Step 9: Deploy Using Shopify CLI

Even 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

Step 10: Install App to Store

# 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/callback

Step 11: Activate (Same as Approach 1, Step 7)

Follow the same activation steps from Approach 1.


Understanding the Function Code

Let's break down what the function does:

Input Structure

{
  cart: {
    lines: [
      {
        id: "gid://shopify/CartLine/1",
        quantity: 2,
        merchandise: {
          id: "gid://shopify/ProductVariant/123",
          product: {
            id: "gid://shopify/Product/456"
          }
        }
      }
    ]
  }
}

Logic Flow

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

Output Structure

{
  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!"
  }]
}

Testing Your Function

Method 1: Shopify CLI Testing

# Run in development mode
shopify app dev

# Make changes to run.js
# Function auto-reloads
# Test in checkout

Method 2: Manual Testing Checklist

  1. Test Case 1: Not Enough Items

    • Add 1 product → No discount ✓
    • Add 2 products → No discount ✓
  2. Test Case 2: Exactly Qualifying

    • Add 3 products → 1 gets 50% off ✓
  3. Test Case 3: More Items

    • Add 5 products → 3 get 50% off ✓
  4. Test Case 4: Mixed Cart

    • Add 3 eligible + 2 non-eligible → Only eligible get discount ✓

Method 3: Debug Logs

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 logs

Troubleshooting

Problem: Function not appearing in Discounts

Solution:

  1. Check extension is deployed: shopify app deploy
  2. Verify in Partners Dashboard: Apps → Your App → Extensions
  3. Ensure store is on correct plan (not Basic)

Problem: Discount not applying

Solution:

  1. Check product IDs are correct
  2. Verify function is activated in Admin
  3. Check date range (start/end dates)
  4. Clear browser cache and test in incognito

Problem: "Function execution failed"

Solution:

  1. Check syntax errors in run.js
  2. Verify shopify.extension.toml configuration
  3. Check Shopify CLI version: shopify version
  4. Redeploy: shopify app deploy --force

Problem: OAuth/Authentication Issues (Approach 2)

Solution:

  1. Verify Client ID and Secret are correct
  2. Check redirect URLs match in app settings
  3. Ensure scopes include write_discounts
  4. Try re-authenticating: shopify auth logout then shopify auth login

How to Get Product IDs

# 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
      }
    }
  }
}

Key Differences: Approach 1 vs 2

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

Next Steps

  1. ✅ You've built a product discount function
  2. 📝 Try modifying the discount logic
  3. 🎯 Explore other function types:
    • Shipping discounts
    • Payment customization
    • Order validation

Useful Resources


Common Customizations

Change discount percentage

const DISCOUNT_PERCENT = 30;  // 30% instead of 50%

Buy 3 Get 1 Free

const REQUIRED_QUANTITY = 3;
const DISCOUNT_PERCENT = 100;  // 100% = free

Specific product only

const ELIGIBLE_PRODUCT_IDS = [
  "gid://shopify/Product/YOUR_PRODUCT_ID"
];

Tiered discounts

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.

About

No description, website, or topics provided.

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors