Tom Sailors Contact
Guide · Loyalty

How to build a custom loyalty points program on Shopify

Published 2026-08-01 · Updated 2026-08-01

Loyalty apps charge monthly for two things: a points ledger and a redemption flow. Both run natively on Shopify primitives — metafields, webhooks, and the Discount API — with no per-order fees and full control of the rules. This is the four-piece build.

The ledger: a customer metafield

The balance is a number_integer metafield at loyalty.points_balance. Metafield values are strings in the API — "150", not 150 — and the write is one mutation:

mutation SetPointsBalance($metafields: [MetafieldsSetInput!]!) {
  metafieldsSet(metafields: $metafields) {
    metafields { id key value }
    userErrors { field message }
  }
}

Accrual: the orders/paid webhook

A custom app subscribes to orders/paid. On each event it computes points from the order subtotal — 1 point per dollar is the common multiplier — reads the current balance, and writes the new one. Two rules keep the ledger honest: make the handler idempotent (Shopify redelivers webhooks; the same order must never accrue twice), and log every accrual with its order ID so any balance can be explained line by line.

The portal: where customers see and spend

A theme snippet or app-proxy page shows the balance from customer.metafields.loyalty.points_balance, earning history from the accrual log, and a redemption form. Gate redemption at a minimum (say 100 points) and cap it at the current balance — enforce both again server-side; the storefront check is a courtesy, the endpoint check is the rule.

Redemption: a single-use code, then the deduction

mutation CreateRedemptionCode($basicCodeDiscount: DiscountCodeBasicInput!) {
  discountCodeBasicCreate(basicCodeDiscount: $basicCodeDiscount) {
    codeDiscountNode { id }
    userErrors { field message }
  }
}

The redemption endpoint validates the balance, creates the code — restricted to that customer, usage limit one, value set by your points-to-dollars rate — deducts the points via metafieldsSet, and returns the code. Order matters: create the code first, deduct second, and roll the deduction back if code creation fails. A customer should never lose points without holding a code.

The admin view

A dashboard query aggregating balances outstanding, points issued versus redeemed, and codes generated tells the merchant what the program owes and whether it moves repeat purchases. Outstanding points are a real liability — the number belongs in front of whoever owns the P&L, not buried in metafields.

When an app is the right answer anyway

Referral tracking, VIP tier emails, and prebuilt integrations are genuine app value. The custom build wins when the rules are yours — odd multipliers, B2B exclusions, points tied to subscription renewals — or when the app bill outruns what the program uses. Migrating balances out of an existing app into metafields is the customer-import problem, and it's routine.

Outgrowing your loyalty app — or its bill?

Email Tom the program rules — earn rate, redemption steps, tiers if any — and current monthly app cost. The build is quoted against what the program actually does.

Email Tom →