Compared

Browserless alternative for PDFs and screenshots

Browserless rents you a browser. RenderBadger hands you the finished file. If a render is all you need, that difference is the whole comparison, and Browserless is cheaper only until you count the code you have to keep.

Who this page is for

You wired Puppeteer to Browserless because your app needed an invoice PDF or a screenshot, and it worked. Months later you maintain a browser-session wrapper, a retry loop, a timeout, a place to put the output and a way to hand it to the customer, for what is really one function call. Or a unit bill arrived that was hard to connect to the number of documents you made.

If you use Browserless for scraping, testing, form automation or agents, this page is not for you. Keep it. It is good at that, and RenderBadger does not do it at all.

What each one gives you

Browserless is a hosted Chrome. You connect with Puppeteer or Playwright, drive the page yourself, call page.pdf(), and get bytes back over the socket. Storage, links, retries, idempotency and usage records are your code.

RenderBadger is a rendering API. You POST a URL or HTML with the options you would have passed to page.pdf(), and get the file back, or a signed URL you can hand to a browser or an email. The browser is behind the endpoint and you never touch it.

BrowserlessRenderBadger
What you getA browser sessionA PDF or image file
Code you maintainPuppeteer or Playwright scripts, session handling, retriesOne HTTP request
PDF optionsWhatever you pass to page.pdf()The same options as request fields: format, margins, header and footer templates, page ranges, print media
Screenshotspage.screenshot()Viewport, full page, element, device scale, PNG, JPEG, WebP
Output storageYoursIncluded, with signed URLs and retention by plan
Idempotency and job recordsYoursIdempotency keys, job status, duration and size per render
Async and webhooksYoursQueue a render, get a signed POST when it finishes
Billing unit30 seconds of browser time; a reconnect is a new unitOne render, whatever it takes
Free tier1,000 units, 2 browsers100 renders, 1 concurrent
Scraping, forms, captchas, proxiesYesNo, by design
Session recording and debuggingYesNo
Self-hostingLicensedNo

Pricing at 2,000 and 10,000 renders

Browserless bills in units: one unit is up to 30 seconds of browser time on one connection. A typical invoice renders in three to five seconds, so one render is one unit unless your code reconnects or waits. Prices below are the ones Browserless lists for annual billing; monthly billing costs more and is not published.

Renders a monthBrowserlessRenderBadger
2,000Prototyping, $25, with 20,000 unitsStarter, $19
10,000Prototyping, $25, same planPro, $49

On that table Browserless wins at 10,000 renders, and it would be dishonest to hide it. What the $25 does not include is the wrapper you write and run: connecting, isolating a context per render, waiting for fonts, catching the hung page, retrying the failed one, uploading the output somewhere, signing a link, recording what happened. That is a few hundred lines on day one and a pager on month-end nights. The $24 difference buys you not owning them. If you are happy owning them, Browserless is the better deal.

Prices checked October 26, 2026. Both in US dollars.

Where Browserless is the better choice

Anything beyond a plain render. Scraping a site that fights back, filling forms, driving a flow for a test suite, running an agent, solving captchas, routing through proxies, recording a session to see what went wrong. Browserless has all of it. RenderBadger has none of it, and will not. The same goes for volume: above fifty thousand renders a month, or when you want to self-host, Browserless is the right shape.

Switching

The options keep their Puppeteer names on purpose. What you pass to page.pdf() goes into the request body as it is. Here is a render that is typical of what we see, on both sides.

Browserless with Puppeteer
import puppeteer from "puppeteer-core";

const browser = await puppeteer.connect({
  browserWSEndpoint: `wss://production-sfo.browserless.io?token=${TOKEN}`,
});
try {
  const context = await browser.createBrowserContext();
  const page = await context.newPage();
  page.setDefaultTimeout(30_000);
  await page.setContent(html, { waitUntil: "networkidle0" });
  await page.evaluateHandle("document.fonts.ready");
  const pdf = await page.pdf({
    format: "A4",
    printBackground: true,
    margin: { top: "20mm", bottom: "20mm" },
    displayHeaderFooter: true,
    footerTemplate: footer,
  });
  await context.close();
  const key = `invoices/${invoiceId}.pdf`;
  await s3.putObject({ Bucket, Key: key, Body: pdf });
  const url = await getSignedUrl(s3, new GetObjectCommand({ Bucket, Key: key }));
  // retries, timeouts, and the failed-render log are still to write
} finally {
  await browser.disconnect();
}
RenderBadger
const res = await fetch("https://renderbadger.sh/v1/pdf", {
  method: "POST",
  headers: { authorization: `Bearer ${KEY}`, "content-type": "application/json" },
  body: JSON.stringify({
    html,
    format: "A4",
    margin: { top: "20mm", bottom: "20mm" },
    footerTemplate: footer,
    output: "url",
  }),
});
const job = await res.json(); // { id, status: "done", url, expiresAt, durationMs }

Three things to know when you move:

  1. Fonts are waited for before printing, so there is no document.fonts.ready to call.
  2. A render that takes longer than 30 seconds should be sent with async: true; you get a job id, and a webhook or GET /v1/jobs/:id when it is done. Async needs the Starter plan.
  3. Send an Idempotency-Key header from your own invoice id. A retry after a network error then returns the same render instead of making and billing a second one.

The full option list is in the docs.

Try it on your own invoice

The free plan is 100 renders a month, no card, and the key is live the moment you create it.

Get a free API key