appscript.dev
Automation Intermediate Sheets

Build a QR-code check-in app

Scan to register attendance at Northwind events — the page logs to a Sheet.

Published Sep 5, 2025

Northwind runs a handful of client events each year, and the door always becomes a bottleneck — someone with a printed list ticking names while a queue forms. Paid event platforms solve this, but they are overkill for a 40-person breakfast briefing, and they leave the attendance data locked in someone else’s dashboard.

This automation is a one-function web app. Every attendee’s badge carries a QR code that points at the web app URL with their ID attached. Scanning it with any phone camera opens the page, which logs the arrival to a Sheet and shows a confirmation. The whole “system” is a single Sheet and a few lines of script, and the attendance data stays yours.

What you’ll need

  • A Google Sheet to hold check-ins, with a header row: Timestamp, Attendee ID. Copy its ID from the URL.
  • A list of attendee IDs — anything unique works (a booking reference, an email, a short code).
  • A QR code per attendee encoding WEBAPP_URL/exec?id=ATTENDEE_ID. Most badge-printing tools and free QR generators accept a URL directly.
  • The project deployed as a web app, set to execute as you and accessible to anyone (see Deploy it).

The script

The QR on each badge links to WEBAPP_URL/exec?id=ATTENDEE_ID. Loading that URL runs doGet, which records the arrival.

// The Sheet that collects check-ins. Copy the ID from its URL.
const CHECKINS_SHEET_ID = '1abcCheckinsId';

/**
 * Runs when an attendee's QR code is scanned. The QR encodes the web app
 * URL with ?id=ATTENDEE_ID, so e.parameter.id identifies who arrived.
 *
 * @param {Object} e The event object Apps Script passes to a web app.
 * @returns {HtmlOutput} A confirmation page shown on the attendee's phone.
 */
function doGet(e) {
  // Guard: a scan with no id is a malformed or hand-typed URL.
  const id = e && e.parameter ? e.parameter.id : null;
  if (!id) {
    return HtmlService.createHtmlOutput('Missing id — please ask staff for help.');
  }

  const sheet = SpreadsheetApp.openById(CHECKINS_SHEET_ID).getSheets()[0];

  // Record the arrival: a timestamp plus the scanned attendee ID.
  sheet.appendRow([new Date(), id]);

  // Show a confirmation on the phone that scanned the badge.
  return HtmlService.createHtmlOutput('Checked in: ' + id);
}

How it works

  1. An attendee scans the QR code on their badge. Their phone camera opens the web app URL, which carries ?id=ATTENDEE_ID.
  2. Loading that URL runs doGet, and Apps Script passes the query string in e.parameter.
  3. The script reads e.parameter.id. If there is no id — a hand-typed URL or a bad scan — it returns a friendly message and stops, so the Sheet never gains a blank row.
  4. It opens the check-ins Sheet and appends a row: the current time and the scanned ID.
  5. It returns a small HTML page confirming the check-in, which the attendee sees on their phone within a second of scanning.

Example run

Three attendees scan their badges as they arrive. Each scan appends a row to the check-ins Sheet:

TimestampAttendee ID
2026-05-25 08:31NW-1042
2026-05-25 08:33NW-1108
2026-05-25 08:36NW-1042

The attendee who scanned NW-1042 sees Checked in: NW-1042 on their phone. Note the duplicate row at 08:36 — a second scan logs a second time (see Watch out for).

Deploy it

The QR codes only work once the project is published as a web app:

  1. In the Apps Script editor, click Deploy then New deployment.
  2. Choose Web app as the type.
  3. Set Execute as to yourself and Who has access to Anyone.
  4. Click Deploy, approve the authorisation prompt, and copy the /exec URL.
  5. Generate each badge’s QR from WEBAPP_URL/exec?id=ATTENDEE_ID, substituting the real ID, and print them on the badges.

Test one badge before the print run — scan it and confirm a row lands in the Sheet.

Watch out for

  • Scans are not deduplicated. A second scan of the same badge adds a second row. To check in each attendee once, read the Sheet first and skip the appendRow if the ID is already present.
  • There is no attendee validation. Any id value is accepted as-is. To reject unknown IDs, keep an attendee list in a second tab and look the scanned ID up before logging.
  • Re-deploy after edits. Saving the script is not enough — publish a new version of the deployment, or scans hit the old build.
  • Anyone with the URL can check in. The ?id= value is visible in the URL, so a curious attendee could check in as someone else. For a low-stakes event briefing this is usually fine; for anything sensitive, add a per-attendee token instead of a guessable ID.
  • doGet is read-style but writes here. Browsers and link previewers sometimes pre-fetch URLs, which can trigger an unintended check-in. For a high-stakes count, move the write behind a confirm button on the page.

Related