---
title: "Event ticket template with a QR code for JavaScript — quario"
description: "A ready-made quario event ticket for JavaScript. One ticket per attendee, each with its own QR entry code, seat and price. Copy the definition and the QR function, point them at your orders, and render to PDF or HTML."
url: "https://getquario.com/templates/event-ticket"
---

# Event ticket template for JavaScript

A ticket has two readers. The person holding it wants the date, the venue, and their seat. The
scanner at the door wants a code it can check against the guest list. An order for three people is
three tickets. Each one has its own name and its own code, so a forwarded email can’t let three
people in on one ticket. This is a ready-made quario event ticket — copy it, point it at your orders,
and render one ticket per attendee to PDF or HTML.

## The template

The whole ticket is a definition — data you can version and review. The event block, the QR code,
the date and venue grid, the seat line and the small print are all in here. This is the exact file
the preview below renders:

```json title="event-ticket.report.json"
{
  "data": "$.order.tickets[*]",
  "sort": [{ "by": "=@.number" }],
  "aggregates": { "tickets": "count" },
  "style": { "family": "sans", "size": 9 },
  "groups": [{ "name": "ticket", "by": "=@.number", "break": "between" }],
  "detail": [
    {
      "type": "split",
      "slots": [
        {
          "type": "text",
          "width": 80,
          "value": [
            { "value": "{{ $.input.order.event.presenter }}\n", "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
            { "value": "{{ $.input.order.event.name }}\n", "style": { "family": "serif", "size": 24, "bold": true, "color": "#1e1f22" } },
            { "value": "{{ $.input.order.event.subtitle }}", "style": { "size": 9, "color": "#5e6062" } }
          ],
          "style": { "paddingRight": 18 }
        },
        { "type": "image", "source": "=qr(@.code)", "fit": "width", "alt": "Entry code {{ @.code }}" }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 38 }, { "width": 38 }, {}],
      "rows": [
        {
          "style": { "family": "mono", "size": 6, "uppercase": true, "color": "#989a97", "paddingTop": 16 },
          "slots": [{ "type": "text", "value": "Date" }, { "type": "text", "value": "Venue" }, { "type": "text", "value": "Entry code" }]
        },
        {
          "style": { "size": 10, "color": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "{{ $.input.order.event.date }}", "style": { "bold": true, "format": { "kind": "date", "form": "full" } } },
            { "type": "text", "value": "{{ $.input.order.event.venue }}", "style": { "bold": true } },
            { "type": "text", "value": "{{ @.code }}", "style": { "family": "mono", "size": 8 } }
          ]
        },
        {
          "style": { "size": 9, "color": "#5e6062", "paddingBottom": 8 },
          "slots": [
            { "type": "text", "value": "Doors {{ $.input.order.event.doors }}, starts {{ $.input.order.event.starts }}" },
            { "type": "text", "value": "{{ $.input.order.event.address }}" },
            { "type": "text", "value": "" }
          ]
        }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 38 }, { "width": 12 }, { "width": 12 }, { "width": 14 }, { "style": { "align": "right" } }],
      "rows": [
        {
          "style": { "family": "mono", "size": 6, "uppercase": true, "color": "#989a97", "paddingTop": 8,
            "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "Ticket holder" },
            { "type": "text", "value": "Section" },
            { "type": "text", "value": "Row" },
            { "type": "text", "value": "Seat" },
            { "type": "text", "value": "{{ @.type }}" }
          ]
        },
        {
          "style": { "size": 13, "bold": true, "color": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "{{ @.holder }}" },
            { "type": "text", "value": "{{ @.section }}" },
            { "type": "text", "value": "{{ @.row }}" },
            { "type": "text", "value": "{{ @.seat }}" },
            { "type": "text", "value": "{{ @.price }}", "style": { "format": "currency" } }
          ]
        }
      ]
    },
    { "type": "text", "value": "Ticket {{ @.number }} of {{ $.tickets }} · Order {{ $.input.order.number }} · Admits one. Scan the code at the door, on paper or on screen.",
      "style": { "size": 7, "color": "#989a97", "spaceBefore": 16, "spaceAfter": 60 } }
  ]
}
```

The rows are the tickets, `$.order.tickets[*]`, and the event is `$.input`. One ticket per page is a
**group** on the ticket number with `"break": "between"`. The QR code is an `image` item in the
second slot of the first split, and its `source` is `=qr(@.code)`. That ticket’s entry code goes in,
and the code comes back as SVG bytes the slot scales to its width.

The event block is one text slot written as three styled runs. The presenter line is small mono
capitals, the event name is the serif at 24 point, and the subtitle is plain. It’s one cell in three faces,
with the line breaks inside the runs.

The date declares `{ "kind": "date", "form": "full" }`, so `2026-09-12` reads _Saturday 12 September
2026_ under `en-IE`. The price is a bare number under `format: "currency"`, and the label above it
reads the ticket’s own type from the row.

## The QR code

[bwip-js](https://github.com/metafloor/bwip-js) draws the QR code as SVG, synchronously, and it runs in
Node and in the browser. A library that needs `await` would have to draw before the render and pass the
bytes in, because an image `source` can’t wait:

```ts title="qr.ts"
import bwipjs from "bwip-js";

/** A QR code of `text`, returned as the SVG's bytes. */
export function qr(text: string): Uint8Array {
  const svg = bwipjs.toSVG({ bcid: "qrcode", text });
  // bwip-js sizes the drawing by `viewBox` alone and leaves the modules' fill to the default,
  // which not every SVG renderer reads as black; the image item needs both stated.
  const [, , width, height] = svg.match(/viewBox="([^"]+)"/)![1].split(" ");
  return new TextEncoder().encode(
    svg
      .replace("<svg ", `<svg width="${width}" height="${height}" `)
      .replace("<path ", '<path fill="#000000" '),
  );
}
```

The code holds the ticket’s entry code alone, not a URL with the holder’s name in it. Your door app
looks the code up, so you can cancel and reissue a lost ticket without reprinting the rest of the
order.

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. Each ticket carries its holder, its seat, its price, and its code. The event sits once
on the order:

```json title="event-ticket.data.json"
{
  "order": {
    "number": "ORD-26-0912",
    "event": {
      "presenter": "Quire Press presents",
      "name": "Letterpress Night",
      "subtitle": "An evening on the Heidelberg platen, with type to set and a print to take home",
      "date": "2026-09-12",
      "doors": "19:00",
      "starts": "19:30",
      "venue": "Quire Press studio",
      "address": "Binnenkant 42, 1011 BM Amsterdam"
    },
    "tickets": [
      { "number": 1, "code": "QP-0912-4471-A", "holder": "Aoife Byrne", "type": "Standard", "section": "Studio", "row": "B", "seat": "7", "price": 18 },
      { "number": 2, "code": "QP-0912-4471-B", "holder": "Ciarán Byrne", "type": "Standard", "section": "Studio", "row": "B", "seat": "8", "price": 18 },
      { "number": 3, "code": "QP-0912-4471-C", "holder": "Niamh Byrne", "type": "Concession", "section": "Studio", "row": "B", "seat": "9", "price": 12.5 }
    ]
  }
}
```

## What it renders

This isn’t a screenshot. quario compiles the definition and data above, runs `qr.ts` in your
browser for each ticket, and `@quario/viewer` paints three tickets right here. The PDF button hands
you the same tickets as real `@quario/pdf` bytes, one 200 × 100 mm page each, with the QR modules as
vector paths. There’s no Word button: `@quario/docx` refuses an SVG image.

*Preview: the page renders this definition and its sample data live, with downloads — [see it rendered](https://getquario.com/templates/event-ticket).*

## Use it

```bash title="shell"
npm install quario @quario/pdf bwip-js
```

The ticket’s size is a render option, not part of the document. `size` takes a `[width, height]` in
points, and 200 × 100 mm is 566.93 × 283.46:

```ts title="event-ticket.ts"
import { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import ticket from './event-ticket.report.json' with { type: 'json' }

// `qr` is the function from qr.ts
const q = quario({ locale: 'en-IE', currency: 'EUR' })   // what `format` presents through
const report = q.report(ticket, { qr })

const data = await getOrder(id)                      // your ticketing data
const bytes = await report.render(pdf({ page: { size: [566.93, 283.46], margin: 24 } }), data)

writeFileSync('tickets.pdf', bytes)                  // one page per ticket
```

Attach the PDF to the confirmation email, or render each ticket on its own for a wallet pass page.
Either way it’s the same definition.

## Make it yours

Because the ticket is declarative, changing it means changing data. A ticket on A4 for printing at
home is a different `size` in the render call. A general-admission event drops the section, row and
seat columns. A festival pass with several days is the dates as a list in the data. A barcode in
place of the QR code is a different `bcid` in the function, as on the
[shipping label](https://getquario.com/templates/shipping-label.md). It renders **without `eval`**, so a name typed at
checkout never becomes code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Will a phone scan the QR code from a screen?

Yes. The code is vector paths in the PDF, so it stays sharp at any zoom. Keep it at least 2 cm
across on paper, and leave the white margin around it, which the scanner needs to find the code.

### Can I generate the tickets as a PDF?

Yes, and it’s the format a confirmation email carries. `@quario/pdf` renders this definition straight
to bytes with no browser anywhere, so your checkout can issue tickets the moment a customer pays.
[Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I use this event ticket template commercially?

The definition is yours — the report definitions, data, and output you create with quario belong to
you. quario is free to evaluate with no time limit. Selling real tickets is past evaluation, so that
needs a license for each production deployment. Its users, agents, and renders are never counted.
[Pricing →](https://getquario.com/pricing.md)

### Where are the other templates?

[Browse the template gallery →](https://getquario.com/templates.md) — receipts, certificates, shipping labels, and more.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Templates","item":"https://getquario.com/templates"},{"@type":"ListItem","position":3,"name":"Event ticket","item":"https://getquario.com/templates/event-ticket"}]}
```
