Research · 10 August 2026

52 of the 54 made-to-order Shopify storefronts I could measure offered no deposit option.

In August 2026 I scanned 120 public storefronts selling made-to-order, custom, commissioned or pre-order goods, and checked each one for a deposit or partial-payment option at checkout. This page reports what the scan found, how it works, and — at some length — what it cannot see.

The finding

96.3%

of the 54 Shopify storefronts where a deposit option could be determined offered none on the products sampled.

52 of 54. An upper bound, not a point estimate. Drawn from a convenience sample of 120 storefronts, so it describes those storefronts and nothing wider.

Of the 120 storefronts in the frame, 54 were confirmed Shopify storefronts where the question could actually be answered. 52 of those had no deposit or partial-payment option on any product sampled; 2 had one. That is 96.3% and 3.7%.

Three qualifications have to travel with that number. Without them it is a different, and wrong, claim.

The frame is a convenience sample

I assembled the 120 candidates by hand on 9 August 2026: three public “best Shopify stores” round-ups for furniture, custom rings and jewellery, plus targeted searches crossing made-to-order language with fifteen verticals. It is not a random sample of Shopify, of e-commerce, or even of made-to-order merchants. The round-up half was pre-filtered to Shopify by whoever wrote the round-up, which raises the confirmed-Shopify rate and skews the list toward large, well-marketed brands. Nothing here generalises past those 120 storefronts, and this figure is never “96% of Shopify stores”.

96.3% is a ceiling, not an estimate

The method reads Shopify’s own selling-plan data. At least one deposit app — PreProduct — takes deposits without selling plans at all, by vaulting a card and charging the balance later, and a storefront using it is invisible to this scan and lands in the no-deposit bucket. I run one such storefront as a control on every controls run for exactly that reason, and on the run behind these figures it came back as no-deposit, as documented. So the true no-deposit share is at most 96.3%. The error runs in one direction only.

The 3.7% is two storefronts

Two is not a market rate. The tool’s own floor for publishing a percentage is 20 storefronts, which the denominator of 54 clears and the numerator of 2 does not — so it raises a thin-numerator warning against that side of the split. Quote it only as “2 of 54”, beside the figure it is the converse of, and never on its own.

The summariser writes its own headline sentence, with those qualifications built into the string rather than sitting beside it, because a neighbouring field does not survive being copied into an article. Reproduced unedited, machine phrasing and all:

These 120 storefronts are a convenience sample assembled by hand for this scan, not a random sample of Shopify, so nothing here generalises beyond that list. Of 54 Shopify stores where a deposit option could be determined, 96.3% offered no deposit or partial-payment option on the products sampled. This figure is an UPPER BOUND, not a point estimate: 1 confirmed deposit merchant(s) in the blind-spot controls are counted as NO_DEPOSIT by this method, so it can only overstate the no-deposit share, never understate it. The other 66 of the 120 are not in that denominator and are not counted as offering nothing: 32 turned out not to be Shopify at all, 1 confirmed Shopify store(s) could not be determined, and 33 were never confirmed to be Shopify in either direction.

headline.statement, summary.json

Two smaller observations, both interesting and neither load-bearing. Only 2 of the 120 storefronts carried any Shopify selling-plan group at all; between them they held 9 deposit groups, and not one storefront in the frame carried a subscription group. And the one price band with enough storefronts for the tool to quote — 20 storefronts whose median variant price fell between 1,000 and 4,999 in their own currency — split 19 no-deposit to 1 with a deposit, or 95%. Since products.json states no currency, those bands are approximate across storefronts, and 1 is an even thinner numerator than 2.

The other 66 storefronts

66 of the 120 are not in that denominator, and not one of them is counted as offering nothing:

  • 32 were not Shopify storefronts. Another platform identified itself in the response headers or the page. They are excluded from every deposit denominator.
  • 33 were never confirmed as Shopify in either direction. Something stopped the scan before the platform could be established, so they belong to neither population. That is 27.5% of the frame.
  • 1 was a confirmed Shopify storefront whose deposit option could not be read. Of the 55 confirmed Shopify storefronts, that is 1.8%.

Those last two are different populations and must not be added together into one pool of unresolved Shopify stores. A quarter of this frame being unresolved is the single largest weakness in it, which is why the counts sit here rather than in a footnote.

Between them, the 34 undetermined storefronts break down like this:

  • 19gave up no platform evidence in either direction — nothing said Shopify, and nothing said anything else either (fingerprint_unavailable).
  • 5could not be asked: /robots.txt itself returned a 429 or an error, which RFC 9309 treats as a full disallow (robots_unavailable).
  • 4disallowed the crawler in robots.txt. The merchant said no, so the scan stopped (robots_disallowed).
  • 3were blocked outright by bot management on first contact (blocked_by_waf).
  • 2redirected somewhere the scan would not follow (redirect_blocked).
  • 1returned an HTTP error (http_error).

A storefront that blocked the crawler is undetermined and is never counted as having no deposit option. That is the whole point of keeping a third value: the alternative silently converts “we could not look” into “there was nothing there”, in the direction that flatters the headline. The aggregator refuses to emit a summary whose buckets do not reconcile against the row count, and a test fails if that ever changes.

Every figure, and where it comes from

Each number on this page is read from the machine-generated summary the scan produces, which is aggregate-only by design and names no storefront. The third column is the field it came from, so you can check the page against the artifact rather than trusting the prose. Every field in that summary also carries its own derivation — the predicate over the raw file that produces it — and the builder throws rather than emit a number with no stated derivation.

DepositDesk market scan, frame mto-2026-08-09, summary generated 10 August 2026.
FigureValueField
Storefronts in the frame120population.totalRows
Confirmed Shopify storefronts55population.shopifyStores
Of those, deposit option determinable54headline.determinateDenominator
No deposit option found52headline.noDepositCount
Deposit option found2headline.offersDepositCount
No-deposit share of those 5496.3%headline.noDepositPct
Deposit share of those 543.7%headline.offersDepositPct
Not a Shopify storefront32population.notShopify
Undetermined, all causes34classifications.INDETERMINATE
Undetermined among confirmed Shopify storefronts1 (1.8%)headline.indeterminateAmongConfirmedShopify(Pct)
Never confirmed as Shopify in either direction33 (27.5%)population.fingerprintUnconfirmed
Product pages read successfully658population.totalProductsSampledOk
Median product pages read per storefront12population.medianSampleDepth
Product pages requested per storefront12inputs.sampleRequestedValues
Clean sample required for a no-deposit verdict8inputs.minSampleForNegativeValues
Smallest denominator the tool will quote a percentage for20inputs.minBucketN
Storefronts carrying any Shopify selling-plan group2population.storesWithAnySellingPlanGroup
Deposit selling-plan groups seen in total9sellingPlanGroups.deposit
Subscription selling-plan groups seen in total0sellingPlanGroups.subscription
Storefronts whose sample lost at least one product1population.storesWithIncompleteSample
Summary generated2026-08-10generatedAt

The summary is aggregate-only and I am happy to send it to anyone who wants to check the arithmetic — email support@depositdesk.app. The raw per-storefront file holds domains so a run can be audited, and it is not published.

How I measured this

Everything below uses public, unauthenticated endpoints that a storefront serves to any visitor. Nothing was bought, no account was created, no cart or checkout was touched, and no merchant was contacted. Per storefront, in order:

  1. 1

    GET /robots.txt

    Parsed properly — group selection, longest-match, $ anchors, Crawl-delay. A storefront that disallows the crawler becomes undetermined, not silently skipped. A 4xx is treated as allow-all per RFC 9309; a 429, a 5xx or a network failure is treated as a full disallow, because a host that is rate-limiting has just asked to be left alone.

  2. 2

    GET / — establish the platform, positively

    Shopify is asserted only when the storefront identifies itself: a powered-by: Shopify response header, an x-shopid or x-shardid header, or a /products.json body of Shopify shape. Not a Shopify storefront is asserted only when something else identifies itself instead — another storefront platform named in the headers or the markup. A missing header corroborates nothing, and nginx, Apache and Cloudflare are not platform evidence. When neither direction answers, the storefront is undetermined.

  3. 3

    GET /products.json?limit=250

    The public catalogue endpoint, read for a price profile: median, 90th-percentile and maximum variant price, and a product count. A catalogue body too large to read is undetermined, never read as evidence of no deposit — the biggest catalogues in any frame are exactly the ones that would vanish.

  4. 4

    GET /products/{handle}.js, twelve times

    The deposit signal. Twelve product pages per storefront, ranked so that pre-order, made-to-order, custom and bespoke handles are requested first, then the most expensive. 658 product pages were read in total, a median of 12 per storefront.

The signal

A deposit implemented through Shopify appears in the selling_plan_groups array of /products/{handle}.js. The discriminator inside it is selling_plans[].recurring_deliveries: a deposit plan is false, a subscription plan is true. That was established against live storefronts running Downpay for deposits and Recharge for subscriptions. A deposit plan’s price_adjustments array is empty where a subscription’s is populated — and price_adjustments is not the deposit percentage. That assumption was tested against live storefronts and is false.

A deposit verdict needs two independent signals, never one: every recurring_deliveries false, and either a known deposit app id or an explicit payment-split phrase with no veto phrase present.

The bare word “deposit” is not a payment-split phrase. On its own it reads a drinks retailer’s returnable bottle deposit as a purchase deposit, and a bare “remaining balance” reads a try-before-you-buy plan — which charges nothing today — as one. Both were confirmed misclassifications against the real classifier, not hypotheticals. A split phrase has to state the split: 50% deposit, deposit of 30%, pay 30% now. Four vetoes each closed a confirmed false positive:

  • A proximity veto rather than a phrase list. A deposit word within one sentence of held, returned, returnable, refundable, rental, hire, damage, cleaning, breakage, per bottle/case/keg forces an ambiguous verdict, in either word order. The old fixed list was defeated by three ordinary merchant sentences about returnable bonds and hire fees, all of which returned a deposit.
  • A shipping balance is not a balance on the goods. “Shipping balance due before dispatch” is a full-price pre-order billing postage later.
  • A later balance alone is not a deposit. It states only the second half of a split, which is equally layaway, an invoice, or charging on dispatch. It now requires a term naming a partial payment somewhere in the same group.
  • Pay-in-full wording vetoes — but only conditionally. A pre-order plan that charges the full price is field-for-field indistinguishable from a deposit plan in this endpoint: same recurring_deliveries: false, same empty price_adjustments. Only the merchant’s wording separates them. But a Shopify purchase-options group carries the pay-in-full plan and the deposit plan together, so vetoing on any pay-in-full phrase threw away genuine deposits — and that costs more than coverage, because a lost storefront leaves the denominator and pushes the published no-deposit share up. So full price vetoes only when it is the only structure described.

Three values, not two

Every storefront lands in one of four states, and the third is the one that keeps the headline honest.

  • Offers a deposit — a deposit selling plan was found on a sampled product.
  • No deposit — no deposit plan on a sample that was both deep enough (a floor of 8 products) and complete. A handle requested and not returned — a 404, a block, a timeout, a robots.txt rule — is disproportionately the one that would have proved a deposit, because the sampler asks for deposit-likely handles first. So a storefront that lost any product cannot earn this verdict.
  • Undetermined — everything else: robots.txt said no, robots.txt could not be reached, the platform could not be established either way, the crawler was blocked, the catalogue was too large to read, the sample was too thin or incomplete, or the selling plan could not be confidently named.
  • Not Shopify — another platform identified itself. Never inferred from a missing header.

The headline denominator is the first two states only. Undetermined storefronts are reported beside it, in their own counts, and are never folded in.

Manners

  • An honest, descriptive user agent with a contact URL — DepositDeskResearchBot/1.0.0 (+https://depositdesk.app/support). No browser impersonation: a storefront that blocks bots should count as undetermined, not be tricked into answering.
  • robots.txt obeyed, including Crawl-delay, and including the cases where obeying it costs the storefront entirely.
  • One request in flight per host, at least a second between them, a 20-second timeout, at most two retries, and a 403 never retried. A host asking for a delay longer than a minute is abandoned rather than knocked on again.
  • Read-only GETs. Cart, checkout, account, admin and app paths are refused at the HTTP client itself, so no code path can reach them. Redirects are followed by hand, capped at three hops, with every hop target checked.

What the controls proved

Before measuring anything, the detector is run against storefronts whose answer was established independently. A run cannot certify itself: the validation flag depends only on a separate controls report, dated and matching the method version, because in the false-positive case the run’s own deposit count is the bug. On this method version, every control behaved:

  • 4 of 4 storefronts known to sell deposits through a selling-plan deposit app were detected, twice over: 4 of 4 with the known deposit product pinned into the sample, which tests the classifier, and 4 of 4 through the ranked sampler with nothing pinned, which tests whether a real run would ever find it. Only the second one certifies the pipeline.
  • 4 of 4 storefronts known to sell at full price only came back as no-deposit. One of them carries live subscription plans and is the false-positive trap.
  • 3 of 3 known-undetermined storefronts came back undetermined: a headless storefront that passes the header check and 404s on the product endpoint; a storefront that rate-limits on first contact, including on robots.txt, so it cannot even be asked; and a storefront whose pre-order plan charges the full price, which is what makes the two-signal rule load-bearing.
  • 1 blind-spot storefront — known to take deposits through an app that does not use selling plans — came back as no-deposit, exactly as documented. That is not scored as a pass. It is the measured false-negative floor, and it is the reason the headline is a ceiling.

The unit tests classify recorded real responses from those same storefronts rather than hand-written shapes, so the boundary cases cannot quietly stop being tested. Method version recorded in the summary as 2026-08-09+5+selling-plan-groups+recurring-deliveries+two-signal-text+full-price-veto+complete-sample+proximity-veto+corroborated-fingerprint+deposit-split-outranks-pay-in-full+named-platform-header.

What this doesn’t tell you

This is the part of the page I would read first if someone else had written it.

Anything about Shopify as a whole

The frame is 120 storefronts I chose. A percentage taken from it describes those storefronts and no others. There is no version of this page on which 96.3% becomes a fact about Shopify merchants.

Whether a storefront can take deposits

Deposits are configured per product, not per store. No deposit found means no deposit option on the twelve products sampled — never that the storefront is unable to take one, and never that it has decided against one.

Deposits taken without Shopify selling plans

The scan reads Shopify's selling-plan data. A deposit app that vaults a card and charges the balance later without selling plans is structurally invisible to it. PreProduct works this way and is confirmed invisible. More sampling does not fix this; it is a floor, not a sampling error.

Deposits taken off the storefront

A phone call, an emailed invoice, a bank transfer, a showroom visit, a request-a-quote form. Plenty of made-to-order businesses take deposits this way, and none of it appears in a product endpoint.

Why any storefront does what it does

The scan reads structure, not intent. A storefront with no deposit option may have weighed one and declined, may take deposits some other way, or may not know the option exists outside Shopify Plus. This measurement cannot tell those apart.

Anything comparable across currencies

products.json does not state a currency, so the price bands are in each storefront's own units and only approximately comparable.

Which storefronts these were

By construction. The projection into the published summary has no domain field, and a guard walks the finished summary — keys and values — and refuses to emit it if anything resembles a hostname, URL, email address or phone number. The raw file keeps domains so the run can be audited, and it is not published. No merchant named here, ever.

What would make it stronger

Three things, in order of how much they would change what can honestly be claimed.

  • A bigger frame, drawn at random. A thousand or more storefronts sampled from a source that was not assembled by hand would let a figure like this describe a population instead of a list. That single change matters more than everything else here combined, and it is the reason the convenience-sample caveat sits next to the number rather than underneath it.
  • A second detection path for card-vaulting deposit apps. Right now the false-negative floor is disclosed rather than closed. Closing it would turn the ceiling into something much closer to an estimate.
  • Somebody else running it. An independent replication is worth more than either of the above. The method is described above in enough detail to rebuild, and the endpoints are public.

Why I ran it

I build a Shopify deposits app, so read all of this with that in mind. DepositDesk adds a deposit option and automatic balance collection to a standard Shopify plan for a flat $29 a month. That is the commercial interest behind the scan, and it is the best reason to be sceptical of it — which is most of why the method, the controls and the measured false-negative floor are on this page instead of in a footnote.

What the scan measured, it reports. Where it could not measure, it says so, and those storefronts leave the denominator rather than being counted as a no. If you find an error in the arithmetic or the method, I would rather hear it than not: support@depositdesk.app.