Methodology & Accuracy
Every generator on GenerateQRCodes.net is powered by a small, tested encoding engine. This page documents the payload formats, the conventions we chose, and — just as importantly — the limits of what a QR code can guarantee, so you know how far to trust a scan.
This page is the reference; the guides are the long form. How a QR code encodes data covers the two layers and the version, module and error-correction facts the engine reports; Error correction and logos the forced level H and the decode-tested logo cap; Quiet zone and print size the border and the PNG presets; Contrast and inverted codes the WCAG formula and its two warnings; The WiFi QR payload format, vCard contact QR codes and URI schemes: mailto, tel, sms and WhatsApp the three payload families in depth; and Static vs dynamic QR codes the trade-offs of the static-only stance. The guides index lists them all. Where a guide and this page overlap, both are computed from the same engine at the same build, so the computed facts they share cannot disagree with each other or with the generators.
Two layers: payload and symbol
A QR code has two independent layers. The payload is the text encoded inside — a
URL, a WIFI: string, a vCard. The symbol is the black-and-white matrix
itself, produced according to the QR standard (ISO/IEC 18004) with built-in error-correction. Our
engine builds the payload exactly right, and the symbol is produced by a vendored copy of the reference
QR encoder (Project Nayuki's qrcodegen), kept byte-comparable with upstream. Most "my code doesn't
work" problems are payload problems, which is why the payload layer is where most of our testing effort
goes.
Symbol rules the renderer enforces
- Quiet zone, always. The 4-module white border required by the standard is rendered into every download and cannot be disabled — cropping it is the most common self-inflicted scan failure.
- Logo overlays force error correction H. A center logo destroys modules that error correction must rebuild, so the engine caps the logo at 16% of the symbol area and always encodes at the highest recovery level when one is present. The 16% cap is decode-tested — our round-trip suite verifies a worst-case logo at that size still scans — not derived from the theoretical ~30% EC-H recovery ceiling, which a centered logo exhausts well before 30% of the area.
- Contrast is computed, not guessed. The style panel evaluates your color pair with the WCAG relative-luminance contrast formula and warns below 4.5:1, and warns separately for light-on-dark (inverted) codes, which many scanners reject.
Symbol constants
The rules above are driven by a handful of named constants in the renderer. They are printed here by reading the renderer at build time, not by copying them into the prose, so a change to the engine would change this table in the same build.
| Constant | Value | What it governs |
|---|---|---|
QUIET_ZONE_MODULES | 4 modules | The white border on each side of the symbol, always rendered, never disableable. |
LOGO_AREA_CAP | 16% of the symbol area | The hard cap on the knockout square a logo may occupy. A request above it is rejected, not clamped, so the caller learns the code would be unreliable. |
LOGO_AREA_DEFAULT | 12% of the symbol area | The knockout area used when none is requested, held below the cap so real-world prints keep decode headroom. |
CONTRAST_WARNING_THRESHOLD | 4.5:1 | The WCAG contrast ratio below which the style panel warns. |
PRINT_DPI | 300 DPI | The resolution the download panel's print-size readout is computed for. |
PNG_SIZE_PRESETS | 512, 1024, 2048 px | The square PNG sizes the export offers. |
The logo area is measured against the symbol excluding the quiet zone, and it is the whole knockout square — the image plus its pad — that counts, because that is everything which obscures modules. The print-size readout is arithmetic on the preset: the pixel width divided by 300 gives inches, and inches times the millimetres in an inch gives millimetres. The engine's own readout for each preset reads:
-
512 px— 1.71 in × 1.71 in (43.3 mm × 43.3 mm) at 300 DPI -
1024 px— 3.41 in × 3.41 in (86.7 mm × 86.7 mm) at 300 DPI -
2048 px— 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI
A readout is a size, not a scanning guarantee: the preset changes the number of pixels, never the number of modules, so a longer payload prints smaller modules at the same preset. The contrast check is likewise a formula, and three colour pairs show what it reports. Black on white assesses at 21.00:1 with no warnings. White modules on a near-black background (#ffffff on #1a1a1a) assess at 17.40:1, above the threshold, and the engine still warns: "Light-on-dark (inverted) codes are not reliably read by all scanners. Prefer dark modules on a light background." Mid-grey on white (#8a8a8a on #ffffff) assesses at 3.45:1, and the panel reports: "Low contrast (3.45:1). Scanners may fail below 4.5:1 — darken the foreground or lighten the background." Contrast is necessary; it is not sufficient.
Payload conventions
- WiFi. The de-facto
WIFI:T:<auth>;S:<ssid>;P:<password>;;convention, with the special characters\ ; , : "backslash-escaped so punctuation in a password cannot corrupt the code. - Contact cards. vCard, chosen over MECARD for broader field support and better scanner coverage.
- Email, SMS, phone, locations. The standard URI schemes (
mailto:,sms:,tel:,geo:) with proper percent-encoding of user-supplied text. - Links. Encoded exactly as entered after validation — we never wrap your URL in a shortener or tracking redirect.
Each generator's page states the exact convention it uses and any scanner caveats.
Payload conventions by type
The engine has one builder per content type, ten in all. Each is a pure function that validates its fields and returns either the exact string handed to the encoder or a list of per-field errors. The builder's header records the specification each format was checked against, and those sources were verified against the live documents on 2026-07-20. The subsections below name each builder, its source, and what its escaping does, on inputs invented for this page; the symbol beside each payload is rendered by the same engine, and the version, module count and error-correction level under it are read back from the encoder. The builder messages quoted are the engine's own, printed at build time.
URL — buildUrlPayload
Per the ZXing "Barcode Contents" wiki, the barcode encodes exactly the text of the URL, and the
protocol must be included so readers recognize it as a URL. The builder therefore validates and
never rewrites: it trims the input, requires a scheme, parses the result as a URL, accepts only
http and https, and returns the trimmed text unchanged. This page
checks at build time that the payload below is byte-identical to the input. A link typed without
its protocol is refused with "Include the protocol (e.g. https://) so scanners recognize the link." and a non-web scheme with "Only http:// and https:// links are supported."
Typed https://example.com/menu?table=4&lang=en, the builder returns:
https://example.com/menu?table=4&lang=en
Version 3, 29×29 modules, error correction
M. The query string's ?, = and & are part
of the URL and pass through untouched; nothing is shortened or redirected.
Plain text — buildTextPayload
The text builder has no convention to follow: it passes arbitrary text through, and the QR byte segment carries it as UTF-8. The only validation is that the text is not empty. Nothing is escaped, because there are no delimiters for a character to collide with, and this page checks at build time that the payload is the input.
A fictional table note:
Table 4 — reserved until 19:30 (ask at the desk)
Version 4, 33×33 modules, error correction M. 48 characters in, 48 characters out; the dash and the colon are ordinary text here.
WiFi — buildWifiPayload
The WIFI: convention is not an RFC; the builder follows the ZXing wiki's example
WIFI:T:WPA;S:mynetwork;P:mypass;; and its escaping rule, under which \,
;, ,, " and : are each prefixed with a backslash.
The engine's escapeWifi handles the backslash first so an already-escaped character is
never processed twice, an ordering locked by a test. Everything else passes through, spaces included:
| Typed value | As it appears in the payload |
|---|---|
one;two | one\;two |
k,v | k\,v |
12:30 | 12\:30 |
say "hi" | say \"hi\" |
a\b | a\\b |
Harbour Loft | Harbour Loft |
Network Harbour Loft, WPA2, password tea;cake:4,"ok" — chosen
because it contains every escaped character at once — produces:
WIFI:T:WPA;S:Harbour Loft;P:tea\;cake\:4\,\"ok\";;
Version 4, 33×33 modules, error correction M. The space in the name is untouched; every delimiter in the password gained a backslash.
Three further rules from the same source and the builder's header. The form offers four choices
— WPA/WPA2 as a single option, WPA3, WEP and an open network — and the engine's
WifiSecurity type accepts a bare WPA value as well, but the convention defines
only the tokens WEP,
WPA and nopass, so WPA2 and WPA3-Personal both map to
T:WPA — the joining device negotiates the strongest protocol itself, and emitting a nonstandard
token breaks parsers, so the engine never produces one (test-locked). An SSID or password consisting
entirely of hex digits is enclosed in double quotes so readers take it as literal ASCII rather than
hex-encoded bytes; the quotes are added after escaping and never escaped themselves. And an open network
drops the P: field entirely, while a hidden network appends H:true;.
Network DECAF with the password
CAFE2024 and WPA3 selected encodes as WIFI:T:WPA;S:"DECAF";P:"CAFE2024";;; the
hidden open network Reading Room encodes as WIFI:T:nopass;S:Reading Room;H:true;;.
Contact card — buildVCardPayload
The builder writes vCard 3.0 per RFC 2426: BEGIN, VERSION:3.0,
N, FN, TEL, EMAIL, ORG,
TITLE, URL and END, one content line per filled-in field,
with CRLF line delimiters. Every content line including the last is CRLF-terminated (RFC 2426
§2.4.2), so the payload ends with a line ending the boxes on this page cannot show. In text
values the backslash, the semicolon, the comma and the line break are reserved, and the engine's
escapeVCardText prefixes the first three with a backslash and flattens a line break to
the two characters \n, backslash first:
| Typed value | As it appears in the payload |
|---|---|
Quill, Fern & Sons; Ltd | Quill\, Fern & Sons\; Ltd |
Finance\Audit | Finance\\Audit |
Floor 2↵North wing | Floor 2\nNorth wing |
↵ marks a line break typed in the field.
An invented card for Mira Quill encodes as:
BEGIN:VCARD VERSION:3.0 N:Quill;Mira;;; FN:Mira Quill TEL;TYPE=CELL:+15551234567 EMAIL:mira@example.com ORG:Quill\, Fern & Sons\; Ltd TITLE:Head of Maps END:VCARD
Version 9, 53×53 modules, error correction M. 9 content lines and 172 characters, of which 18 are line endings. The ampersand in the organization needs no escaping; the comma and the semicolon do.
The structured N line keeps the component order the RFC fixes — family name, given name,
then three components this generator leaves empty — which is why a run of semicolons is part of the
format rather than a defect. The phone number is normalized to E.164 by the same routine the phone
builder uses, the email is checked for the shape of an address, and a URL passes through the URL builder's
validation and is written as the trimmed text, unchanged.
Email — buildEmailPayload
A mailto: URI per RFC 6068 and the ZXing wiki. The subject and body become query parameters,
and because encodeURIComponent leaves the sub-delims !,
', (, ) and * bare even though RFC 3986 reserves
them, the engine's strictEncodeURIComponent encodes those too, so every emitted character
outside the unreserved set is percent-encoded. RFC 6068 §2 also requires percent-encoding %, &, ; and = inside the address itself; the builder does
that in a single pass so a literal % is encoded exactly once. An address typed as front-desk&bookings@example.com is written as
mailto:front-desk%26bookings@example.com.
To bookings@example.com, subject Table for 2?, body
Hello! Window seat (if free) *please*.:
mailto:bookings@example.com?subject=Table%20for%202%3F&body=Hello%21%20Window%20seat%20%28if%20free%29%20%2Aplease%2A.
Version 7, 45×45 modules, error correction
M. The spaces, the question mark, the exclamation mark, the parentheses and
the asterisks are all percent-encoded; the address keeps its @.
Phone — buildPhonePayload
A tel: URI per RFC 3966 carrying an E.164 global number, following the ZXing wiki's guidance
to use the most complete version of the number possible. The normalizer accepts spaces, dots, dashes
and parentheses and removes them, accepts the 00 international prefix as an alias for
+, and requires a plus sign followed by seven to fifteen digits whose first digit
is not zero. A number without a country code is refused rather than guessed: "Include the country code in international format, e.g. +1 555 123 4567." Letters are refused with "Use only digits, spaces, dashes, and parentheses." Typed as
001 555 123 4567, the number encodes as tel:+15551234567 — the same payload
as the example below.
Typed +1 (555) 123-4567, the builder returns:
tel:+15551234567
Version 2, 25×25 modules, error correction Q. Parentheses, the dash and the spaces are gone; only the plus sign and the digits remain.
SMS — buildSmsPayload
An sms: URI per RFC 5724 in its ?body= form, with the number normalized
exactly as the phone builder does. The body uses the strict RFC 3986 encoding, because the RFC 5724
grammar does not allow bare sub-delims such as ! or ' in the body value:
the text Hello! Table for 2? (window seat) becomes Hello%21%20Table%20for%202%3F%20%28window%20seat%29. The SMS generator page
surfaces the engine's own disclosure next to the result, quoted here verbatim: "Pre-filled message support varies by platform: some phones open the composer with the text, others ignore it. The number always works."
To +1 555 123 4567 with the body above:
sms:+15551234567?body=Hello%21%20Table%20for%202%3F%20%28window%20seat%29
Version 5, 37×37 modules, error correction M.
WhatsApp — buildWhatsAppPayload
A click-to-chat link, https://wa.me/ followed by the number, per the WhatsApp Help Center
rule the builder's header quotes: omit any zeroes, brackets or dashes when adding the phone number
in international format, with pre-filled text via ?text= as URL-encoded text. The builder
normalizes the number to E.164 and then drops the plus sign, because wa.me takes digits only, and
encodes the text with encodeURIComponent rather than the strict variant — so the same
message that the SMS builder percent-encodes in full keeps its
! and parentheses here. Compare the two payloads.
To +1 555 123 4567 with the same text:
https://wa.me/15551234567?text=Hello!%20Table%20for%202%3F%20(window%20seat)
Version 5, 37×37 modules, error correction M. The payload is an ordinary https link, which is why it — like a URL code — can be shared as text rather than as an image.
Location — buildGeoPayload
A geo: URI per RFC 5870 (WGS-84), in the geo:<lat>,<lng>
form shown on the ZXing wiki. Latitude must lie within ±90 and longitude within ±180, and both are
emitted at six decimal places with trailing zeros trimmed. A latitude beyond the pole is refused:
"Latitude must be between -90 and 90." The coordinates below are arbitrary values chosen to show the rounding,
not a place.
Latitude 12.3456789, longitude -98.76543:
geo:12.345679,-98.76543
Version 2, 25×25 modules, error correction M. The latitude was typed with more decimals than the builder keeps and was rounded; the longitude already had fewer and is emitted unchanged, with no zeros padded.
Calendar event — buildEventPayload
A VEVENT as shown on the ZXing wiki, wrapped in BEGIN:VCALENDAR per RFC
5545, with DTSTART and DTEND in UTC basic format and CRLF line endings.
The summary, location and description are escaped by the same
escapeVCardText the contact builder uses, so the semicolon and comma in the title and
the line break in the description are handled identically. The end must be after the start; an event
that ends when it starts is refused with "The end must be after the start." The builder's header states a
caveat the event generator must surface, quoted here verbatim: "Calendar QR codes are less reliable than other types: native camera app support is inconsistent, and some phones show the raw text instead of an add-to-calendar prompt. Test on the devices your audience uses."
An invented evening titled Open evening; maps, tea & cake, starting at
2026-11-14T18:00:00.000Z and ending two hours later:
BEGIN:VCALENDAR VERSION:2.0 PRODID:-//GenerateQRCodes.net//QR Event//EN BEGIN:VEVENT SUMMARY:Open evening\; maps\, tea & cake DTSTART:20261114T180000Z DTEND:20261114T200000Z LOCATION:Reading Room\, Floor 2 DESCRIPTION:Bring a map\nand a friend END:VEVENT END:VCALENDAR
Version 12, 65×65 modules, error correction
M. The start is written as 20261114T180000Z:
the dashes and colons of the ISO form removed, a trailing Z marking UTC.
The symbol facts, side by side
Under every example are three numbers from the encoder. The version is the size class it chose; the module count is the symbol's width and height in modules, not counting the quiet zone; and the error-correction level is the one actually encoded. Every example on this page requests level M, the generator's default without a logo, and the level reported is never lower than requested but can be higher, because the reference encoder raises the level when doing so does not increase the version. The table collects the ten examples; payload length drives the version, and the version drives the module count. The shortest payload here, the phone at 16 characters, is version 2; the longest, the calendar event at 280 characters, is version 12.
| Type | Payload length | Version | Modules | Error correction |
|---|---|---|---|---|
| URL | 40 characters | 3 | 29×29 | M |
| Plain text | 48 characters | 4 | 33×33 | M |
| WiFi | 50 characters | 4 | 33×33 | M |
| vCard | 172 characters | 9 | 53×53 | M |
| 118 characters | 7 | 45×45 | M | |
| Phone | 16 characters | 2 | 25×25 | Q |
| SMS | 73 characters | 5 | 37×37 | M |
| 76 characters | 5 | 37×37 | M | |
| Location | 23 characters | 2 | 25×25 | M |
| Calendar event | 280 characters | 12 | 65×65 | M |
Static codes only
Every code is static: the data lives in the image itself, with no redirect through our servers. Static codes never expire and cannot be held hostage — but they also cannot be edited after printing, and scan counts cannot be tracked. We consider that the right default and state it plainly rather than selling the alternative.
What the generators do not verify
The engine validates format, not truth: it cannot know whether a WiFi password is correct, a phone number reachable, or a URL safe. Error correction helps a damaged code scan, but print size and contrast still matter. Treat a freshly generated code as untested until a real device has scanned it.
The examples above make the limit concrete. A wrong WiFi password encodes exactly as well as the right one, and the symbol facts are identical; a phone number with two digits transposed normalizes cleanly to E.164; a mailbox that does not exist has the shape of an address. Nothing in a payload tells the engine whether it is true, and nothing in the symbol can. Three misreadings follow from forgetting this. Cropping the 4-module quiet zone, or printing the code flush against artwork, removes the border the standard requires around the symbol. Inverting the colours can pass the contrast threshold and still draw the inverted warning, as the readouts above show. And a code that scans cleanly and then fails to join, dial or open is not evidence of an encoding fault — check the inputs against their source before suspecting the symbol.
Worked examples can't drift
The example payloads in each page's explanation are computed at build time by the very same engine that runs the interactive generator, not typed in by hand. If the encoding logic ever changed, the copy would change with it (or the build would fail), so the documentation cannot silently disagree with the tool.
This page holds itself to the same rule. Every payload, version, module count, contrast ratio, print-size readout and quoted builder message above is produced by calling the engine when the site is built; the identity of the URL and text payloads with their inputs is asserted at build time, and every refusal quoted is a real rejection the builder returned. A worked example that cannot be produced fails the build rather than shipping a hand-drawn approximation.
Tested against reference payloads — and round-tripped
The engine is covered by automated tests that check its output against independently constructed reference strings — including hostile inputs like passwords full of semicolons and names with commas. On top of that, a round-trip suite renders each payload type to an image, rasterizes it, and decodes it with an independent QR decoder: the decoded text must match the payload byte for byte, including a worst-case run with a logo obscuring the symbol's center. If you believe a generated code is wrong, that is exactly the kind of report we want: see the contact page, and confirmed issues are fixed in the engine and locked in with a new test.
Last reviewed 2026-09-30. The specification sources behind every builder were verified against the live documents on 2026-07-20, as the builder's header records.