A Google Apps Script that turns an attendee list in a Google Sheet into a print-ready grid of colour-coded event badges — as sticker labels, lanyard cards, or wristbands, on A4 or Letter paper.
Built as a portfolio/demo tool. Plain Apps Script + HTML/CSS/JS, no external libraries.
- Three badge styles, each with a live preview in the dialog.
- A4 or Letter, with adjustable columns, rows, and page margin.
- Badges colour-coded by ticket type (VIP, Speaker, Sponsor, …).
- Reads the sheet live — re-sort or filter rows to change the print order.
- A partial last page prints only the badges it has, leaving the rest of the sheet blank and re-usable.
- Output by browser print (high fidelity) or by saving a PDF to Drive.
- Layout is real CSS Grid sized in millimetres, so it lines up with physical label sheets.
Everything runs from a custom Labels menu:
- Generate Labels… — opens the options dialog, then Print or Save PDF.
- Setup / Reseed Demo Data — (re)creates the
Attendeessheet with ~60 sample rows.
-
Open the Google Sheet you want to bind this to (or create a new one).
-
Go to Extensions ▸ Apps Script to open the bound editor.
-
Add the files and paste in the matching contents:
Code.gs— replace the default file.- File ▸ + ▸ HTML, name it
dialog, paste indialog.html. - File ▸ + ▸ HTML, name it
labels, paste inlabels.html.
HTML files are named without the
.htmlextension in the editor — the code loads them asdialogandlabels. -
Save, then reload the spreadsheet tab. A Labels menu appears.
-
Run Labels ▸ Setup / Reseed Demo Data. The first run asks you to authorize (Sheets + Drive) — accept once. It creates an
Attendeessheet with ~60 generic sample rows (John Doe, Jane Smith, …) and aSettingssheet of editable defaults (see below). -
Run Labels ▸ Generate Labels…, choose your options, and Print or Save.
| RegNo | Name | Phone | Company | Ticket | Seat |
|---|
- RegNo sets the print order: attendee 1 goes top-left, then left-to-right, top-to-bottom, flowing onto later pages in order.
- The sheet is read live at generate time, so re-sorting or filtering just changes the output order — it never breaks generation.
- Ticket picks each badge's colour (see below), even when the ticket text itself isn't printed.
- Replace the demo rows with your own; just keep the header row.
Recognised ticket types and their colours:
| Ticket | Colour | Ticket | Colour |
|---|---|---|---|
VIP |
amber | Staff |
sky |
Speaker |
teal | Media |
purple |
Sponsor |
rose | Regular |
indigo |
| (anything else) | slate |
The colours themselves live in the Settings sheet, so you can re-theme without
touching code (see below).
Created automatically by Setup / Reseed Demo Data, the Settings sheet holds
the editable defaults as a simple Setting / Value table. Edit a value, and the
next generate picks it up — no code changes.
| Setting | Controls |
|---|---|
| Data sheet name | Which tab the attendee rows come from |
| Default event name/date | Pre-fills the dialog's event fields |
| Lanyard logo text | The caption under the placeholder logo |
| Lanyard role label | Shown when the ticket is blank or Regular (default ATTENDEE) |
| Default fields | Which field checkboxes start ticked (comma list) |
| Style grid / margin | Default columns × rows and margin per style (sticker/lanyard/wristband) |
Colour: * |
Each ticket type's two-stop gradient as #light, #dark hex |
Event date is plain text. The Value column is formatted as text so what you type (e.g.
July 1, 2026) prints verbatim — Sheets won't turn it into a date serial. Values are also read as their displayed text, so even an already-converted cell prints cleanly rather than as a timestamp.
It's robust by design:
- Optional. If the sheet (or any single value) is missing or invalid, the
built-in defaults are used — behaviour is identical to having no
Settingssheet at all. Colours must be valid hex or the default for that row is kept. - Non-destructive. Re-running Setup / Reseed Demo Data won't overwrite a
Settingssheet you've edited. To restore the defaults on purpose, use Labels ▸ Reset Settings to Defaults.
| Option | What it does | Default |
|---|---|---|
| Print style | Sticker sheet / Lanyard card / Wristband | Sticker |
| Page size | A4 (210×297 mm) or US Letter (215.9×279.4 mm) | A4 |
| Columns × Rows | Badges per page | per style |
| Page margin | Millimetres on all four sides | per style |
| Event details | Event name + date (lanyard / wristband only) | — |
| Fields | Name, Phone, Company, Ticket, Seat | Name + Phone |
Choosing a style applies a sensible default grid and shows only the options that style uses — for example, Event details appears for lanyard and wristband but is hidden for stickers.
All styles share the same page-grid math, cut guides, and partial-page behaviour; only the per-badge content differs.
- Sticker sheet — compact adhesive labels, many per page (default 4 × 12). A coloured header band (ticket colour) plus name, phone, and any optional fields.
- Lanyard card — a portrait badge (default 2 × 2, ≈ 95 × 138 mm) shaped like
a lanyard insert: a punch-slot guide, a coloured header with a placeholder logo
and a large role banner (the ticket type;
Regular/blank shows asATTENDEE), then name, company, phone, and an event name + date footer. Turn on Seat for a small seat/table chip in the corner. - Wristband — a long horizontal strip (default 1 × 11, full width). Coloured bands across the top and bottom carry the role text (so it stays visible when the band is wrapped round a wrist); the white centre holds the name, a combined meta line (company · phone · seat), and the event details. Keep columns at 1 for a single full-width band per row.
Every dimension is derived from the chosen page and your inputs — nothing is
hard-coded (Code.gs ▸ computeLayout, PAGE_SIZES):
printableWidth = pageWidth − (2 × margin)
printableHeight = pageHeight − (2 × margin)
cellWidth = printableWidth ÷ columns
cellHeight = printableHeight ÷ rows
Example — A4 with a 10 mm margin (printable area 190 × 277 mm):
| Grid | Cell size (mm) |
|---|---|
| 4 × 12 | 47.5 × 23.0833 |
| 5 × 12 | 38 × 23.0833 |
In labels.html, this becomes:
@page { size: <width> <height>; margin: 0 }plus an outer.pagewhose padding reproduces your margin, so the printable area lines up exactly.box-sizing: border-boxand zero gap, so cells tile like a real label sheet.- Single, never-doubled cut guides: each cell draws its right + bottom border, the first column adds a left border, the first row adds a top border. Because every line belongs to a real badge, a partial page has no stray lines or empty boxes.
- Text lines clip with an ellipsis so every badge keeps a uniform height.
Both paths render the same HTML from labels.html.
- Print (recommended) — opens the grid in a new tab and calls
window.print(). Browsers fully support CSS Grid and@page, so this is the most accurate option. Choose Save as PDF in the print dialog if you want a file. - Save PDF to Drive — converts the HTML to a PDF with Apps Script's built-in
Blob.getAs('application/pdf')and saves it to your Drive. No libraries, but Google's server-side converter has limited CSS support (gradients and some colours may differ), so prefer Print when colour fidelity matters.
Re-theme the colours, defaults, logo, etc. Edit the Settings sheet — no
code needed (see The Settings sheet). Each Colour: row
is a #light, #dark pair drawn as a gradient. The built-in defaults that seed
that sheet live in Code.gs ▸ defaultSettings.
Add a new field (say a Role column on Attendees):
Code.gs— add'Role'to theHEADERSarray (and to the seed data if you reseed).Code.gs ▸ normaliseFields— addRole: f.Role === true(default off) or!== false(default on).dialog.html— add a checkbox<input type="checkbox" id="f_Role">and aRole: document.getElementById('f_Role').checkedline ingetOptions().labels.html— render it in whichever styles you want:- Sticker (inside
.body):<? if (fields.Role) { ?><div class="line company"><?= rec.Role ?></div><? } ?> - Lanyard (inside
.lmain):<? if (fields.Role) { ?><div class="lorg"><?= rec.Role ?></div><? } ?> - Wristband: push it onto the
metaarray, e.g.if (fields.Role && rec.Role) meta.push(rec.Role);
- Sticker (inside
The dimension math is independent of which fields show, so nothing else changes.
- Print at 100% / “Actual size”, not “Fit to page”.
- Enable background graphics so the colour bands actually print.
- Label sheets vary by a millimetre or two — run one test print on plain paper, hold it against the real sheet, and tweak the margin (and columns/rows if needed) until the grid lines up with the die-cut labels.
| File | Purpose |
|---|---|
Code.gs |
Menu, demo-data seeding, live sheet reading, layout math, ticket colours, PDF export. |
dialog.html |
The options UI (style, page size, grid, event details, fields) and the Print / Save-PDF actions. |
labels.html |
The grid template (CSS Grid in mm). Rendered server-side and shared by both output paths. |
Apps Script manages its own
appsscript.jsonmanifest, and OAuth scopes are auto-detected on first run — there's nothing extra to configure.
Released under the MIT License — free to use, modify, and distribute.