Event ticket PDF generator

Event ticket PDF generator

Generate event ticket PDFs from HTML with a clean ticket layout, server-side or JavaScript QR codes, one ticket per page, and email delivery.

Concerts, meetups, conferences and workshops all need the same thing after checkout. Attendees need a ticket they can print or show on a phone, with a code you can scan at the door.

A PDF is the most portable format for that. It opens on any device, prints cleanly and attaches to an email. Here's how to generate ticket PDFs from an HTML template.

Quick answer. Build one .ticket block per attendee with break-after: page, embed each QR code as a data URI, and send the HTML to the API:

curl -X POST https://html-pdf.com/v1/pdf \
  -H "Authorization: Bearer $HTML_PDF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<div class=\"ticket\"><h1>Frontend Night 2026</h1><p>TKT-8F3K2Q</p></div>", "prefer_css_page_size": true}' \
  -o tickets.pdf

Ticket layout

This section gives you a ticket template with a clear hierarchy. A ticket needs these parts.

A6 landscape or a half-page layout fits a phone screen well. Use A4 if attendees are likely to print it.

<!doctype html><html><head><meta charset="utf-8"><style>
  @page { size: 148mm 105mm; margin: 0; } /* A6 landscape; Chrome needs explicit mm for A6 */
  html, body { margin: 0; }
  body { font-family: system-ui, sans-serif; color: #111; }
  .ticket { box-sizing: border-box; width: 148mm; height: 105mm; padding: 8mm; display: flex; gap: 6mm;
            break-after: page; overflow: hidden; }
  .ticket:last-child { break-after: auto; }
  .info { flex: 1; border-right: 2px dashed #bbb; padding-right: 6mm; }
  .event { font-size: 20px; font-weight: 700; margin: 0 0 2mm; }
  .type { display: inline-block; background: #111; color: #fff; padding: 1mm 3mm; border-radius: 2mm; font-size: 11px; }
  .qr { width: 42mm; text-align: center; font: 10px ui-monospace, monospace; }
  .qr img, .qr svg { width: 42mm; height: 42mm; }
</style></head><body>
  <!-- one .ticket block per attendee -->
</body></html>

break-after: page gives each ticket its own page. A group order becomes one PDF with one ticket per page, and you can still split it later if needed.

Generating QR codes on your server is the simplest and most reliable approach. Embed each code as inline SVG or a PNG data URI, and the page needs no JavaScript and no external requests. In Node.js, the qrcode package does both.

import fs from "node:fs";
import QRCode from "qrcode";

const esc = s => String(s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
const head = fs.readFileSync("ticket-head.html", "utf8"); // the <head> and styles above

const tickets = [
  { id: "TKT-8F3K2Q", name: "Ana García", type: "General" },
  { id: "TKT-9P1Z7M", name: "Tom Becker", type: "VIP" },
];

const blocks = await Promise.all(tickets.map(async t => {
  const qr = await QRCode.toDataURL(t.id, { errorCorrectionLevel: "M", margin: 1, width: 400 });
  return `<div class="ticket">
    <div class="info">
      <p class="event">Frontend Night 2026</p>
      <p>Thu 12 Nov 2026 · 19:00<br>The Warehouse, 5 River St</p>
      <p><strong>${esc(t.name)}</strong><br><span class="type">${esc(t.type)}</span></p>
      <p style="font-size:9px;color:#666">Non-transferable. Show this ticket at the entrance.</p>
    </div>
    <div class="qr"><img src="${qr}" alt="QR code"><div>${t.id}</div></div>
  </div>`;
}));

const html = `${head}<body>${blocks.join("")}</body></html>`;
const res = await fetch("https://html-pdf.com/v1/pdf", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.HTML_PDF_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ html, prefer_css_page_size: true, filename: "tickets-order-1042.pdf" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
fs.writeFileSync("tickets-order-1042.pdf", Buffer.from(await res.arrayBuffer()));

If you prefer vector output, QRCode.toString(t.id, { type: "svg" }) returns an SVG string instead. In Python, segno.make(ticket_id).svg_inline() gives you inline SVG.

What goes in the QR code matters more than how it's drawn. Use a random, unguessable ID (or a signed token), check it against your database at the door, and mark it as used on the first scan.

QR codes: in the page with JavaScript

You can also draw the codes in the page itself. Load a QR library from a CDN, draw the codes and set a flag when they're done.

Then pass "wait_for_expression": "window.ticketsReady === true" so the PDF isn't captured before the codes exist. Setting wait_delay (up to 10 seconds) also works, but waiting for an explicit flag is faster and more reliable.

Python version

Here is the render step in Python, assuming your HTML is already filled in.

import os, requests

html = open("tickets.html", encoding="utf-8").read()  # filled, escaped, QR codes embedded
r = requests.post("https://html-pdf.com/v1/pdf",
    headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
    json={"html": html, "prefer_css_page_size": True, "max_pages": 20}, timeout=60)
r.raise_for_status()
open("tickets.pdf", "wb").write(r.content)

PDF tickets vs wallet passes

A PDF is not an Apple Wallet or Google Wallet pass. Wallet passes are separate formats (a signed .pkpass bundle for Apple Wallet, and Google's Wallet API for Android) that need their own developer setup and signing.

Many events send both. The PDF is the universal fallback that always works, and an "Add to Wallet" button serves those who want it. The same ticket ID can power both.

Email delivery

The usual flow is to attach the PDF to the order confirmation email. Use your mail provider or SMTP library, and give the file a name that's easy to find later (tickets-frontend-night-1042.pdf).

For a "download your tickets" button, request "response": "url" instead. The link is valid for 24 hours, so store the PDF in your own storage if you need long-lived links.

Send tickets only after payment is confirmed, for example from your payment webhook.

Generate tickets with html-pdf.com

html-pdf.com turns HTML or a URL into a PDF with Chromium. Each ticket page costs one credit. You get 100 free credits once you confirm your email, then pay from $0.005 per page, and purchased credits never expire.

Your HTML is not stored. Get an API key, browse templates, or read the docs.

With the MCP server, an AI agent can create tickets on request:

"Create PDF tickets for Frontend Night on 12 November 2026 at The Warehouse for these 5 attendees, one ticket per page, each with a QR code of its ticket ID and the ID printed underneath."

FAQ

How do I put one ticket per page in a PDF?

Wrap each ticket in its own block with break-after: page, and set break-after: auto on the last one. A group order then becomes one PDF with one ticket per page.

How do I add a QR code to a ticket PDF?

Generate it on your server with a library like qrcode (Node.js) or segno (Python) and embed it as a data URI or inline SVG. If you draw it with JavaScript in the page instead, use wait_for_expression so the PDF waits for the codes.

How much does it cost to generate ticket PDFs?

Each ticket page costs one credit. New accounts get 100 free credits once with no card, then packs run from $10 for 1,000 credits to $250 for 50,000, and purchased credits never expire.

Can a PDF ticket be added to Apple Wallet or Google Wallet?

No. Wallet passes are separate formats with their own setup and signing. Many events send the PDF plus an "Add to Wallet" button, both using the same ticket ID.

This page as markdown · text