Update, same day. This post measured 8 search terms. I have since run the same
method over 100, with 715 unique products, and two of the conclusions here do not
survive the bigger sample. Most niches are not dead — the median first page carries
157 ratings, and only 7 terms of 101 came back with 5 or fewer. And "the biggest
sellers are all $0+" is wrong: free products dominate download counts, but the
median price of a product ranking on page one is $45. The full data, the caveats
and the free CSV are in
I measured 100 digital-product niches.
I am leaving this post up unedited below, because being wrong at n=8 and finding out
at n=100 is the actual lesson.
I spent a day building nine digital products for niches I picked by counting competitors. Then I measured the niches properly and found out most of them were empty rooms.
Here is the data, the script that produced it, and the two things it changed about how I'd pick a niche.
The wrong number
The standard advice is to find a niche with low competition. So you search the marketplace, count the results, and pick the term with the fewest.
That number cannot tell you what you need to know. A search term with two products can be uncontested because nobody sells there — or because nobody buys there. Those are opposite situations and the competitor count looks identical in both.
The number that distinguishes them is ratings on the products that already rank. A rating is left by a buyer. No ratings across the top of a search means no buyers reached the top of that search.
The script
Search Discover, open the products that rank, pull their public listing JSON, read price, rating count and sales count.
const { chromium } = require('playwright');
async function scan(browser, term) {
// No custom user agent. With one set, Discover returns a page with no
// product links and every measurement comes back zero.
const page = await browser.newPage({ locale: 'en-GB', viewport: { width: 1400, height: 1000 } });
await page.goto('https://gumroad.com/discover?query=' + encodeURIComponent(term),
{ waitUntil: 'domcontentloaded' });
// Cards render after first paint. At 2.5s they are not there yet.
await page.waitForTimeout(4500);
await page.evaluate(() => window.scrollTo(0, 1200));
await page.waitForTimeout(1200);
const links = await page.evaluate(() =>
Array.from(new Set(Array.from(document.querySelectorAll('a[href*="/l/"]'))
.map(a => a.href))).slice(0, 8));
const rows = [];
for (const link of links) {
// Fetched from Node, not from inside the page: every seller lives on their
// own subdomain and the browser blocks it as cross-origin.
const res = await fetch(link.split('?')[0] + '.json',
{ headers: { 'User-Agent': 'Mozilla/5.0' } });
if (!res.ok) continue;
const r = await res.json();
rows.push({
name: r.name,
price: r.price_formatted,
ratings: r.ratings?.count ?? 0,
sales: r.sales_count ?? '-',
});
}
await page.close();
return rows;
}
Appending .json to any public Gumroad product URL returns the listing data. That is the whole trick.
What it found
| Search term | Products | Ratings across the top 8 | Largest sales count seen |
|---|---|---|---|
| digital planner | 5,461 | 459 | 27,700 |
| chatgpt prompts | 4,778 | 304 | 2,675 |
| claude code | 1,114 | 338 | 5,145 |
| freelance tools | 843 | 205 | 539 |
| resume template | 1,892 | 159 | 735 |
| budget tracker | 2,114 | 86 | 427 |
| net worth tracker | 257 | 14 | 925 |
| bookkeeping spreadsheet | 46 | 1 | 11 |
| etsy seller bookkeeping | 2 | 0 | — |
| construction job costing | 7 | 2 | — |
The bottom three are the ones I had picked, by counting competitors. Two competitors and zero ratings looked like an open goal. It was an empty stadium.
Two things this changed
1. Free is the distribution mechanism, not the opposite of one
Look at what is attached to the biggest numbers in that table:
- Second Brain Template — $0+, 27,700 sales, 373 ratings
- A Claude workflow guide — $0+, 5,263 sales
- A macOS utility — $14+, 5,145 sales
- A Blender add-on — €6+, 1,299 sales
The pattern is pay-what-you-want with a low or zero minimum. On a marketplace where nobody has heard of you, the download is the thing you are competing for, and a price is a wall in front of it. The revenue comes from the fraction who pay anyway, multiplied by a volume you cannot get any other way.
A paid product with no ratings does not move. Which brings up the part I did not know when I started.
2. New sellers are not in Discover at all
After nine products and a day of confusion about zero sales, I checked properly instead of assuming. I searched for phrases unique to my own listings:
-
markup vs margin→ 8 products, none mine, and mine is literally about that -
construction job costing→ 7 products, none mine - the category browse page → 44 products, none mine
- my own brand name → nothing
Nine storefronts, invisible since the minute they went up. Every conclusion I had drawn about pricing and niches was about a market I was not in.
If you are launching on Gumroad, verify you are actually in the index before you interpret any of your numbers. Search a phrase that is unique to your listing. If it does not come back, nothing else you measure means anything yet.
The honest ending
I do not have a resolution for that last part. I have a store, payment processing, nine products and no traffic, and the marketplace is not going to give me any until something outside it does first.
What I do have is the measurement I should have run on day one, before building anything. It takes ten minutes and it would have saved me the day.
The script is above and it works standalone — paste it, add a main that loops over your candidate terms, and run it before you build. If you want the packaged version with the publishing tooling I wrote alongside it, it is here, pay what you want including nothing.
Top comments (0)