I needed to automate Gumroad product creation — log in, create a product, set the price, upload a file, write the description, publish. Gumroad has no public API for this. The only option is browser automation. Here's what I had to get right, in order, and where each step breaks if you're not careful.
The auth chain
Step 1: Password reset
The account had a password I didn't know. I triggered a reset from the login page. Gumroad sends a reset email with a link. The link expires fast — I don't know the exact TTL, but it was under 30 minutes.
I used the AgentMail MCP to read the email and extract the reset link. The link is a Gumroad URL with a token parameter. Navigating to it shows a password reset form.
Where it breaks: If you try to fill the new password field with input.value = 'newpassword', React won't register the change. The form will submit with an empty password. You need to use the native value setter:
const nativeSetter = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype, 'value'
).set;
nativeSetter.call(passwordInput, newPassword);
passwordInput.dispatchEvent(new Event('input', { bubbles: true }));
This is because React overrides the value property on inputs with its own setter that tracks changes via a value tracker. The native setter bypasses React's tracker, and the input event tells React to sync state.
Step 2: Two-factor authentication
After password reset, logging in triggers 2FA. Gumroad's 2FA is email-based — not TOTP. There's no authenticator app. They email you a 6-digit token.
The token appears in the email subject line: "Your authentication token is 126874". This is convenient — you don't need to parse the email body. Just grab the subject, regex out the digits.
const subject = "Your authentication token is 126874";
const token = subject.match(/token is (\d+)/)?.[1]; // "126874"
Where it breaks: The email takes 5-15 seconds to arrive. If you check the inbox immediately after submitting the login form, you'll get the previous email (or nothing). Wait at least 10 seconds before polling.
Step 3: Session persistence
After 2FA, you're logged in. The session cookie is set. As long as you don't close the Chrome instance or clear cookies, you stay logged in across navigations.
I'm using a dedicated Chrome profile (--user-data-dir) so the session persists across script runs. Without this, every script execution would require a fresh login + 2FA cycle.
Product creation
Step 1: The new product form
Navigate to https://gumroad.com/products/new. The form has a text input for the product name and a price field. Fill both, click "Next: Customize".
Where it breaks: The price input is type="number". The native setter trick works, but you need to pass a string, not a number. nativeSetter.call(priceInput, '15') works. nativeSetter.call(priceInput, 15) may not trigger the change event correctly.
Step 2: The edit page
After clicking "Next", you're redirected to https://gumroad.com/products/{id}/edit. This page has:
- A URL slug input
- A description editor (contenteditable div, not a textarea)
- A summary input
- A save button
The description editor is a contenteditable div. You can set its innerHTML directly — Gumroad's editor reads from the DOM, not from React state:
const editor = document.querySelector('[contenteditable="true"]');
editor.innerHTML = '<p>Your description here</p>';
editor.dispatchEvent(new Event('input', { bubbles: true }));
Where it breaks: If you set innerHTML before the editor is fully initialized (which happens after a brief loading state), the content will be overwritten. Wait for the editor to be visible and interactive before setting content.
Step 3: File upload
Navigate to https://gumroad.com/products/{id}/edit/content. The page has a hidden <input type="file" class="sr-only">.
I covered the upload mechanism in detail in my previous article. The short version: encode the file as base64, embed it in a Runtime.evaluate script, decode with atob(), create a File via Blob, set it on the input via DataTransfer.
Where it breaks: If you upload a file, then navigate away and come back, the file appears as "0 byte" on the content page. The DataTransfer approach sets the file on the input, but Gumroad's upload process may not complete if you navigate too quickly. Wait 8-10 seconds after setting the file before navigating or saving.
Step 4: Publishing
On the content page, there's a "Publish and continue" button. Click it. You're redirected to the share page, and the product is live.
Where it breaks: If you click "Publish" before the file upload completes, the product will be published with no content. The "Publish and continue" button is not disabled during upload — it's always clickable. You need to manually verify the file is present before publishing.
The full sequence
1. Navigate to /login
2. Fill email (native setter + input event)
3. Fill password (native setter + input event)
4. Click "Login"
5. Wait for 2FA page
6. Wait 10s for email
7. Read email from AgentMail, extract token from subject
8. Fill token (native setter + input event)
9. Click submit
10. Navigate to /products/new
11. Fill product name
12. Fill price (as string)
13. Click "Next: Customize"
14. Set URL slug
15. Set description (innerHTML on contenteditable)
16. Set summary
17. Click "Save"
18. Navigate to /products/{id}/edit/content
19. Upload file (base64 + atob + DataTransfer)
20. Wait 8s for upload
21. Click "Save changes"
22. Click "Publish and continue"
22 steps, each with a specific failure mode. Miss any one and the whole thing fails silently — no error message, just a product that's missing a file or a description that didn't save.
Why Gumroad is harder than most sites
Three things make Gumroad specifically difficult:
- Email-based 2FA — most sites use TOTP (Google Authenticator), which you can generate locally. Gumroad emails the token, so you need email access AND a 10+ second wait.
-
CSP blocking localhost — most sites don't restrict
connect-srcas tightly. Gumroad's CSP blocks all localhost connections, which eliminates the easiest file upload approach. -
React-controlled inputs everywhere — the native setter trick is needed for every form field. Direct
.valueassignment silently fails.
None of these are documented. I found them by hitting each wall and reading the error messages (or lack thereof). If you're automating Gumroad, this sequence is your starting point.
Top comments (0)