Everything Price Bulk Editor does — in detail.
Built around four guarantees: you preview before you commit, you can always rollback, you see live progress, and your filter rules are precise enough to exclude exactly what you need.
Dry-run every task before you commit.
The most common bulk-editor disaster is committing a job that touches more SKUs than you expected. Dry-run renders the full result without touching Shopify: which products matched your filter, which variants would change, what the new price would be, and which items would fail validation.
- ✓ Side-by-side old → new diff per variant
- ✓ Eight typed risk warnings — high / medium / low severity
- ✓ SKU count + total revenue delta estimate
- ✓ Exportable as CSV for stakeholder sign-off
filter: tag=summer-sale AND vendor=Outdoor matched: 2,481 SKUs across 412 products action: −25% off compare-at, floor $9.99 variant was now Δ SUM-TEE-S $39.00 $29.25 −$9.75 SUM-TEE-M $39.00 $29.25 −$9.75 SUM-CAP-OS $24.00 $18.00 −$6.00 … 2,478 more rows WARNINGS (3): SUM-LEGGINGS-XL — would price below floor; skipped SUM-PACK-A — missing compare-at; skipped SUM-SOCKS-L — currently archived; skipped estimated revenue delta over 7 days: −$48,210
Every dry-run runs the same eight rule checks the executor runs at commit time — except no products get touched. Each warning carries a severity so you know what's catastrophic vs. just worth noticing.
- ● Price below cost. The new price is under the Shopify cost-per-item. Margin would turn negative.
- ● Price goes negative. The action produces a negative price. Hard reject.
- ● Sign error suspected. The median delta contradicts the action sign (e.g. you said "decrease" but the math goes up). Often a missing minus sign.
- ● Missing compare-at. You asked for percent off compare-at, but the variant has none. Skipped.
- ● Archived product. The product is archived on Shopify. Skipped — you probably didn't mean to discount it.
- ● Variant without cost. Margin pricing rule, but the variant has no cost on Shopify. Skipped.
- ● Price outlier. The new price is more than two standard deviations from the median. Worth a second look.
- ● Low inventory. The variant has fewer than five units in stock. Informational — you may not want to feature it.
- 2026-06-14 09:00 created snapshot 2,481 variants
- 2026-06-14 09:01 apply −25% compare-at, 2,478 succeeded, 3 skipped
- 2026-06-21 23:59 rollback replayed snapshot, 2,478/2,478 restored
Snapshot-based rollback that actually completes.
Before any task runs, we record the prior price + compare-at price for every targeted variant. Rollback replays that snapshot — it's not "apply the reverse percentage" math, which compounds rounding errors and can't recover from external edits during the sale.
- ✓ Per-variant exact restore, including compare-at
- ✓ Idempotent — retry-safe if Shopify rate-limits
- ✓ Resumable if interrupted mid-rollback
- ✓ Per-market snapshots restored independently
CSV import for arbitrary per-SKU prices.
When the price logic doesn't fit a rule (contract pricing from a wholesaler, a one-off liquidation list from finance), upload a CSV with your SKUs and the new absolute price. The same dry-run + rollback flow applies.
- ✓ Columns:
sku(recommended) orvariant_id, plusnew_price. Optional:new_compare_at_price. - ✓ Up to 5,000 rows per file
- ✓ Per-row dry-run validation before commit
- ✓ Audit log retains the original CSV
sku,new_price,new_compare_at_price SUM-TEE-S,29.25,39.00 SUM-TEE-M,29.25,39.00 SUM-CAP-OS,18.00,24.00 SUM-PACK-A,49.00,69.00 SUM-HOODIE-L,54.00, … up to 5,000 rows per file
Tag, collection, and attribute-based filters.
Filter the catalog using any combination of tag, collection, vendor, product type, price band, or inventory level. Variant filters add SKU patterns and option values.
Product-level
- tag = "summer-sale"
- collection ∈ "Outdoor", "Hiking"
- vendor != "Discontinued"
- price BETWEEN $20 AND $80
Variant-level
- sku STARTS_WITH "SUM-"
- option.size IN "S", "M"
- inventory_quantity > 0
- compare_at IS NOT NULL
Exclusion
- NOT tag = "new-arrival"
- NOT sku IN [upload list]
- NOT vendor = "Brand X"
- NOT product_type = "Gift Card"
Per-market pricing for Shopify Markets.
Apply a different price, percentage off, or rounding rule per market. Set absolute prices for the US, percentage off the EU base, and a rounded-to-.95 rule for Switzerland — all in one task. Each market's prior state is snapshotted independently.
| Market | Rule | Round | Floor |
|---|---|---|---|
| US | −25% of compare-at | .99 | $9.99 |
| EU | −20% of base | .00 | €10.00 |
| CH | Fixed CHF | .95 | CHF 14.95 |
| UK | −25% of compare-at | .99 | £8.99 |
Recurring rules — define once, run forever.
Flash Friday every week, monthly clearance on the 1st, automatic end-of-season reversion on Sept 22. Each run creates a fresh snapshot, so every recurrence is independently rollback-able.
Flash Friday at 09:00 CET
Every Friday at 09:00, apply −20% to tag="flash-friday". Auto-rollback at 23:59 Sunday. Rotates with the SKUs currently tagged.
End-of-month clearance
Last day of each month, set prices to cost × 1.1 for inventory > 90 days old. Rollback the following Monday.
Full history of every change, exportable to CSV.
Every action — task created, dry-run inspected, commit, rollback, recurrence triggered — is logged with the user, timestamp, IP, and full payload. Filter by date, user, or affected SKU; export to CSV for finance, GDPR data requests, or internal audits.
Retention: from 30 days on Free up to 5 years on Scale, by plan tier.
2026-06-14T09:00Z marie@store.com task.create "Summer Sale 25%" 2026-06-14T09:00Z marie@store.com task.dry_run matched=2481 2026-06-14T09:01Z marie@store.com task.commit succeeded=2478 skipped=3 2026-06-14T09:18Z system task.complete duration=1041s 2026-06-21T23:59Z system task.rollback restored=2478 of 2478 2026-06-22T08:14Z cfo@store.com audit.export period=2026-06 format=csv
Margin- and cost-based pricing.
Set price as cost × multiplier, target a fixed margin percentage, or apply tiered markup by price band. Combine with rounding rules (always .99, always .95, etc.) and minimum-margin floors.
- ✓ Pull cost from Shopify Inventory cost-per-item
- ✓ Floor by minimum margin (e.g. always ≥ 40%)
- ✓ Tier by price band (markup 1.4× under $20, 1.6× over)
| SKU | Cost | Rule | New price | Margin |
|---|---|---|---|---|
| SUM-TEE-S | $11.20 | ×1.6 → .99 | $17.99 | 37.7% |
| SUM-CAP-OS | $5.40 | ×1.8 → .99 | $9.99 | 46.0% |
| SUM-PACK-A | $22.50 | ×1.4 → .99 | $31.99 | 29.7% |
| SUM-JKT-L | $48.00 | ×1.4, min 35% | $73.99 | 35.1% |
Catch the manual edit that broke your sale.
Once a campaign is live, anything else that touches a product price — a teammate fixing a typo, an inventory-sync app, a Shopify Flow rule — silently drifts the catalog away from the snapshot the rollback expects. We listen on the Shopify products/update webhook and surface every drift as an alert with the variant, the campaign, the price we wrote, and the price that's there now. Acknowledge it or roll it back; either way, no surprises when the sale ends.
- ✓ Webhook-driven — fires within seconds of the manual edit
- ✓ Idempotent per (variant, campaign, observed-price); Shopify retries can't dupe
- ✓ Inline list + nav banner counter, polled every 30 seconds
- ✓ Drift caught here keeps the rollback contract honest
We checked the App Store listings of seven direct competitors in May 2026. None advertise drift detection.
2026-06-15 14:22 variant SUM-TEE-S
campaign price $29.25 → observed $29.99
source: products/update webhook
status: NEW [acknowledge]
2026-06-15 16:08 variant SUM-CAP-OS
campaign price $18.00 → observed $24.00
source: products/update webhook
status: NEW [acknowledge]
2026-06-16 09:41 variant SUM-PACK-A
campaign price $49.00 → observed $52.00
source: products/update webhook
status: ACKNOWLEDGED
by: marie@store.com on 2026-06-16 10:02
Your campaigns don't get stuck. Guaranteed.
Most "my BFCM is frozen" support tickets in this category have the same root cause: the worker chain that drives a bulk operation lost a message, a deploy interrupted mid-run, or Shopify's bulk API briefly hiccupped. We run three independent recovery layers so the operator never sees a campaign stuck in RUNNING for hours without a path forward.
Status-progress recovery
The progress-polling handler self-heals during normal operation — if Shopify shows a bulk operation we don't know about (or vice versa) it reconciles before the merchant ever notices.
Stuck-message scanner
A cron sweep finds tasks that are READY past run_at, RUNNING with no progress, or fully applied but still mid-flight; either resets a stuck Messenger row for redelivery or re-dispatches the right TaskMessage. No Shopify API calls — fast and cheap.
Shopify reconciliation
When the cheap fixes fail, an hourly cron asks Shopify directly: "what's the real status of bulk operation X?" Shopify's answer wins; our local state conforms to it. Orphans get marked, completions get acknowledged, failures get surfaced with the Shopify error code.
The three layers were ported from a sister BigCommerce app where they've run in production for two years. Every recovery decision lands in the audit log so support can reconstruct what happened later.
Stuck campaigns are the #1 complaint cited in reviews of competing apps. We made it the failure mode we engineer against, not the one we apologize for.
Try the workflow on your own catalog.
Install free on Shopify, run one dry-run against your store, decide afterwards. The free plan handles 100 SKU edits a month — enough to validate before paying for anything.
Free plan · No credit card · 5-day trial on Growth and Scale · Save 30% with annual billing