· Updated · 12 min read

What's Actually Inside a QR Code: Every Data Format Explained

A QR code stores a string of characters and nothing more. Everything else — joining a network, saving a contact, opening a map — is a convention layered on top, and the two formats people hit problems with most are the two with no standards body behind them. Here is every format, and the escaping rules that quietly break them.

Ask what a QR code can hold and the usual answer is a list: a link, a Wi-Fi password, a contact card, a calendar event, a payment. That list is real, but it describes what scanners do, not what QR codes are. ISO/IEC 18004 defines how to store a string of characters in a grid of black and white squares. It says nothing about Wi-Fi, or contacts, or anything else.

Everything above that layer is convention. A handful of prefixes — WIFI:, MECARD:, geo:, otpauth:// — are recognised by scanners, which then act on the text that follows. Miss the convention by one character and you get a code that encodes perfectly, decodes perfectly, and does nothing at all.

That gap is where most "my QR code does not work" problems actually live, and the Wi-Fi format is where they cluster, because it has escaping rules that almost nobody implements correctly. This is a reference to the formats and a diagnosis of the mistakes.

One problem this article does not cover is character encoding. Escaping is about the separator characters a format reserves; encoding is about whether a character can be stored at all. If your payload comes back with question marks or with é where an accent should be, that is the other problem — see why QR codes mangle accents, emoji and the euro sign.

The Short Answer

  • A QR code stores text, and only text. Formats are conventions layered on top of it.
  • A scanner that does not recognise a prefix shows the raw text instead of acting — the common symptom of a typo in the prefix.
  • In the WIFI: and MECARD: formats, five characters must be escaped with a backslash: \ ; , : ".
  • Escape the backslash first. Doing it last re-escapes the backslashes you have just added.
  • A value that could be read as hexadecimal — Cafe, Beef, Added — should be wrapped in double quotes.
  • A trailing newline, a lower-case prefix or a missing ;; can each break a code on one platform while working on another.

Every Format, and What Starts It

The prefix is the whole mechanism. These are the formats in general circulation, with the standard each one comes from where a standard exists:

Starts withWhat it doesDefined by
https://Opens a web pageRFC 3986. By far the most common payload.
WIFI:Joins a wireless networkNo formal standard. The ZXing project's Barcode Contents document is the de facto specification.
MECARD:Adds a contact, compactlyNTT DoCoMo.
BEGIN:VCARDAdds a contact, fullyRFC 6350 (vCard 4.0); RFC 2426 for 3.0.
BEGIN:VEVENTAdds a calendar eventRFC 5545 (iCalendar).
otpauth://Sets up two-factor authenticationGoogle's Key Uri Format. Widely implemented, never standardised.
mailto:Opens a pre-filled emailRFC 6068.
MATMSG:Opens a pre-filled email (legacy)NTT DoCoMo. Superseded in practice by mailto:.
SMSTO:Opens a pre-filled text messageNTT DoCoMo. Broader scanner support than sms:.
sms:Opens a pre-filled text messageRFC 5724.
tel:Dials a numberRFC 3966.
geo:Opens a map pinRFC 5870.
BCDPre-fills a SEPA bank transferEuropean Payments Council, EPC069-12.
(anything else)Displayed as plain text—

Two things follow from this table that are worth stating plainly. First, the formats with real standards behind them — mailto:, tel:, geo: — are the ones that rarely cause trouble. Second, the two formats people hit problems with most, Wi-Fi and MeCard, are precisely the two with no standards body at all. That is not a coincidence.

The Wi-Fi Format, and Why So Many Generators Get It Wrong

A Wi-Fi payload looks simple:

WIFI:T:WPA;S:Guest Network;P:correct-horse;;

T is the security type, S the network name, P the password, and an optional H:true marks a hidden network. The fields are separated by semicolons and the payload closes with two of them. So far so obvious — and then someone's password contains a semicolon.

The five reserved characters

Because the semicolon is a delimiter, a semicolon inside a value has to be escaped with a backslash, and so do four other characters. The ZXing document lists them as \, ;, ,, : and ".

CharacterWhy it is reservedWhat happens if you do not escape it
;Ends a fieldThe value is cut short at the semicolon and the remainder is stranded as a nameless fragment.
\It is the escape characterIt silently consumes the character after it.
:Separates a field name from its valueUsually tolerated inside a value, but outside the format and not portable.
,Separates repeated values, and surname from forename in MeCardA name or address can split in the wrong place.
"Wraps a value that could be read as hexadecimalA literal quote at the start and end of a value is mistaken for the wrapper and stripped.

So a password of pa;ss has to be encoded as pa\;ss. Without the backslash, the phone reads the password as pa, tries to join, and fails with a wrong-password error that sends people off to check the router.

The ordering trap

This is the subtle one, and it is why hand-rolled encoders break on a minority of passwords rather than obviously and always.

Suppose the password is back\slash;semi — a backslash and a semicolon. An encoder that replaces semicolons first, then backslashes, produces a value in which the backslash it added for the semicolon is itself escaped. The password arrives with literal backslashes in it, and the join fails.

The fix is to escape the backslash before anything else, or — better — to do the whole thing in a single pass with one character class, so nothing the encoder writes can be re-read as input. A single-pass replacement over [\\;,:"] is correct by construction; two sequential passes in the wrong order are not.

The hex rule nobody implements

The format says a value that could be interpreted as hexadecimal should be enclosed in double quotes, so the parser knows it is text. That sounds like an edge case until you notice how many ordinary words are made entirely of the letters a to f: Cafe, Face, Beef, Added, Decade, Faced, Cabbage. A coffee shop calling its network Cafe has a hex-ambiguous SSID, and the correct encoding is S:"Cafe";, not S:Cafe;.

This matters more for passwords than names, because a password of 123456 or deadbeef is both plausible and entirely hexadecimal. In practice most parsers guess right and most people never notice. But "most parsers guess right" is the definition of a bug that surfaces on one person's phone and nobody else's.

There is a second-order subtlety here that even careful implementations miss: the wrapping quotes are the unescaped ones. A network genuinely named "Guest", quotation marks included, is written S:\"Guest\";, and a parser that strips quotes after unescaping will eat them. The ZXing document gives the worked example: an SSID of "foo;bar\baz" encodes as WIFI:S:\"foo\;bar\\baz\";;.

Why the mistakes are so consistent

There is a mundane reason the same errors recur across unrelated generators. The Wi-Fi format has no standards body; the reference everyone copies from is a wiki page. And that page's own rendering has swallowed its backslashes — a bug reported against the ZXing project as issue #1143, which points out that the escaping rule was being displayed without the very characters it was describing. Anyone reading it quickly would come away knowing that some characters need escaping and not which, or with what.

When a format's canonical documentation cannot render its own escape character, a generation of copied implementations gets it wrong in the same way. That is roughly what happened.

The other Wi-Fi details that bite

  • A trailing newline. A text editor that adds one at the end of the file puts it inside the QR code, where it is invisible and breaks the payload on both major platforms.
  • Lower-case prefixes. wifi: is accepted by some parsers and ignored by others. Use WIFI:.
  • WPA3. The documented values for T are WEP, WPA, WPA2-EAP and nopass. Android has accepted SAE for WPA3 since Android 10 and it is widely used, but it is not in the published list, so older parsers may fall back or refuse.
  • The closing ;;. Some parsers read to the end of the string regardless; others drop the final field without it.

You can check any of this against a real payload with the QR code payload decoder, which resolves the escapes, shows what each field actually decodes to, and names the specific rule any problem breaks. If you are creating a code rather than debugging one, the Wi-Fi QR code generator handles the escaping for you, and the step-by-step guide covers the practical side.

MeCard or vCard? The Size Difference Is Larger Than It Looks

Both formats add a contact. vCard is the real standard, carries far more fields, and is what a phone exports. MeCard is a compact single-line format from NTT DoCoMo. For a QR code the difference is not academic, because every extra byte pushes the symbol to a higher version with more, smaller modules.

Here is the same contact — name, mobile number, email address — encoded both ways, measured with a real encoder rather than estimated:

FormatBytesQR version at level MGridModule size at 30 mm wide
MeCard61433 × 330.909 mm
vCard 3.0116745 × 450.667 mm

Printed at the same 30 mm width, the vCard's modules are about 27% narrower. At error correction level H — the level usually chosen when a logo is involved — the gap widens further: version 6 for MeCard against version 10 for the vCard, 41 modules against 57.

The practical reading: on a business card, where the code is small and the contact is simple, MeCard scans more reliably at the same size. Use vCard when you genuinely need its fields — job title, company, address, photo — and then size the code for the data rather than for the layout. Our guide to QR codes on business cards covers the design side, and the capacity calculator will tell you which version your particular contact lands on. If the version numbers themselves are what you came for, QR code versions 1-40 has the full table and the arithmetic that links a version to a printed module size.

Two-Factor Codes: The Payload You Should Not Paste Anywhere

When an app shows a QR code to set up two-factor authentication, the payload is an otpauth:// URI:

otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

The pieces are: the type (totp for time-based, hotp for counter-based), a label of issuer:account, and a query string carrying the parameters. secret is the Base32-encoded key and is required. algorithm defaults to SHA1, digits to 6, and for TOTP period defaults to 30 seconds.

Worth understanding clearly: that secret is the entire credential. It is not a code that expires in thirty seconds — it is the seed every future code is generated from. Anyone who reads it can produce valid codes indefinitely, and unlike a password it cannot be rotated without re-enrolling the account. It is closer to a spare key than to a login.

Which is why the sensible habit is not to paste one into any website, including ours. Our payload decoder parses entirely in your browser with no network request at all — you can load it, disconnect, and it still works — but the general rule is worth keeping regardless, because most tools that look similar do not make that promise.

The Rest, Briefly

geo:

geo:40.71872,-73.98905,100 — latitude, longitude, and optionally altitude in metres. Latitude runs from −90 to 90 and longitude from −180 to 180, so a latitude outside that range almost always means the two have been written the wrong way round. RFC 5870 puts latitude first; a surprising number of mapping APIs do the opposite.

tel:

tel:+12125551212. Use the full international form with the leading +: without a country code the number only dials correctly from inside the same country, which is a poor property for something printed on a poster.

SMSTO: and sms:

Two formats for the same job, and they are not interchangeable. SMSTO:+18005551212:your message takes the message raw; sms:+18005551212:your%20message follows RFC 5724 and percent-encodes it. SMSTO: has the broader reach across scanners, which makes it the safer choice for print.

mailto: and MATMSG:

mailto:someone@example.com?subject=Hello&body=Hi%20there is the standard and the one to use. MATMSG:TO:...;SUB:...;BODY:...;; is the DoCoMo equivalent, uses the same escaping rules as Wi-Fi and MeCard, and is worth recognising when you meet it rather than generating.

BEGIN:VEVENT

An iCalendar fragment. It needs DTSTART to be useful, and times ending in Z are UTC — an event written in local time without a timezone will land in the wrong hour for anyone who scans it elsewhere.

BCD (EPC QR)

The European Payments Council's format for SEPA credit transfers, used by European banking apps. It is positional rather than named: one field per line, in a fixed order, beginning with the service tag BCD. Payment QR codes vary a great deal by country, and this is the European one.

Debugging a Payload That Does Not Work

In rough order of how often each one turns out to be the answer:

  1. Read the decoded text, not the picture. Scan the code with something that shows you the raw string before acting on it. Nearly every diagnosis starts here.
  2. Check for a trailing newline or space. Invisible, and enough on its own.
  3. Check the prefix spelling and case. WIFI: not wifi:; SMSTO: not SMS:.
  4. Look for a value that stops early. A password that decodes to the first half of itself means an unescaped semicolon.
  5. Look for stray backslashes in the decoded value. That is over-escaping, or escaping in the wrong order.
  6. Check for hex ambiguity in the network name and password.
  7. Confirm the terminator. ;; at the end of the escaped formats.
  8. Only then suspect the code itself — size, contrast, error correction. If the text decodes correctly, the symbol is fine, and the problem is in the string.

Steps two to seven are what the payload decoder does automatically. If the payload turns out to be an ordinary web address, the question changes from syntax to destination, and the link checker is the right tool — particularly given how much QR code phishing has grown.

Frequently Asked Questions

What data can a QR code hold?

Text, and nothing else. A QR code stores a string of characters; the formats are conventions that scanners agree to recognise. A string beginning WIFI: is treated as network credentials, one beginning geo: as a coordinate, one beginning otpauth:// as a two-factor secret. A scanner that does not recognise the prefix falls back to showing the raw text, which is exactly what happens when a prefix is misspelled.

Which characters need escaping in a Wi-Fi QR code?

Five: the backslash itself, the semicolon, the comma, the colon and the double quote, each written with a backslash in front of it. The semicolon matters most because it is the field separator, so an unescaped one inside a password ends the field early and the phone tries to join with a truncated password. The same five characters apply to the MeCard and MATMSG formats.

Why does my Wi-Fi QR code work on Android but not iPhone?

Usually a detail the two parsers disagree about rather than a broken payload. Lower-case prefixes, a missing closing double semicolon, a trailing newline left by a text editor, and WPA3 written as SAE are all handled inconsistently. None of them produces an error message; the code simply does nothing on one platform and works on the other, which is why it reads as a phone problem rather than a payload problem.

Why should an SSID like Cafe be wrapped in quotes?

Because every letter in it is a hexadecimal digit, so a parser is entitled to read it as a hex-encoded byte string rather than as text. The Wi-Fi format says any value that could be interpreted as hex should be enclosed in double quotes to mark it as ASCII. Cafe, Face, Beef, Added and Decade all qualify, and so does any all-numeric password.

What is the difference between MeCard and vCard?

MeCard is a compact single-line format from NTT DoCoMo; vCard is the full standard defined in RFC 6350, written as multiple lines. Both add a contact, but vCard costs far more characters for the same information. For a name, mobile number and email address, MeCard encodes in 61 bytes against the vCard's 116, which is the difference between a 33-module symbol and a 45-module one at the same error correction level.

Is it safe to paste a 2FA QR code into an online decoder?

As a habit, no. The secret inside an otpauth:// payload is the seed from which every future code is generated, not a code that expires, and it cannot be rotated without re-enrolling the account. Any tool that processes it on a server has seen a long-lived credential. Tools that parse entirely in the browser do not, and you can verify that claim by loading the page, disconnecting, and checking it still works.

Why does my QR code show text instead of doing something?

Because the prefix did not match anything the scanner recognises, so it fell back to displaying the raw string. The usual causes are a misspelled scheme, a lower-case prefix where an upper-case one was expected, a leading space or byte-order mark inserted by a text editor, or a format the particular scanner has never supported. The payload is intact; it just does not trigger an action.

Does a longer payload make a QR code harder to scan?

Yes, and the effect is often underestimated. More data means a higher QR version, and each version adds four modules to each edge. Printed at a fixed width, the modules shrink in proportion, and a camera has to resolve individual modules rather than the code as a whole. Choosing MeCard over vCard, or shortening a URL, is frequently the cheapest way to make a small printed code scan reliably.

The Short Version

A QR code is a container for text. The formats are an agreement about what certain text means, and the ones that cause trouble — Wi-Fi and MeCard — are the ones with no standards body and an escaping scheme that is easy to implement almost correctly.

Escape five characters, do the backslash first, quote anything that looks like hex, close with two semicolons, and check for the newline your editor added. Then read the decoded text rather than trusting the generator, because a payload that is subtly wrong looks exactly like one that is right until somebody tries to scan it.

Check a Payload Before You Print It

Paste the decoded text into the payload decoder and it resolves every escape, shows what each field really contains, and names the rule behind anything that is wrong. It runs entirely in your browser.

Read the Text Before Your Phone Acts on It

Every diagnosis in this article starts with seeing the decoded string. Scan & Generate shows you the payload of any code before it joins a network or opens anything — which is also the safest way to scan a code you did not print yourself.