PROJECT

Shopify Synth

Realistic, seed-reproducible Shopify order data for testing analytics tools against how Indian D2C commerce actually behaves.

Next.jsOpen sourceSeeded generationD2C analytics
Live demoView source
Open live demo →GitHub repo →

What is it?

Shopify Synth is an open source tool that generates realistic, seed-reproducible Shopify store data. Same seed, same output, every single time. I built it for people who need to test analytics tools against something that actually behaves like a real store, not a demo dataset with round numbers.

Live demo →Source →

Why we built it?

Honestly, I built this because I was annoyed. I was working on an analytics product for Indian D2C brands and needed test data, and every "fake Shopify data" tool I tried just populates a dev store with placeholder products and random orders. Fine if you're doing a demo. Completely useless if you're trying to find out whether your calculations actually hold up.

None of them modeled the stuff that actually breaks analytics in this market: heavy cash-on-delivery order splits, return-to-origin risk, festival demand spikes that don't follow a normal calendar at all. So I built something that does, mostly because I got tired of testing against data I already knew was lying to me.

How it works?

Three things go in: your product catalog (a real CSV, not a template), a set of behavioral parameters, and optional date-range overrides for things like a festival sale window.

Here's the part that took me a while to get comfortable with: nothing in here is actually random, not in the "anything goes" sense. Every value comes from one seeded generator I pass through the code explicitly, never a global random source. That's the entire reason the same seed gives you the same store twice, and honestly it also just made debugging saner, I always knew exactly where randomness was entering the picture.

Most numbers, prices, discounts, order counts, come from a bell curve, but a clamped one. A product priced around ₹800 has no business generating at ₹40 or ₹4,000 just because a normal distribution technically allows it at the tails. So everything gets bounded to a sane range. Yes/no outcomes (COD or prepaid, returned or not) are weighted coin flips. Which product lands in someone's basket is weighted by that product's revenue share, same logic as a loot table in a game.

Days aren't flat either, which took some getting used to structurally.

Trend+ Seasonality+ Noise

Each day gets a trend (ramping up, flat, declining), a seasonality bump (weekends, festivals), then a little random noise on top so it doesn't look robotic. Month one doesn't look like month three, and Diwali doesn't look like a random Tuesday, because it shouldn't.

Basket building doesn't chase a target number, and this was a deliberate choice after a version that did caused problems (more on that below). It decides how many items go in, picks products weighted by revenue share, rolls quantities, and lets the order value fall out as whatever it happens to be. Nobody actually sets their own average order value on purpose, it's just a consequence of what's for sale and how people shop, so I stopped pretending the generator could set it on purpose either.

Every run also produces a comparison table, expected values against what actually got generated, so you can see if the tool is quietly lying to you before you build anything on top of it.

How to use it?

  1. Bring a real product catalog. Export or prepare it as a CSV. There's no built-in fake catalog on purpose, you have to upload your own, otherwise you're not really testing anything.
  2. Start from a preset, don't build from scratch. Rather than setting all 13 behavioral parameters by hand, pick a starting scenario and it autofills sensible values for you:

    Subscription wellness, high retention, low COD

    Discount-dependent fashion, high volume, weekend spikes

    COD-heavy licensed catalog, high RTO risk

    Hero product + launch ramp, mostly new customers

    Declining brand, rising discounts, rising refunds

    High AOV, low frequency, metro prepaid, festival peaks

  3. Adjust from there if you need to. The preset fills in a reasonable starting point, not a locked config. Nudge individual parameters, COD rate, RTO rate, items per basket, whatever your test actually needs.
  4. Layer a timeline override if the test needs a specific window. A two-week festival sale, a single launch day, whatever it is, you only specify what changes for that window. Your base settings stay untouched underneath it.
  5. Run the generator.
  6. Read the comparison table before you trust anything. It shows expected values next to actual generated values, side by side. If something's off, this is where you'll see it before it quietly corrupts whatever you're testing downstream.
  7. Export as JSON or a Shopify-importable CSV, ready to plug into whatever analytics tool you're actually trying to test.

What important decisions we took while building?

No built-in fake catalogs. Shipping fictional branded product data in an open source tool felt wrong to me, plain and simple. And a catalog nobody can replace with their own doesn't actually test anything real. So the tool just refuses to run without a real upload.

Average order value stopped being an input, and it took three broken attempts before I got there.

Round 1

Target unreachable

Round 2

Two new bugs

Round 3

Std 16x off

Final

Derived instead of sampled

Round one capped unit prices near a target AOV. Broke almost immediately, a preset targeting ₹849 couldn't hit that number against a real catalog averaging in the ₹600s no matter how hard I tuned the cap. Round two rebuilt basket construction to actively sample toward the target instead. That fixed the mean, but surfaced two new bugs in the process, an uncapped probability field quietly going over 100%, and a validation formula that ignored a two-stage customer-assignment rule I'd built earlier. Round three fixed those two, but then standard deviation came in roughly 16x off target, and it took me a minute to realize why, a handful of oversized orders barely move an average across 6,500 orders, but they completely dominate a variance calculation.

At that point I stopped trying to patch the tolerance band a fourth time and asked a more basic question: does a merchant even set average order value directly? No. It's a consequence of catalog and shopper behavior, not a dial anyone turns. So I pulled it out as an input entirely and made it a derived, live-computed number instead. I didn't patch the bug class, I made it structurally impossible.

Timeline overrides layer on top of base settings instead of replacing them. A two-week festival override only needs to specify what actually changes for those two weeks, not restate every single parameter from scratch. Small decision, saved a lot of repeated typing later.

The tool validates its own output, because I got burned trusting it once. Orders get tagged new or returning two ways, a direct roll, or failing a second repeat-purchase check. My first validation code just averaged the input rate against the output, and it kept showing a 23-point gap that looked exactly like a generator bug. It wasn't. The validation math was wrong, not the generation logic, the actual effective rate needed both paths added together, not averaged. I found a near-identical version of the same mistake later, my expected standard deviation for daily order counts was wrong because I was only accounting for the wobble within a single day, not the swing between days caused by weekends and festivals. Both times, the generator was right and my own math checking it was wrong. That's the whole reason the comparison table exists as a built-in feature instead of something you're expected to verify by hand every time.

If you are evaluating me for senior product or growth work, this is the kind of ambiguity I am comfortable sitting in until the model is right.