explainer
The WIFI: QR Code Format, Explained (WPA3, Hidden Networks, and the Semicolon Problem)
WiFi QR code format explained field by field: WIFI:T:WPA;S:..., backslash escaping, WPA3 and hidden networks, and why the code won't scan on an iPhone.
Point a phone camera at a café’s WiFi card and it offers to join the network. No magic: the QR code holds one line of ASCII that looks like WIFI:T:WPA;S:MyNetwork;P:hunter2;H:true;;, and the phone is doing string parsing. The strange part is that this format wasn’t a standard when every phone started shipping support for it — it began as a proposal on a wiki page in the ZXing barcode project, and the Wi-Fi Alliance only formalized a version of it years later, with different escaping rules than the one everyone actually implemented. That gap is where most “why doesn’t my WiFi QR code work” problems live.
The payload, field by field
Everything after WIFI: is a list of KEY:value; pairs terminated by an extra semicolon — so a well-formed payload always ends in ;;. Order is irrelevant: the ZXing documentation states flatly, “Order of fields does not matter.”
| Field | Example | What it means |
|---|---|---|
T | WPA | Authentication type. ZXing: “can be WEP or WPA or WPA2-EAP, or nopass for no password. Or, omit for no password.” |
S | MyNetwork | ”Network SSID. Required.” |
P | hunter2 | ”Password, ignored if T is nopass (in which case it may be omitted).” |
H | true | ”Optional. True if the network SSID is hidden.” |
E | TTLS | ”(WPA2-EAP only) EAP method, like TTLS or PWD” |
A | anon | ”(WPA2-EAP only) Anonymous identity” |
I | myidentity | ”(WPA2-EAP only) Identity” |
PH2 | MSCHAPV2 | ”(WPA2-EAP only) Phase 2 method, like MSCHAPV2” |
Those quotes are from the ZXing “Barcode Contents” wiki page, the origin of the format. Its own framing is worth noticing: “We propose a syntax like ‘MECARD’ for specifying wi-fi configuration.” Not a spec, not an RFC — a proposal, in a section the wiki heads “Wi-Fi Network config (Android, iOS 11+).”
One landmine: H has history. ZXing notes it “was mistakenly also used to specify phase 2 method in releases up to 4.7.8 / Barcode Scanner 3.4.0. If not a boolean, it will be interpreted as phase 2 method… for backwards-compatibility.” H:true is safe; H:MSCHAPV2 means something else entirely.
Two specs, one string
The Wi-Fi Alliance eventually wrote the URI down properly. Section 7 of the WPA3 Specification (v3.5) is titled “WIFI URI” — “This section defines the URI representation for Wi-Fi credentials using the ‘WIFI’ URI scheme” — and gives an ABNF grammar:
WIFI-qr = "WIFI:" [type ";"] [trdisable ";"] ssid ";" [hidden ";"] [id ";"]
[password ";"] [public-key ";"] ";"
type = "T:" *(unreserved) ; security type
trdisable = "R:" *(HEXDIG) ; Transition Disable value
ssid = "S:" *(printable / pct-encoded) ; SSID of the network
hidden = "H:true" ; hidden (stealth) SSID is used
id = "I:" *(printable / pct-encoded) ; SAE password identifier
password = "P:" *(printable / pct-encoded)
public-key = "K:" *PKCHAR ; SAE-PK, base64 SubjectPublicKeyInfo
printable = %x20-3a / %x3c-7e ; semi-colon excluded
Read the two documents side by side and they are not the same format:
| ZXing wiki (what shipped) | WPA3 Spec §7 (what’s standardized) | |
|---|---|---|
| Escaping | Backslash: \ ; , " : | Percent-encoding — “The semi-colon is excluded from the printable set… and therefore is percent-encoded” |
T values | WPA, WEP, WPA2-EAP, nopass | Only WPA; absent means open |
| WEP | Documented | ”NOTE: This specification does not define usage of the WIFI URI with WEP shared key.” |
| Enterprise | E, A, I, PH2 | Not defined. I: is reused for the SAE password identifier |
| Extras | — | R: (Transition Disable), K: (SAE-PK public key) |
Note the I: collision: in ZXing it’s an 802.1X identity, in the WPA3 spec it’s an SAE password identifier. Parser authors get to guess. The saving grace is the spec’s forward-compatibility rule: “Devices parsing this URI shall ignore semicolon separated components that they do not recognize.”
In practice the ZXing dialect wins, because that’s what the phones implement. Android’s Settings app parses it directly — WifiQrCode.java says it “Supports to parse 2 types of QR code: 1. Standard Wi-Fi DPP bootstrapping information or 2. ZXing reader library’s Wi-Fi Network config format,” and its comments spell out the backslash rule: '\\', ';', ',' and ':' are escaped with a backslash '\\', should remove at first. Android has generated these codes from Settings → Network & internet → Wi-Fi → Share since Android 10; iOS has decoded them in the stock Camera app since iOS 11.
The semicolon problem
Since ; is the field separator, an SSID or password containing one splits the payload in the wrong place unless you escape it. ZXing calls out five characters: “Special characters \, ;, ,, " and : should be escaped with a backslash (\) as in MECARD encoding.”
Their own example — an SSID whose literal value is "foo;bar\baz", double quotes included:
WIFI:S:\"foo\;bar\\baz\";;
A more ordinary case. Network Cafe;Bar, password p@ss\word:
WIFI:T:WPA;S:Cafe\;Bar;P:p@ss\\word;;
The backslash in the password becomes \\, and the semicolon in the SSID becomes \;. Skip the first and the password arrives one character short; skip the second and the phone tries to join a network called Cafe with no password. This is the single most common bug in hand-rolled generators, and the reason to use one that handles it — our WiFi QR Code Generator escapes all five characters in both fields.
There’s a second, weirder rule in the ZXing doc that almost no generator implements. For both S and P: “Enclose in double quotes if it is an ASCII name, but could be interpreted as hex (i.e. "ABCD").” A network literally named ABCD, or a password of DEADBEEF, is ambiguous — ASCII string or hex-encoded key — and the quotes disambiguate. If an all-hex-looking SSID silently fails to join, that’s your first suspect. Our generator doesn’t add the quotes either, deliberately: the rule would also wrap an ordinary name like Cafe (four hex digits), and a reader that doesn’t strip the quotes would then look for a network literally called "Cafe". Rename the network if you can.
Under the WPA3 spec you’d write that same Cafe;Bar as S:Cafe%3BBar instead. Don’t, unless you know the reader. The spec’s own grammar puts % inside the printable set, so a literal percent sign is ambiguous there too. Ship the backslash dialect.
WPA3: there is no T:WPA3
This is the most common misconception, and the spec settles it in one sentence: “If the ‘type’ is present, its value is set to ‘WPA’ and it indicates password-based authentication is used.” That’s it. T:WPA is the value for WPA, WPA2, and WPA3-Personal alike.
The spec’s worked example makes the behavior explicit. For WIFI:T:WPA;S:MyNet;P:MyPassword;;:
- “STA that supports WPA3-Personal might use SAE or PSK (WPA3-Personal Transition Mode)”
- “STA that does not support WPA3-Personal uses PSK (WPA2-Personal)”
The client negotiates whatever the access point offers. You don’t encode it. If you want to force WPA3 and block a downgrade, that’s the R: field — WIFI:T:WPA;R:1;S:MyNet;P:MyPassword;; sets Transition Disable, after which a WPA3-capable client “uses SAE only (WPA3-Personal Only Mode).”
A caveat on T:SAE: some tools emit it, and Android’s parser does define a SECURITY_SAE constant alongside SECURITY_WPA_PSK, SECURITY_WEP, and SECURITY_NO_PASSWORD. It appears in neither the ZXing table nor the WPA3 grammar’s normative text. T:WPA is the portable choice.
The other two values: T:nopass for an open network (omit P: entirely), and T:WEP, which the WPA3 spec pointedly declines to define. Treat WEP as an escape hatch for ancient hardware, not a choice.
Wi-Fi Easy Connect (DPP) is a separate thing with a separate URI — Android’s parser keys off PREFIX_DPP = "DPP:" versus PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:". AOSP’s docs describe it as a public-key bootstrapping protocol added in Android 10 where “Bootstrapping and authentication is configured using a URI, which is acquired by scanning a QR code.” A DPP: code is not a password in a picture; a WIFI: code is.
Hidden networks and H:true
H:true tells the phone the SSID isn’t broadcast. It works, but it’s the flakiest part of the format for a reason that has nothing to do with QR codes: a hidden network doesn’t announce itself in beacon frames, so the client can’t discover it passively. It has to actively probe for that exact SSID and wait for a response — which means slower joins, more failures at the edge of range, and on some devices a network that appears to save but never connects.
Hiding an SSID also isn’t a security control; the name leaks in the probe requests your own devices broadcast looking for it. Generate a longer password instead with the password generator, and see password entropy explained for what that actually buys you.
”The WiFi QR code doesn’t work”
Ranked by how often it’s actually the cause:
- Escaping. An unescaped
;,:,,,", or\in the SSID or password. Decode the code and read the raw string — visible in seconds. - Missing the final
;. The payload must end;;. Hand-rolled generators often emit one trailing semicolon and strict parsers reject it. - iPhone: wrong app. iOS joins networks from the stock Camera app or the Code Scanner control in Control Center. A third-party scanner has to explicitly use
NEHotspotConfiguration; most just show you the decoded text. A wall ofWIFI:T:WPA;S:...instead of a join prompt is the app’s fault, not the code’s. - Non-ASCII SSID. Emoji and accented characters are fine — QR byte mode carries UTF-8 — but generators that build the payload in a non-UTF-8 pipeline mangle them, and the name silently stops matching.
- 6 GHz-only network. The 6 GHz band mandates WPA3 or Enhanced Open; WPA2-Personal isn’t permitted there. A device without WPA3 parses your code perfectly and still fails to associate.
- Enterprise (802.1X).
E/A/I/PH2are Android-side extensions at best and absent from the standardized grammar. Don’t expect cross-platform joins. - Physical problems. Too small, low contrast, cropped quiet zone, damaged finder patterns — generic to all QR codes, and covered in how to scan a QR code from a screenshot along with error-correction levels and why a center logo breaks things.
The part nobody says out loud: it’s plaintext
A WiFi QR code is your password, printed. No encryption, no hashing, no expiry, no revocation. Anyone who photographs the card on the counter — from across the room, at an angle, out of focus — has your PSK forever, and can decode it in a browser with the QR Code Scanner. Try it on your own code and read the string back.
Two consequences worth acting on. Put the code on the guest network, never the primary one, so a leaked PSK reaches a segment with nothing on it. And treat a printed code as a rotation event: when it walks off, change the password and reprint.
What our generator emits
Our WiFi QR Code Generator builds the ZXing dialect, because that’s the one phones read. In order: WIFI:T:<type>;S:<ssid>; then H:true; when the hidden box is checked, then P:<password>; (omitted entirely when the type is nopass), then the closing ;. That ordering matches the WPA3 grammar above; ZXing readers don’t care either way.
Three disclosures, so you know what you’re getting:
- Encryption options are
WPA/WPA2,WEP, andNone (open). The first emitsT:WPA, which — per the WPA3 spec above — is also the correct value for a WPA3-Personal network. There is no separate WPA3 setting because there shouldn’t be one. - No enterprise fields. No
E,A,I,PH2,R:, orK:. If you need 802.1X onboarding, this isn’t the tool. - It renders at 256px with a 2-module margin at the
qrcodenpm package’s default error-correction level,M(~15% recoverable). Fine on a screen or a laminated card, thin for anything that will get scuffed; for those, run the same payload through the QR Code Generator, which exposes L/M/Q/H directly.
Everything happens in the browser — no request carries the password, which matters more than usual for a tool whose entire input is a credential.
Generate one at the WiFi QR Code Generator, then scan the result with the QR Code Scanner before you print it. Reading your own payload back is the fastest way to confirm the escaping is right — and a useful reminder of how little a WiFi QR code is hiding.