Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Firebase does not include a native HTML-to-PDF API. The dependable design is to send an authenticated request to server-side code, populate a controlled HTML template, render it with headless Chrome through Puppeteer or Playwright, and return the PDF or store it in Cloud Storage. You can run that renderer in Cloud Functions for Firebase or in a custom Cloud Run container, then optionally route traffic through Firebase Hosting.

The architecture that works

Treat Firebase as the authentication, API, hosting and storage layer—not as the PDF rendering engine. A typical request flows like this:

  1. Your app sends an authenticated request containing an invoice ID or other safe data reference.
  2. Server-side code verifies the user and loads the authoritative data. Do not trust totals, permissions or template variables supplied by browser JavaScript.
  3. The server fills a controlled HTML template and escapes inserted text.
  4. Puppeteer or Playwright launches headless Chrome and calls the browser’s PDF function.
  5. The endpoint either streams the PDF response or writes it to Cloud Storage and returns an authorized download URL.

Google Cloud’s Cloud Run browser-automation guidance explicitly lists headless Chrome for creating PDFs or screenshots of web pages and identifies Puppeteer and Playwright as high-level control libraries. The PDF is therefore produced by browser rendering in your backend, not by Firebase Hosting.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose Cloud Functions or Cloud Run

Both backends can serve an HTTPS endpoint, but they suit different operational needs.

Decision point Cloud Functions for Firebase Cloud Run
Firebase integration Closest integration with Firebase CLI, Authentication and event-driven functions. Can still use Firebase services, but you deploy and operate a container.
Runtime control Use a supported runtime and its execution constraints. Select the base image, runtime, browser packages and operating-system dependencies.
Browser dependency management Convenient for a small Node.js function, but browser binary size and runtime limits must fit the supported environment. Best when Chromium needs custom packages, fonts, launch flags or a non-Node runtime.
Firebase Hosting route Hosting can route HTTPS requests to the function. Hosting can route HTTPS requests to the service.
Hosting timeout Requests routed through Hosting have a documented 60-second timeout. The same 60-second Hosting route timeout applies.

When Functions is the better start

Choose Cloud Functions when your application is already organized around Firebase CLI deployments, the renderer is modest, and your supported runtime can accommodate the browser dependency. The Firebase getting-started material states that production deployment requires the Blaze plan, so confirm the current plan, runtime support and limits before deploying.

When Cloud Run is the better fit

Use Cloud Run when you need a custom container, browser binaries, OS-level libraries, predictable fonts, a runtime other than Node.js, or finer control over memory and concurrency. Containerizing Chromium also makes it easier to reproduce the same environment locally and in production.

Do not hide long jobs behind Hosting

A large document, slow remote asset or cold browser can exceed the documented 60-second Firebase Hosting route timeout. For those jobs, call the backend directly where appropriate or create an asynchronous job: accept the request, render in the background, save the PDF, and notify the client when an authorized download is ready. Verify the direct service’s current limits rather than assuming the Hosting limit applies—or does not apply—to every route.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a secure HTML template

Keep templates under your control

Store templates with your server code or in a private, versioned location. Do not accept arbitrary HTML, CSS, file paths or destination URLs from an untrusted client. A renderer that can fetch arbitrary URLs may be abused to probe internal services or exfiltrate data.

Validate and escape values

Accept an identifier such as invoiceId, load the invoice on the server, check authorization, and derive the amount, customer and line items there. Escape text before inserting it into HTML. If a field genuinely supports rich markup, sanitize it with a narrowly defined allowlist instead of concatenating raw input.

Make assets available to Chrome

Fonts, logos, images and stylesheets must be reachable from the rendering environment. Prefer bundled or authenticated assets over fragile third-party URLs. Wait until fonts and images have loaded before exporting; otherwise the PDF can contain fallback fonts, blank image boxes or shifted page breaks.

Minimal Cloud Functions implementation

The following example is an implementation pattern, not a turnkey official Firebase recipe. It uses an HTTPS function, a server-side template, and Puppeteer. Pin compatible package versions and verify the currently supported Node.js runtime before deployment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { onRequest } = require("firebase-functions/v2/https");
const puppeteer = require("puppeteer");
const escapeHtml = require("escape-html");

exports.invoicePdf = onRequest(async (req, res) => {
  if (req.method !== "POST") return res.status(405).send("POST required");
  // Verify Firebase Authentication here; reject unauthenticated callers.
  const { invoiceId } = req.body || {};
  if (!/^[A-Za-z0-9_-]{1,80}$/.test(invoiceId || "")) {
    return res.status(400).send("Invalid invoiceId");
  }

  // Load and authorize the invoice from your database.
  const invoice = await loadAuthorizedInvoice(invoiceId, req);
  if (!invoice) return res.status(404).send("Not found");

  const rows = invoice.items.map(item =>
    `<tr><td>${escapeHtml(item.name)}</td>` +
    `<td>${escapeHtml(String(item.quantity))}</td>` +
    `<td>${escapeHtml(item.totalFormatted)}</td></tr>`
  ).join("");
  const html = `<!doctype html><html><head>
    <meta charset="utf-8">
    <style>@page { size: A4; margin: 18mm; } body { font: 12pt Arial; }</style>
    </head><body>
    <h1>Invoice ${escapeHtml(invoice.number)}</h1>
    <p>${escapeHtml(invoice.customerName)}</p>
    <table>${rows}</table>
    </body></html>`;

  const browser = await puppeteer.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: "networkidle0" });
    await page.evaluate(() => document.fonts.ready);
    const pdf = await page.pdf({ format: "A4", printBackground: true, preferCSSPageSize: true });
    res.set("Content-Type", "application/pdf");
    res.set("Content-Disposition", `attachment; filename="invoice-${invoice.number}.pdf"`);
    return res.status(200).send(pdf);
  } finally {
    await browser.close();
  }
});

In production, replace loadAuthorizedInvoice with your database access and Authentication checks, keep secrets in the server environment, and consider writing large PDFs to Cloud Storage instead of holding every result in the response process.

Cloud Run container considerations

A Cloud Run service packages your application and Chromium into an image. Choose a maintained Node.js or other base image, install the browser dependencies required by your chosen automation library, and expose an HTTP server on the port supplied by the platform. Set memory and concurrency from measurements on your actual templates. A browser per request is simple but expensive; a carefully managed browser or page pool can improve throughput, while excessive concurrency can exhaust memory. Close pages and browsers on success, timeout and error paths.

PDF quality and template details

Page size, margins and breaks

Use CSS @page rules and the PDF options together. Decide whether CSS controls the paper size (preferCSSPageSize) or the API option does, then test both portrait and landscape templates. Add explicit page-break rules around invoices, tables and signatures. Long tables need repeated headers and a policy for rows that cannot be split.

Images and fonts

Use absolute, reachable asset URLs or inline small assets. Wait for network idle and document.fonts.ready, but do not rely on network idle alone when an application keeps analytics connections open. A local font package or a stable internal asset endpoint avoids changes caused by external font providers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Dynamic pages

If the template is a client-rendered application, wait for a specific readiness selector rather than exporting immediately after navigation. For a static server-generated template, page.setContent is usually simpler and avoids exposing an unnecessary public URL to the renderer.

Returning or storing the file

Streaming is appropriate for small, interactive documents. For lengthy reports or jobs that may exceed a request timeout, create a job record, render asynchronously, save the PDF to a private Cloud Storage bucket, and return a short-lived authorized download path. Never make a bucket public merely to simplify downloads. Include a document ID and status endpoint so clients can retry safely without creating duplicate invoices.

Or skip the browser setup

ScreenshotNeo is a hosted screenshot and PDF API. It can accept a URL and return a PDF, so it is useful when your HTML template is already served from an authenticated or signed route and you do not want to package Chromium yourself. It also removes cookie banners, newsletter popups and chat widgets before capture; bot checks, blank pages and failed loads are not billed; and its MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf.

For a public or signed template URL, call the API as documented at ScreenshotNeo’s documentation:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The examples use the service’s default image output; request PDF output and other capture options through the documented parameters. ScreenshotNeo’s Free plan includes 1,000 shots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try the hosted route.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

The function deploys but Chromium will not start

The browser binary or required OS libraries do not match the runtime. Pin a compatible automation-library version, use the supported browser installation method, or move to a Cloud Run image that explicitly installs Chromium and its dependencies. Avoid adding launch flags blindly; use only those required by the container’s security model.

The PDF is blank or missing data

The export ran before client rendering completed, the template threw a browser error, or data was inserted into the wrong document. Log the response status, capture browser console errors, wait for a readiness selector, and verify the generated HTML in a local browser.

Images or fonts are missing

Check that the renderer can resolve every URL without browser-only credentials, wait for image completion and fonts, and inspect network failures. Bundle critical assets or expose them through a controlled signed endpoint.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Requests time out at 60 seconds

If the request passes through Firebase Hosting, the documented route timeout is 60 seconds. Reduce asset and template work, call the backend directly where suitable, or switch to an asynchronous job that stores the completed PDF.

Users can alter invoice totals

Do not use client-submitted totals as authoritative values. Send an ID, re-read the record server-side, authorize it for the caller, calculate totals on the server and escape every inserted text value.

Retries create duplicate documents

Give each request an idempotency key or deterministic job ID. Record the rendering state before starting, reuse a completed object on safe retries, and clean up partial files after failures.

Testing and operating the renderer

  • Test representative short and long documents, large tables, images, page breaks, Unicode characters and right-to-left text where applicable.
  • Measure cold-start and warm-render times, memory consumption, browser crashes and concurrent requests on the exact templates you ship.
  • Set explicit navigation, asset and overall job timeouts; log document IDs rather than sensitive customer data.
  • Retry transient browser or storage failures with limits, but do not retry validation or authorization errors.
  • Check current Firebase, Cloud Functions, Cloud Run and Puppeteer or Playwright runtime guidance before each production upgrade.

There is no universal per-document price or performance number for this workflow. Your bill and throughput depend on runtime, memory, invocation duration, storage and workload, so measure them with production-shaped documents instead of assuming a benchmark.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can Firebase Hosting itself convert HTML into a PDF?

No. Hosting can serve files and route requests to a function or Cloud Run service; a server-side browser renderer must create the PDF.

Should I use Puppeteer or Playwright?

Both are documented high-level browser-control approaches for headless Chrome. Choose based on the browser version, APIs and packaging constraints that fit your selected runtime.

Do I need Cloud Storage for every PDF?

No. Stream small results directly. Storage is useful for asynchronous, large or repeatedly downloadable documents.

What plan is required to deploy production Cloud Functions?

The Firebase getting-started guidance states that production deployment requires the Blaze plan; verify current plan requirements before launch.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.