—

Add to contacts

This is the page a scanner lands on. The visit and the save are both logged against this attendee.

Badge KitAttendee vCard QR codes, bulk export, and scan tracking

0 attendees loaded
direct vCard encoding
checking storage…

Bring in the list

A CSV or Excel file with one row per attendee. Headers get matched automatically; fix anything it guesses wrong below.

Drop a spreadsheet here .csv, .tsv, .xlsx — or click to browse

Column mapping

Each mapped column becomes a field on the contact card. Leave a field unset to keep it off the badge entirely.

Load a file to map its columns.

Parsed roster

Nothing loaded yet.

Assign walk-in
The printed code stays valid; re-export for the Worker to make it live.

Badge proof

What actually gets printed. Point a phone camera at it to test.

—
QR version
—
Modules
—
Payload
—
Min print size
—

Encoding

Fields on the card

Export

A zip of PNGs plus a manifest is what a badge printer needs for a variable-data merge.

Contact sheet

Every attendee's code. Click one to load it into the proof.

What tracking costs you

The honest version, because it decides the architecture.

A direct vCard QR cannot be tracked. The phone decodes the card locally and never touches a network. No server sees it, so there is nothing to count. That mode is fast and works with no infrastructure — but it is invisible.

A link QR is trackable because every scan is an HTTP request you own. That is the only reliable way to get a number. It costs you a domain, a tiny endpoint, and a dependency on the venue's connectivity.

The numbers below are a live demo of the data model. In link mode your endpoint writes these rows; here you can seed them to see the shape. Simulated rows are marked.

Event totals

By attendee

Sorted by scans. In a real event this is the sponsor-facing number.

Activity

Most recent 100 events.

No scans recorded.

What it takes to run this for real

Everything in the prototype except the scan endpoint runs in the browser. The endpoint is the only piece you have to host, and it is small.

1. The scan endpoint, and the header that decides everything

One Worker, two routes: log the view and show a card page; log the save and hand over the contact. The whole user experience lives in how that second response is shaped, and it differs by platform.

// iPhone: serve text/vcard INLINE. Safari renders a contact sheet
// with "Create New Contact" — one tap. Do NOT add Content-Disposition.
return new Response(rec.vcard, {
  headers: { "content-type": "text/vcard; charset=utf-8", "cache-control": "no-store" }
});

// Android Chrome: skip the file entirely. An intent: URL opens the
// Contacts app with the fields already filled in — one tap to Save.
const href = "intent:#Intent;action=android.intent.action.INSERT;"
  + "type=vnd.android.cursor.dir/contact;"
  + "S.name="    + encodeURIComponent(rec.fn)    + ";"
  + "S.phone="   + encodeURIComponent(rec.phone) + ";"
  + "S.email="   + encodeURIComponent(rec.email) + ";"
  + "S.company=" + encodeURIComponent(rec.org)   + ";"
  + "S.browser_fallback_url=" + encodeURIComponent("/" + slug + ".vcf?dl=1")
  + ";end";

// Everything else: Web Share API with the .vcf, so Contacts shows up
// as a share target. Plain download is the last resort.

The mistake that produces a bad experience: setting Content-Disposition: attachment on the vCard route. That turns iOS's one-tap contact sheet into a file the person has to hunt down in Downloads. The complete Worker with all three paths, KV tracking and a stats page ships alongside this kit; load it with Export for Worker on the Codes tab.

2. Slugs, and why they should be random

Sequential ids let anyone walk the whole roster by incrementing a number, which turns 300 badges into a scrapeable contact database. Use a random slug per attendee — this prototype generates 6-character ones. Keep the mapping server-side and never put the attendee's email in the URL.

3. Things worth charging for
  • Scan counts per attendee. Falls out of the endpoint for free.
  • Save-to-contact rate. Separate the page view from the .vcf fetch and you can tell curiosity from intent. Usually a much lower and more interesting number.
  • Who scanned whom. The one organisers actually want, because it is a networking graph. It needs the scanner identified — either they are signed in on their own card page, or the landing page asks. Meaningfully more work, and the real upsell.
  • Edit after print. With link mode the badge is a pointer, so a wrong job title is a database fix rather than a reprint. Easy to sell, costs you nothing.
4. Before you quote it
  • Attendees have to opt in at registration to both the shared details and the tracking. Get that wording into the registration form, not into your contract.
  • Conference wifi fails. Link mode degrades to a dead QR; direct mode does not. A hybrid — link QR on the badge, direct QR on a backup card — covers it.
  • Long vCards make big QR codes. Watch the module count on the proof as you toggle fields; dropping a note field can be the difference between a 25 mm and a 40 mm code.
  • Budget for reprints. Roughly 3–5% of any 300-person roster has a name or title wrong at print time.