Read-only extraction of a Product Hunt product's current launch status, upvote count, leaderboard rank, and comment count. The result is a point-in-time snapshot because engagement metrics change during and after launch.
Use Cases
- Checking whether a Product Hunt product is live/launched or otherwise marked with a launch status.
- Retrieving the current upvotes, Product Hunt rank, and comments for a product.
- Looking up a product when the caller has a Product Hunt slug or canonical product URL.
- Resolving a product name to its canonical
/products/{slug}URL before extraction. - Reading a launch supplied as the legacy or launch-page URL
/posts/{slug}.
Automation Flow
- Resolve the shortest direct URL available. If the caller supplies a canonical Product Hunt URL, use it unchanged; both
https://www.producthunt.com/products/{slug}andhttps://www.producthunt.com/posts/{slug}are valid launch-page forms. If the caller supplies a slug and it is explicitly known to be a launch-page slug, usehttps://www.producthunt.com/posts/{slug}; otherwise usehttps://www.producthunt.com/products/{slug}. Do not guess an opaque slug from a product name. - If only a product name is supplied, resolve it with Product Hunt search. Prefer
https://www.producthunt.com/search/launches?q={query}; if that route does not render usable results, usehttps://www.producthunt.com/search?q={query}. Select the matching result and read its/products/{slug}or/posts/{slug}href before navigating to that page. Match the product name/tagline rather than assuming a slug from the name. - In a stealth browser session, navigate directly to the resolved URL, solve the Cloudflare challenge if presented, wait briefly for progressive rendering, and run the evaluator below on the loaded page. Batch navigation, challenge solving, waiting, and evaluation in one browser-agent call where supported.
(() => {
const clean = s => (s || '').replace(/\s+/g, ' ').trim();
const num = s => {
const m = clean(s).match(/\d[\d,]*/);
return m ? Number(m[0].replace(/,/g, '')) : null;
};
const body = clean(document.body.innerText);
const h1 = [...document.querySelectorAll('h1')].find(e => clean(e.innerText));
const name = h1 ? clean(h1.innerText) : null;
const canonical = document.querySelector('link[rel="canonical"]')?.href || location.href;
const productUrl = [...document.querySelectorAll('a[href]')]
.map(a => a.href)
.find(u => /\/(?:products|posts)\/[^/?#]+/.test(u)) || canonical;
const main = document.querySelector('main') || document.body;
const controls = [...main.querySelectorAll('button,[role="button"]')];
const upvoteControl = controls.find(e => /upvote|up-vote/i.test(
clean(e.getAttribute('aria-label')) + ' ' + clean(e.innerText)
));
const upvotes = upvoteControl
? num(upvoteControl.innerText + ' ' + (upvoteControl.getAttribute('aria-label') || ''))
: num((body.match(/([\d,]+)\s*(?:upvotes?|votes?)/i) || [])[1]);
const rankMatch = body.match(/(?:rank(?:ed)?|position|#)\s*#?\s*(\d{1,4})\b/i) || body.match(/#(\d{1,4})\b/);
const rank = rankMatch ? Number(rankMatch[1]) : null;
const commentMatch = body.match(/([\d,]+)\s+comments?\b/i);
const commentLink = [...main.querySelectorAll('a[href],button,[role="button"]')].find(e =>
/comment|discussion/i.test(clean(e.innerText) + ' ' + (e.getAttribute('aria-label') || ''))
);
const comments = commentMatch
? Number(commentMatch[1].replace(/,/g, ''))
: (commentLink ? num(commentLink.innerText) : null);
const statusMatch = body.match(/\b(Launched|Launching|Coming soon|Live|Ended|Not launched)\b/i);
const status = statusMatch ? statusMatch[1] : null;
return { name, url: productUrl, status, upvotes, rank, comments };
})()- Return the evaluator's object as the result. Preserve
nullfor a metric that is not rendered rather than inferring it from unrelated product cards or search results.
Possible Friction Points
- Product detail pages use
https://www.producthunt.com/products/{slug}; launch pages may also use the durable-lookinghttps://www.producthunt.com/posts/{slug}form. Prefer a supplied canonical URL and do not invent a slug. - Product names do not always uniquely or trivially determine the slug; resolve the
/products/{slug}or/posts/{slug}href from search when the slug is unknown. The launches search route ishttps://www.producthunt.com/search/launches?q={query};/search?q={query}is a fallback. - Product Hunt may present a Cloudflare managed challenge. Use a stealth browser session and invoke the browser's Cloudflare solver before extraction; wait for the page to render, and reject a result whose title or body is still
Just a moment.... - Upvotes, rank, and comments are live counters and can change between page loads. Treat the output as a snapshot with no historical guarantee.
- Do not count engagement controls from recommendation cards. The evaluator scopes controls to
mainwhen available and prefers the control explicitly labeledUpvote. - Rank or launch status may be absent for products that did not participate in a ranked launch; return
nullrather than guessing. - The page may contain related products and multiple links to the same product. Use the page heading and canonical/product URL, not a recommendation card, as the identity.
- Search may require an additional Cloudflare solve and a short wait before results render. Navigate directly to the search URL rather than visiting the homepage first.