Shopify promotion engine · Admin GraphQL API
Enter a promotion once. It runs, ends and cleans up by itself.
A sample jewelry store with four campaigns: Black Friday, a weekly rotating deal, a free gift and weekend free shipping. Move the store clock to Black Friday, run sync and watch prices, badges, banners and discounts change — then undo it.
How it's built
Next.js 16 · TypeScript · a framework-free promotion engine with unit tests (node:test) · Shopify Admin GraphQL 2026-10 · Tailwind CSS 4 · Vercel.
No workflow per campaign
A campaign is a record: schedule, who, benefit, storefront messaging. One reconciler computes the state the store should be in right now and emits only the calls needed to get there. New campaign = new record, not new automation.
Regular prices are always safe
Sale prices are computed from the price book, never from the live price, so markdowns cannot compound. Every call is written to a ledger with the values it overwrote; ending or undoing a run restores them exactly.
No double discounts
Each product gets at most one price benefit: highest priority wins, the rest are skipped and reported. Marked-down products are left out of automatic discounts, and discounts are created with combinesWith off for product discounts.
Store time, DST included
Schedules are wall-clock rules in America/Los_Angeles — fixed dates, “4th Thursday of November +1”, weekly rotations. The clock change on Nov 1 does not shift starts or ends; windows are also written to Shopify so a missed sync can't leave a sale running.
Production setup vs. this build
How I'd start on your store
- Audit what exists: Flow workflows, metaobjects, Liquid bindings, discount nodes, apps touching prices.
- Agree the campaign model and the price book, then dry-run the reconciler read-only against your store.
- Switch on one campaign type at a time, with the ledger and readiness alerts from day one.
- Hand over: a manager-facing form, a runbook and the test suite.