ZXing vs MECARD WiFi QR Code Syntax: Protocol Specification & Parser Edge Cases.
The authoritative technical specification for the WIFI: URI format. Master character escaping, UTF-8 boundary handling, hex passphrases, and cross-platform mobile parser compatibility.
- Standard Grammar: The global standard string format is
WIFI:S:<SSID>;T:<WPA|WEP|nopass>;P:<password>;H:<true|false>;;terminated with a double semicolon (;;). - Mandatory Escaping: Five special delimiter characters (
\,;,:,,,") MUST be preceded by a backslash (e.g.\;). Failing to escape causes premature token termination and password corruption. - Hidden SSIDs: The flag
H:true;instructs mobile wireless daemons to transmit active directed 802.11 probe requests instead of passively listening for beacon frames. - Origin: Created by Sean Owen in Google's open-source ZXing ("Zebra Crossing") library, adapted from NTT DoCoMo's MECARD contact card format.
1. NTT DoCoMo MECARD vs ZXing Lineage
In the early 2000s, Japanese telecommunications giant NTT DoCoMo established the MECARD format for encoding contact cards into 2D barcodes on flip phones. MECARD used semicolon-delimited key-value tags: MECARD:N:Smith,John;TEL:5551234;;.
In 2008, Sean Owen and the contributors of Google's open-source ZXing ("Zebra Crossing") barcode image processing library borrowed this structural convention to create a lightweight, human-readable Wi-Fi provisioning string prefixed with WIFI:.
When Apple added native camera QR code scanning in iOS 11 (2017) and Google integrated Google Lens / Play Services Barcode scanning into Android, both operating systems adopted the ZXing WIFI: grammar rather than creating competing proprietary schemes.
2. Formal ABNF Grammar Specification
The formal Augmented Backus-Naur Form (ABNF) grammar governing valid Wi-Fi QR strings is defined as follows:
Notice the mandatory terminating double semicolon (;;). While some modern fault-tolerant parsers accept a single trailing semicolon, standards-compliant encoders must always output double semicolons to prevent parser overrun.
3. Character Escaping & Delimiter Collision
The most prevalent bug in amateur QR code generators is the failure to escape reserved delimiters. If your Wi-Fi password contains a semicolon (e.g. Secret;Password!), an unescaped string produces:
The parser reads P:Secret, encounters the unescaped ;, and treats Password! as an unrecognized parameter key, corrupting the password and causing handshake authentication rejection.
The Escaping Standard: Five characters must be preceded by an escaping backslash (\):
\;— Escaped semicolon\:— Escaped colon\\— Escaped backslash\,— Escaped comma\"— Escaped double quotation mark
4. Hex Keys, Hidden SSIDs & UTF-8 Bytes
64-Character Hexadecimal Pre-Shared Keys
Standard WPA2 ASCII passphrases span 8 to 63 characters. However, IEEE 802.11i also allows a raw 256-bit Pairwise Master Key expressed as exactly 64 hexadecimal characters ([0-9a-fA-F]64). When encoding hex keys, do not surround them with quotes.
UTF-8 Multibyte SSIDs & Emojis
IEEE 802.11 allows SSIDs up to 32 octets (bytes). If an SSID contains UTF-8 emojis or accents (e.g. Café ☕), ensure the QR code is generated with UTF-8 byte encoding mode (ECI 000026) rather than ISO-8859-1.
Hidden SSID Probe Directive (H:true)
Routers with SSID broadcast disabled do not announce their presence in beacon frames. Supplying H:true; instructs the client to transmit active directed probe requests for that SSID.
5. Platform Parser Differences (iOS vs Android)
| Behavior / Edge Case | Apple iOS (AVFoundation) | Android (Play Services / ML Kit) |
|---|---|---|
| Unescaped Semicolon | Truncates password, auth fails | Truncates password, auth fails |
| Missing Trailing Semicolon | Leniently accepted | Leniently accepted |
| T:WPA3 Support | Supported (iOS 15+) | Supported (Android 10+) |
| Open Network String | Accepts T:nopass & omitted P: | Accepts T:nopass;P:; |
6. Reference TypeScript Serialization Code
Below is the standards-compliant TypeScript serializer implemented natively inside WiFi QR Generator's client-side engine:
function escapeZxing(str: string): string {
return str.replace(/([\\;:,"])/g, '\\$1');
}
export function formatWifiPayload(
ssid: string,
password?: string,
authType: 'WPA' | 'WEP' | 'nopass' | 'WPA3' = 'WPA',
hidden: boolean = false
): string {
const cleanSsid = escapeZxing(ssid);
let payload = `WIFI:S:${cleanSsid};T:${authType};`;
if (authType !== 'nopass' && password) {
const cleanPass = escapeZxing(password);
payload += `P:${cleanPass};`;
}
if (hidden) {
payload += 'H:true;';
}
payload += ';';
return payload;
}