TL;DR: Day 60 of an indie iOS dev experiment. Yesterday: 3 Gumroad SKUs LIVE. Today: 2 more (ASC API Toolkit $499, AutoApp Dashboard $39). Total catalog: $572. The bottleneck wasn't writing the products — it was the Gumroad bureaucracy. Browser automation cut that to 90 seconds per SKU.
The pile-up
When you've been writing a product for 6 weeks and finally have the PDF + cover + description ready, the LAST thing you want is a 20-step Gumroad checkout-to-publish flow. But that's exactly what Gumroad asks:
- Click "New product"
- Pick "Digital product" radio
- Set currency to USD
- Fill name + price
- Click "Next: Customize"
- Wait for product edit page to load
- Upload PDF (drag-drop or file picker)
- Wait for upload
- Fill description (contenteditable, no plain textarea)
- Click "Save and continue"
- Add cover image
- Set tags
- Configure pricing tiers
- Set up upsells
- Save again
- Open Share tab
- Generate permalink
- Set up URL slug
- Click "Publish"
- Verify the public URL
For one SKU, fine. For 5 in a launch wave, that's 100 clicks, 50 wait moments, 10+ context switches. I'd rather automate.
What I built
I have a gumroad_uploader.py script (~700 lines, MIT) that drives Gumroad via Chrome DevTools Protocol + Playwright. The script encodes the 20-step flow as a typed ProductSpec dataclass and walks through it.
@dataclass(frozen=True)
class ProductSpec:
slug: str
permalink: str
name: str
description: "str"
price_usd: float
cover_path: Path
file_path: Path
is_bundle: bool = False
tags: tuple[str, ...] = ()
For each spec, the script:
- Navigates to /products/new (uses persistent Chrome session = logged in once)
- Clicks "Digital product" radio
- Fills name + price
- Clicks "Next" — waits for /products//edit URL
- Uploads PDF via file input
- Fills description (Gumroad uses
[aria-label="Description"]contenteditable) - Clicks "Save and continue"
- (Manual: cover image — Gumroad rejects programmatic file uploads to the cover slot)
- Verifies public URL HTTP 200
For the 2 SKUs I shipped today (ASC API Toolkit + AutoApp Dashboard), this took ~90 seconds each. Total wall-clock for both: 3 minutes.
What works
1. Persistent Chrome session
Use --user-data-dir=/path/to/persistent when launching Chrome with --remote-debugging-port=9222. Login once, the session persists across restarts. This is critical because Gumroad's anti-bot increasingly sees fresh sessions as suspicious.
chrome.exe \
--remote-debugging-port=9222 \
--remote-allow-origins=* \
--user-data-dir="$HOME/.gumroad-cdp-profile" \
--no-first-run --no-default-browser-check
2. Multi-selector resilience
Gumroad's React UI changes selectors across releases. My script tries multiple selectors per field:
SELECTORS = {
"name_input": [
"LABEL:Name", # Playwright's get_by_label
'input[name="name"]',
'input[aria-label="Name"]',
'input[placeholder*="Name"]',
'input[id*="name"]',
],
...
}
The custom LABEL: prefix uses page.get_by_label() which is the most stable across Gumroad's redesigns.
3. The "Next" button needs a real submit
The "Next: Customize" button looks like a button but actually triggers a React form submit. If you click it programmatically without focus on the price field, the form doesn't submit. Fix:
price_input.click()
price_input.press("End")
next_btn = page.locator('button[type="submit"]:has-text("Next")').first
next_btn.click()
4. Description editor is contenteditable, not textarea
Gumroad's description field looks like a textarea but is actually [aria-label="Description"] contenteditable. Use Playwright's .fill() (not direct value set):
desc = page.locator('[aria-label="Description"]').first
desc.click()
time.sleep(0.5) # let cursor settle
desc.fill(description)
What doesn't work (yet)
1. Cover image upload
Gumroad's cover image slot has additional drag-drop validation that rejects Playwright's set_input_files(). I haven't found a reliable way around this. Cover image is the only step still manual.
2. Bundle product picker
When creating a Bundle SKU, the "Add products to bundle" picker only shows products that have been published 24+ hours ago. New SKUs from the same launch session don't appear in the picker due to indexing delay.
I created a Bundle SKU draft today ("Indie Hacker 2026 Toolkit") — it's parked, will add yesterday's 3 SKUs to it tomorrow once they index.
3. Affiliate enable for new SKUs
Same indexing delay. New SKUs don't show in the affiliate program product picker for 24h. Setup affiliates with existing SKUs first, add new ones the next day.
Why this matters at indie scale
The unit economics:
- Manual upload time per SKU: 15-25 minutes (with reading + filling + verifying)
- Automated time per SKU: 90 seconds + cover image (~1 min manual)
- Time saved per SKU: ~15 minutes
- Time saved on a 5-SKU launch wave: 75+ minutes
That's not "more SKUs faster." That's "more SKUs feasible." When manual cost is 15 min, you ship 1-2 SKUs per session. When automated cost is 1.5 min, you ship 5+ in the same focus block. The catalog grows faster.
The 5 SKUs LIVE today (Day 60)
For context, here's what's in my Gumroad catalog after this wave:
- iOS Indie Launch Playbook ($19) — 50pp PDF
- 30-Day B2B Cold Email Templates ($15) — 25 templates × 5 ICPs
- 14 iOS Rejection Reasons (FREE) — lead magnet
- NEW ASC API Toolkit ($499) — 60+ Python scripts
- NEW AutoApp Dashboard ($39) — manifest-driven indie tool stack
Plus an affiliate program at 30% commission (writeup).
Source
The actual gumroad_uploader.py: github.com/jiejuefuyou (MIT). 700 LOC, no dependencies beyond playwright. Tested against Gumroad's UI as of 2026-05-07.
If you're shipping multiple Gumroad SKUs in 2026 and want the proven automation playbook + the 50pp PDF on iOS launches: iOS Indie Launch Playbook covers the full 60-day toolkit.
Top comments (0)