001 — Tool
Disco
Run a sale. End it cleanly.
A discount and price management CLI for Shopify. Plans are explicit and editable, every write previews before it runs, writes are reversible, and recovery is a documented procedure rather than operator memory. Base prices and market price lists.
- Language
- Python 3.12+
- Licence
- Proprietary
- Runs
- Your machine
002 — The problem
Starting a sale is easy. Ending one exactly is not.
A sale touches the price and compare-at price of every variant, in your base currency and in every market price list. Products already marked down get discounted twice. Broken compare-at prices hide the strikethrough.
When the sale ends, someone has to put every price back exactly as it was. Usually that means a spreadsheet and a good memory.
003 — How it works
The whole sale, start to finish.
- 01
backupSnapshot a collection: base prices and every market price list, each entry in its own currency.
- 02
generateBuild an editable plan from the backup. Set a base discount, scope by colour, give outliers a deeper markdown, prune what shouldn't be on sale.
- 03
applyPreview every variant, then confirm. Products already on sale are skipped, never stacked. Broken compare-at prices are healed and flagged.
- 04
unwindWrite the plan's captured baseline back, variant by variant, base and market. The sale ends where it started.
$ disco --dry-run apply price_plans/summer.json
14 on sale, 2 skipped, 1 healed · 5 product(s) · 2 market(s)
SKIPPED (already on sale): Field Jacket [Olive] compareAt 240.00 > price 180.00
SKIPPED (already on sale): Field Jacket [Navy] compareAt 240.00 > price 180.00
! Merino Crew [Fog] EU Retail compare-at inverted -> healed (price 92.00, was 78.00)
full preview → price_previews/summer-20260601T091244Z.json
$ disco apply price_plans/summer.json
14 on sale, 2 skipped, 1 healed · 5 product(s) · 2 market(s)
SKIPPED (already on sale): Field Jacket [Olive] compareAt 240.00 > price 180.00
SKIPPED (already on sale): Field Jacket [Navy] compareAt 240.00 > price 180.00
! Merino Crew [Fog] EU Retail compare-at inverted -> healed (price 92.00, was 78.00)
full preview → price_previews/summer-20260601T091312Z.json
apply these changes? [y/N] y
auto_backup_id: backup-20260601T091318Z
succeeded: 6 failed: 0
ok gid://shopify/Product/7517161062482 Merino Crew
ok gid://shopify/Product/7517161095250 Trail Pant
ok gid://shopify/Product/7517161128018 Camp Mug
ok gid://shopify/Product/7517161160786 Wool Beanie
ok price_list gid://shopify/PriceList/19342164057 EU Retail
ok price_list gid://shopify/PriceList/19342164312 UK Retail
004 — Safety
It refuses to write when it shouldn't.
- Preview before write.
applyalways shows what it will do first. A real write needs confirmation;--dry-runnever writes. - Automatic pre-write backup. Before any mutation, the affected live state is snapshotted. Recovery is
disco restore <auto-backup-id>. - Wrong-store refusal. A plan taken from one store will not write to another.
- Drift refusal. If live prices moved since the plan was generated,
applyshows the drift and stops, even with-y. - No accidental piped writes. A piped or
--jsonrun stops at the preview unless you say otherwise.
005 — What it doesn't do
Know the edges before you rely on it.
- No web UI. Terminal only, with an optional guided interactive mode.
- No cloud sync. Backups, plans and previews stay on your filesystem.
- No live multi-store. One store per invocation.
- No rerun of a partially applied plan. Restore the automatic backup, regenerate, apply.
006 — Who it's for
For teams who run sales often and end them on time.
Brands selling in more than one currency. Merchandisers who want an uneven sale without a spreadsheet. Agencies who have been asked, once too often, what the prices were before the promotion.
007 — Get Disco
Disco is available by request while the code is private.
Write with a line about your store and how you would use it. Messages are read by the person who builds the tools.