ScreenshotNeo

BlogHow-to

Create PDF Receipts from HTML for an Indian Online Store

Generate PDF receipts from HTML with Puppeteer or Playwright, control print layout, and understand which GST document fields may apply.

By the ScreenshotNeo team4 October 202612 min read

To create a PDF receipt from HTML, render a prepared HTML document in a browser and use Puppeteer’s page.pdf() or Playwright’s page.pdf(). Set paper size and margins deliberately, add print CSS, and verify the result with long item lists and addresses. First decide which document the transaction requires: a tax invoice, bill of supply, or receipt voucher have different particulars under GST rules. A PDF renderer lays out the data; it does not determine tax treatment.

This guide builds a receipt PDF with Puppeteer, shows the equivalent Playwright flow, and explains the document and rendering decisions to make before using it for live orders. Confirm transaction-specific GST requirements with current official material and a qualified tax adviser.

1. Choose the right document for the transaction

Do not treat “receipt,” “invoice,” and “receipt voucher” as interchangeable labels. The appropriate document depends on the transaction and the seller’s circumstances. CBIC’s GST rules list different particulars for a tax invoice, bill of supply, and receipt voucher. The PDF implementation should follow the document classification decided for the order.

Tax invoice particulars to consider

CBIC’s tax-invoice rule lists particulars including supplier name, address and GSTIN; a consecutive serial number unique for the financial year; issue date; recipient details in specified circumstances; HSN code for goods or accounting code for services; description; quantity and unit for goods; total and taxable values; and tax rates and amounts. Buyer address requirements depend on registration and supply circumstances. See the [CBIC GST invoice rules](https://cbic-gst.gov.in/pdf/rules/Chapter-6.pdf) and confirm which particulars apply to the store’s transactions.

Receipt voucher and bill of supply

A receipt voucher has its own particulars. The CBIC list includes supplier identity and GSTIN, a financial-year-unique consecutive number, issue date, recipient particulars, description, advance received, tax rate and amount, place-of-supply information for relevant inter-state supplies, reverse-charge status, and supplier signature or digital signature. A bill of supply also has a separate list. Use the official rules to identify the right document and field set instead of copying a tax-invoice template for every order. [CBIC receipt-voucher and bill-of-supply rules](https://cbic-gst.gov.in/pdf/rules/Chapter-6.pdf).

Some provisions describe consolidated invoices for particular supplies to unregistered recipients and separate timing rules for service invoices. These are scenario-specific; they are not a universal rule for every online order. The research cited here does not establish every current e-invoicing threshold, QR-code notification, marketplace arrangement, or exception. Verify those questions against current official notices and professional advice.

2. Build and render a receipt with Puppeteer

The example uses structured order data, escapes values before inserting them into HTML, loads that HTML in a controlled browser page, and writes an A4 PDF. Install Puppeteer in a Node.js project:

npm install puppeteer

Create receipt.js:

const puppeteer = require('puppeteer');

// Example only. Populate these fields from validated order records and
// use the document type and fields appropriate to the transaction.
const order = {
  number: 'INV-2026-00042',
  date: '2026-10-04',
  seller: 'Example Store',
  sellerAddress: '12 Market Road, Bengaluru, Karnataka',
  gstin: 'YOUR_SELLER_GSTIN',
  buyer: 'Asha Customer',
  buyerAddress: '45 Garden Street, Chennai, Tamil Nadu',
  items: [
    { description: 'Cotton shirt', quantity: 2, unit: 'piece', unitPrice: 800, taxable: 1600, taxRate: 5, tax: 80 },
    { description: 'Shipping', quantity: 1, unit: 'service', unitPrice: 50, taxable: 50, taxRate: 0, tax: 0 },
  ],
  total: 1730,
};

function escapeHtml(value) {
  return String(value).replace(/[<>&\"']/g, (char) => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '\"': '&quot;',
    "'": ''',
  })[char]);
}

function money(value) {
  return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(value);
}

const rows = order.items.map((item) => `
  <tr>
    <td>${escapeHtml(item.description)}</td>
    <td class="numeric">${escapeHtml(item.quantity)} ${escapeHtml(item.unit)}</td>
    <td class="numeric">${money(item.taxable)}</td>
    <td class="numeric">${escapeHtml(item.taxRate)}%</td>
    <td class="numeric">${money(item.tax)}</td>
  </tr>`).join('');

const html = `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Receipt ${escapeHtml(order.number)}</title>
  <style>
    @page { size: A4; margin: 18mm 16mm; }
    * { box-sizing: border-box; }
    body { color: #202124; font: 12px/1.45 Arial, sans-serif; }
    h1 { font-size: 22px; margin: 0 0 16px; }
    h2 { font-size: 14px; margin: 20px 0 6px; }
    .meta { display: flex; justify-content: space-between; gap: 24px; }
    .meta div { min-width: 0; white-space: pre-line; }
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    thead { display: table-header-group; }
    tr { break-inside: avoid; }
    th, td { border-bottom: 1px solid #d8dce0; padding: 8px 6px; text-align: left; vertical-align: top; }
    th { background: #f1f3f4; }
    .numeric { text-align: right; white-space: nowrap; }
    .total { margin-top: 14px; text-align: right; font-size: 16px; font-weight: bold; }
    @media print { body { print-color-adjust: exact; -webkit-print-color-adjust: exact; } }
  </style>
</head>
<body>
  <h1>Receipt / document ${escapeHtml(order.number)}</h1>
  <div class="meta">
    <div><strong>Seller</strong><br>${escapeHtml(order.seller)}<br>${escapeHtml(order.sellerAddress)}<br>GSTIN: ${escapeHtml(order.gstin)}</div>
    <div><strong>Buyer</strong><br>${escapeHtml(order.buyer)}<br>${escapeHtml(order.buyerAddress)}<br>Date: ${escapeHtml(order.date)}</div>
  </div>
  <table>
    <thead><tr><th>Description</th><th class="numeric">Quantity</th><th class="numeric">Taxable value</th><th class="numeric">Rate</th><th class="numeric">Tax</th></tr></thead>
    <tbody>${rows}</tbody>
  </table>
  <p class="total">Total: ${money(order.total)}</p>
</body>
</html>`;

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'networkidle0' });
    await page.pdf({
      path: `receipt-${order.number}.pdf`,
      format: 'A4',
      printBackground: true,
      margin: { top: '18mm', right: '16mm', bottom: '18mm', left: '16mm' },
      preferCSSPageSize: true,
    });
  } finally {
    await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it with node receipt.js. The file path is relative to the current working directory. The values and tax presentation above are illustrative, not a compliance determination. The renderer does not validate GSTINs, serial-number uniqueness, tax calculations, or document classification.

Security and data handling

  • Build the PDF from trusted structured order records. Escape every customer- or seller-supplied string before placing it in HTML; otherwise markup can break the document or inject active content.
  • Avoid loading arbitrary user-provided URLs in the rendering browser. If templates include remote images, fonts, or stylesheets, restrict their sources and ensure the browser environment cannot access internal services.
  • Keep order identifiers and generated files associated with the source order and document record. Define access control and retention according to the store’s needs; the cited rendering documentation does not prescribe a storage design.
  • Use decimal-safe arithmetic for monetary calculations in production and calculate tax in the order system. Do not rely on display formatting or browser JavaScript to determine the amount owed.

3. Equivalent implementation with Playwright

Playwright’s page API also provides page.pdf(); it renders using print CSS by default. Install the package and browser runtime according to the [Playwright installation guide](https://playwright.dev/docs/intro), then use the same prepared HTML string:

npm install playwright
const { chromium } = require('playwright');

async function saveReceiptPdf(html, outputPath) {
  const browser = await chromium.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'networkidle' });
    await page.pdf({
      path: outputPath,
      format: 'A4',
      printBackground: true,
      margin: { top: '18mm', right: '16mm', bottom: '18mm', left: '16mm' },
    });
  } finally {
    await browser.close();
  }
}

// Call with the escaped, complete HTML document built from the order record.
saveReceiptPdf(html, 'receipt.pdf').catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Choose the library that fits the store’s existing stack and deployment environment. Both use browser-based print rendering. Check the API references for supported options: [Puppeteer PDFOptions](https://pptr.dev/api/puppeteer.pdfoptions), [Puppeteer PDF generation guide](https://pptr.dev/guides/pdf-generation), and [Playwright Page API](https://playwright.dev/docs/api/class-page).

4. Set page size, margins, and print CSS

Use CSS and API options together. The example chooses A4 explicitly, prints backgrounds, sets margins, and lets CSS page sizing take precedence in Puppeteer. Playwright documents A4 as a supported format. Puppeteer’s PDF options also cover width and height, page ranges, background printing, CSS page-size preference, and font readiness.

Control Use it for Things to check
format, width, height, or CSS @page Choosing paper geometry for the receipt or invoice Set one intentional page size. If CSS should decide in Puppeteer, enable preferCSSPageSize.
margin and @page margins Keeping content inside printable page bounds Check that headers, totals, and long addresses do not collide with page edges.
printBackground Including colored fills, badges, or other background graphics Without it, backgrounds may not appear in the generated PDF.
pageRanges Emitting selected pages from a larger document Use only when the workflow intentionally needs a subset; verify that required totals and terms remain included.
Print CSS Controlling page breaks, repeated table headers, and print-only styling Test the actual browser renderer. Avoid splitting an item row or a total block across pages.

Use thead { display: table-header-group; } to request repeated table headings in print layout and break-inside: avoid for rows or compact blocks. These are layout hints, not a guarantee that an oversized row can fit on one page. Keep fonts readable and check that long descriptions can wrap without pushing numeric columns off the page.

5. Handle fonts, images, and page breaks

  • Fonts: Puppeteer documents that PDF generation waits for fonts to be ready by default. If using external fonts, make sure they can load in the deployed environment and inspect the generated file for fallback fonts. Local or bundled assets reduce dependence on third-party availability.
  • Images and logos: Wait for remote resources to finish loading, or use controlled local assets. A page can render before an image becomes available if the page lifecycle or network behavior differs from expectations.
  • Long orders: Test enough rows to force multiple pages. Confirm the table header repeats and the final total remains with the closing section.
  • Long addresses and descriptions: Test unbroken identifiers, international characters, and text that wraps to several lines. Permit wrapping in description columns while keeping quantity and amount columns legible.
  • Screen styling: These APIs render print CSS by default. If the page was switched to screen media before PDF generation, print-specific styles will not be active; remove that switch or restore print media.
  • Color and backgrounds: If the design depends on background colors, enable background printing and use print color adjustment styles where appropriate.

6. Generate PDFs in a store workflow

  1. Classify the transaction. Decide whether the document is a tax invoice, bill of supply, receipt voucher, or another document for this transaction and seller.
  2. Validate order data. Calculate totals in the order system, assign the document number under the store’s numbering process, and validate required fields before rendering.
  3. Render deterministically. Use a known template version, explicit paper size and margins, and controlled fonts and assets.
  4. Check output quality. Verify that the PDF opens, contains expected pages, and shows identifiers, item details, tax breakdowns, and totals clearly.
  5. Associate the artifact with the order. Store or deliver the generated document using the store’s access and retention design, and make failures observable so they can be retried safely.

For reliability, avoid creating duplicate documents with conflicting identifiers on retries. Make generation repeatable for a given order and template version, and distinguish a failed render from a failed order transaction. Browser startup and resource loading add work to each job; reusing a browser process can reduce repeated startup overhead, but isolate pages and close them after use. Set a job timeout and ensure cleanup runs on both success and failure.

7. Troubleshooting common PDF problems

Symptom Likely cause Fix
PDF is blank or content is missing HTML was not ready, content is rendered asynchronously, or a resource failed Wait for the required content or selector before calling PDF generation; inspect browser console and network failures.
Background colors or bands disappear Background printing is disabled Enable Puppeteer’s printBackground or Playwright’s background option and inspect print CSS.
Wrong paper size or unexpected scaling Conflicting API dimensions and CSS @page rules Choose one intended size; in Puppeteer use preferCSSPageSize when CSS should control it.
Text is clipped at the edge Margins are too small, fixed widths exceed the page, or long text cannot wrap Adjust margins and column widths; allow descriptions to wrap and test long values.
Rows split awkwardly across pages Print layout cannot keep the row together or the row is taller than a page Use page-break hints, shorten or restructure content, and test oversized descriptions separately.
Fonts differ from the browser preview Font files were unavailable, blocked, or not ready when rendering Bundle or reliably serve fonts and wait for font readiness before capture.
Images are missing Remote image loading is incomplete or the URL is inaccessible from the renderer Use controlled accessible assets and wait for their load; avoid depending on fragile third-party URLs.
Process hangs or jobs time out Navigation waits forever on persistent network requests or browser cleanup is skipped Use a suitable readiness condition, set an overall timeout, and close pages and browser resources in finally.
GST fields or totals are wrong Business data or document classification is wrong; PDF rendering does not validate compliance Fix the order and tax logic at its source and have the applicable document requirements reviewed.

8. Performance, reliability, and cost

Puppeteer and Playwright require a browser runtime in the generation environment. Capacity and cost depend on the store’s runtime, concurrency, document complexity, asset loading, and hosting arrangement; the cited API documentation does not provide a cost comparison or benchmark. Measure representative jobs in the intended deployment before setting throughput or timeout limits.

  • Reduce unnecessary remote resources and keep templates self-contained where practical.
  • Set concurrency limits so many simultaneous renders do not exhaust memory or CPU.
  • Use a queue for bursts and retry transient renderer failures with a bounded retry policy.
  • Track render duration, page count, failures, and retries, while avoiding unnecessary exposure of customer data in logs.
  • Decide whether to write PDFs to a path or handle returned bytes based on the application architecture. Puppeteer documents both file output and PDF data output.
  • Make delivery idempotent so a retry does not create conflicting customer documents or duplicate side effects.

There is no evidence in the cited sources for a universal cheapest option or expected throughput. The practical choice is the browser API already supported by the application and operations team, measured with the actual receipt template.

9. Or skip the browser setup

If you need a screenshot or PDF capture endpoint instead of managing a browser runtime, ScreenshotNeo accepts one GET request with a URL and returns a screenshot or PDF. See the ScreenshotNeo API documentation. For an HTML page reachable by URL, this cURL example requests a PDF:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/receipt/INV-2026-00042 -d format=pdf -o receipt.pdf

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Use this for a receipt page that can be safely accessed by the capture service; do not expose private customer documents through a public URL without suitable access controls.

Sign up free for 1,000 screenshots a month, with no card required.

10. FAQ

Does generating a PDF make a receipt GST-compliant?

No. The renderer turns supplied HTML into pages. The seller must determine the correct document type and applicable particulars for the transaction.

Should an online store always use A4?

No single size is right for every document or workflow. A4 is supported by the documented browser APIs and is a reasonable explicit choice for a full-page invoice, but select and test the geometry your store needs.

Can the receipt be generated without visiting a public webpage?

Yes. Puppeteer and Playwright can render HTML supplied directly to a page, as in the examples. ScreenshotNeo’s URL-based call is for a page accessible to its capture endpoint.

Which browser library should I use?

Use the one that fits the application’s existing dependencies and deployment environment. Both expose PDF generation and print-oriented rendering; test your own template and runtime.

Sources