vCard contact QR codes

A contact QR code carries a small text document: a vCard. This guide shows what the vCard QR code generator writes into it and why, which characters are escaped and how, what the builder refuses, and how to read the symbol facts the engine reports. Every payload, escaped string, normalised number, rejection message and module count below is produced at build time by the same engine that runs the generator, from a fictional contact — nothing is typed by hand, so this page cannot disagree with the tool. The wider series is on the guides index.

Two layers, applied to a contact card

The methodology page draws one line through every code on this site: the payload is the text that gets encoded, and the symbol is the black-and-white matrix built from that text to ISO/IEC 18004 by a vendored copy of Project Nayuki's qrcodegen. The symbol does not know it is carrying a contact, only bytes; everything that makes the scan behave like a contact lives in the payload, and most codes that "don't work" are payload problems. That is why this guide is mostly about the text.

Why vCard 3.0 rather than MECARD

Two text conventions exist for contact data inside a QR code. The engine emits vCard 3.0, the format specified in RFC 2426, and the methodology page records the reason: vCard was chosen over MECARD for broader field support and better scanner coverage. The builder's input type shows what that buys in practice: seven fields — first name, last name, phone, email, organisation, job title and website URL. The two name parts are combined into the N: and FN: lines; each of the other five gets a line of its own when it is filled in. There is no photo field (see the FAQ).

What the builder writes, line by line

A card for a fictional contact — Priya Castellanos, Field Engineer at Halvorsen, Reyes & Co, with an invented number and address — produces exactly this payload:

BEGIN:VCARD
VERSION:3.0
N:Castellanos;Priya;;;
FN:Priya Castellanos
TEL;TYPE=CELL:+442079460123
EMAIL:priya@example.com
ORG:Halvorsen\, Reyes & Co
TITLE:Field Engineer
URL:https://example.com/priya
END:VCARD

Rendered by the same engine as the generator: version 11, 61×61 modules, error correction M. The payload is 218 bytes in 10 content lines.

The lines, in the order the builder emits them:

  • BEGIN:VCARD and END:VCARD bracket the card, and VERSION:3.0 declares the dialect; all three are always present.
  • N: is the structured name: family name, semicolon, given name, then three positions the builder leaves empty. The semicolons on this line are separators, which is why a semicolon inside a name must be escaped.
  • FN: is the formatted name, first and last joined with a space, built from the same two inputs as N: so the two lines cannot disagree.
  • TEL;TYPE=CELL: carries the number, labelled as a mobile number and normalised to E.164 rather than copied as typed.
  • EMAIL: carries the address after a format check, escaped like the other text fields; ORG: and TITLE: carry escaped free text.
  • URL: carries the website, validated by the same check the URL generator applies — protocol required, http or https only — then written exactly as entered, never rewritten.

Optional lines are omitted when their field is empty, not written blank. A card with only a name and number comes out as 6 lines and 112 bytes, against 10 lines and 218 bytes for the full card:

BEGIN:VCARD
VERSION:3.0
N:Castellanos;Priya;;;
FN:Priya Castellanos
TEL;TYPE=CELL:+442079460123
END:VCARD

Line endings: CRLF, including the last one

RFC 2426 delimits content lines with CRLF — a carriage return followed by a line feed — and the builder joins every line with that pair, then appends one more after END:VCARD, because the specification (§2.4.2) treats every content line, the last included, as CRLF-terminated. A browser shows those terminators as ordinary line breaks, so here is the same payload with each one written out:

BEGIN:VCARD\r\n
VERSION:3.0\r\n
N:Castellanos;Priya;;;\r\n
FN:Priya Castellanos\r\n
TEL;TYPE=CELL:+442079460123\r\n
EMAIL:priya@example.com\r\n
ORG:Halvorsen\, Reyes & Co\r\n
TITLE:Field Engineer\r\n
URL:https://example.com/priya\r\n
END:VCARD\r\n

A hand-assembled card that ends its lines with a bare line feed, or omits the final terminator, looks identical on screen and differs byte for byte — the kind of detail the engine's reference tests, which compare its output against independently constructed strings, exist to catch.

Escaping: backslash, semicolon, comma and newline

RFC 2426 reserves four things inside a text value: the backslash, the semicolon, the comma and the line break. Every free-text field — both name parts, the email, the organisation and the title — goes through escapeVCardText, which doubles a backslash, puts a backslash before each semicolon and comma, and turns a typed line break (CRLF, LF or CR) into the two characters \n. Five inputs and what the escaper returns for each (⏎ marks a typed line break):

Case Typed Written into the card
A comma in a company name Halvorsen, Reyes & Co Halvorsen\, Reyes & Co
A semicolon in a job title Research; Development Research\; Development
A backslash in a department code Ops\Field Ops\\Field
A line break typed into a title Director⏎EMEA Director\nEMEA
A backslash already sitting before a semicolon a\;b a\\\;b

Order matters, and the last row shows why: the backslash is handled first, so the backslashes the escaper itself inserts are never doubled in turn. The engine's tests lock this in with hostile inputs, including names with commas. The URL: line is the one exception — validated, not escaped, because a web address is written as entered.

Phone numbers become E.164

The TEL line does not necessarily contain what you typed: the builder normalises the number to E.164, the global-number form of the tel: convention in RFC 3966 — a plus sign followed by the country code and number. Its own refusal states the range: "An international number has 7–15 digits and cannot start with 0 after the country code prefix." Spaces, dots, dashes and parentheses are dropped, and a leading 00 is treated as an alias for the plus — the most complete form of the number, as the ZXing Barcode Contents wiki the engine cites for tel: advises. Three fictional inputs and the line each one produces:

Typed Written into the card
+44 20 7946 0123 TEL;TYPE=CELL:+442079460123
0044 (20) 7946-0456 TEL;TYPE=CELL:+442079460456
+1 (555) 010-2233 TEL;TYPE=CELL:+15550102233

A number with no country code is refused rather than guessed. The same normaliser serves the phone call and SMS generators, covered in the guide to mailto:, tel: and sms: payloads.

What the builder refuses to encode

Validation is per field, so the generator can point at the input that is wrong. Four inputs and the message each returns, read from the builder at build time — if it ever stopped refusing one of them, this page would fail to build:

  • No name at all: "Enter at least a first or last name."
  • A phone number without a country code: "Include the country code in international format, e.g. +1 555 123 4567."
  • An email address with no domain ending: "This is not a valid email address."
  • A website typed without its protocol: "Enter a full URL including https://."

A first name alone or a last name alone is enough; only a card with neither is refused.

Format, not truth

The engine validates format, not truth. It cannot know whether the number is reachable, whether mail to the address arrives, or whether the organisation exists. A number with one wrong digit encodes into an equally flawless symbol; only the phone call fails. The website line is confirmed to be an http or https URL and nothing more — the methodology page is explicit that it cannot know whether a URL is safe.

Two further limits follow from the code being static. The data lives in the image, with no redirect through our servers, so the code never expires and cannot be held hostage — but it cannot be edited after printing, and scans cannot be counted. The fix for a changed number is a new code; the trade-off is laid out in the guide to static and dynamic QR codes. Treat a freshly generated code as untested until a real device has scanned it and the saved contact has been checked.

Reading the symbol facts

Under every worked example the engine reports three facts: the version, the size in modules and the error correction level. The encoder chooses the version for the payload, and the side length in modules follows from it. Payload length drives the version: a longer payload needs at least as high a version — here, a higher one:

Card Bytes Version Modules EC level
Name and number only 112 7 45×45 M
Full card 218 11 61×61 M
Full card at error correction H 218 15 77×77 H

The same 218 bytes rendered at level H instead of M climb from version 11 to version 15 — the cost of a higher recovery level, paid in symbol size. The reported level can also exceed the requested one — the engine documents that it may be boosted when that does not increase the version — and the name-only card requested at level L comes back at level M, version 5. Versions and levels are the subject of the guide to how a QR code encodes data.

Three more symbol rules apply to a contact card as to every code:

  • The quiet zone is part of the symbol. The renderer adds a white border of 4 modules on every side, as ISO/IEC 18004 requires, into every download; it cannot be disabled, and cropping it is the most common self-inflicted scan failure. The PNG presets are 512, 1024, 2048 pixels square and print at 1.71 in × 1.71 in (43.3 mm × 43.3 mm); 3.41 in × 3.41 in (86.7 mm × 86.7 mm); 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI — see the quiet zone and print size.
  • A logo forces error correction H. A centre logo destroys modules that error correction must rebuild, so the engine encodes at the highest level with one present and caps the logo at 16% of the symbol area — a decode-tested cap, not one derived from the theoretical ~30% EC-H recovery ceiling. See error correction and logos.
  • Contrast is computed. The style panel evaluates the colour pair with the WCAG relative-luminance formula and warns below 4.5:1, with a separate warning for light-on-dark inverted codes, which many scanners reject. See contrast and inverted codes.

Common misreadings

  • Trimming the white border. The 4-module quiet zone looks like empty margin and is not; place the code with the border intact.
  • Inverting the colours. Light modules on a dark background trigger the engine's separate warning for a reason: many scanners reject such codes.
  • Treating a clean scan as proof the data is right. A wrong digit or a misspelt address encodes perfectly; only a real scan and a look at the saved contact catches it.
  • Hand-assembling the payload. Pasting a vCard built elsewhere into the plain text generator bypasses the escaping, normalisation and CRLF handling above.

vCard QR code FAQ

Why does the generator use vCard rather than MECARD?

The methodology page records the choice: vCard was chosen over MECARD for broader field support and better scanner coverage. The builder emits vCard 3.0 as specified in RFC 2426 from the seven fields it accepts: the two name parts go into the N: and FN: lines and each remaining field on a line of its own.

Can I change the contact details after the code is printed?

No. Every code on this site is static: the vCard text lives inside the image, with no redirect through our servers, so it never expires and cannot be held hostage — but nothing can be edited after printing. If a number or address changes, generate and print a new code.

Why does the phone number need a country code?

The builder normalises every number to E.164, the global-number form of RFC 3966: a plus sign followed by the country code and number. Its own refusal states the range: "An international number has 7–15 digits and cannot start with 0 after the country code prefix." Spaces, dots, dashes and parentheses are accepted, and a leading 00 stands in for the plus, but a number with no country code is refused rather than guessed.

What happens to commas and semicolons in a name or company?

They are escaped automatically. RFC 2426 reserves the backslash, the semicolon, the comma and the line break inside a text value, so the builder doubles a backslash, puts a backslash before each semicolon and comma, and turns a typed line break into the two characters backslash-n. The round-trip suite confirms the rendered symbol decodes back to the escaped payload byte for byte.

Can the card include a photo?

No. The builder has no photo field. The optional logo in the style panel belongs to the symbol layer, not the payload: it decorates the image, forces error correction level H and is capped by the engine at a decode-tested fraction of the symbol area. It never becomes part of the contact data.

Does the generator check that the number or email address is real?

No. The engine validates format, not truth. It can confirm that a number has a country code or that an address has the shape of an email address, but not whether the number is reachable or the address exists. Treat a freshly generated code as untested until a real device has scanned it and the saved contact has been checked.

Build a card with the vCard QR code generator; it runs in your browser, and nothing you type is transmitted to us unless you choose to save a code to an account. How the encoding is built and verified is on the methodology page.