DEV Community

Ashish Kumar
Ashish Kumar

Posted on

The WiFi QR Code Format Decoded: Build One Yourself in 30 Lines

You've scanned one a hundred times. The cafe printed a QR code, you pointed your phone at it, and you joined their WiFi without typing the 32-character password. What's actually encoded in that little black-and-white square?

The answer is shorter than you'd guess. The WiFi QR format is one of the cleanest specs you'll find — six fields, plain text, no binary encoding, no cryptography. You can build a generator from scratch in thirty lines of JavaScript, which we'll do at the end of this post.

But there are gotchas. Special characters in passwords break naive implementations. iOS and Android handle the same QR slightly differently. And the WPA3 transition is starting to break older codes in subtle ways.

The format

A WiFi QR code is just a text string in this shape:

WIFI:T:WPA;S:NetworkName;P:password123;;
Enter fullscreen mode Exit fullscreen mode

That's the entire format. The QR code itself is just standard text encoding (any QR code library handles it); the magic is in the string format. Six possible fields, separated by semicolons:

Field Meaning Required?
T Authentication type Yes
S SSID (network name) Yes
P Password Required for WPA/WEP
H Hidden network (true/false) Optional
E EAP method (for enterprise) Optional
I, A Identity, anonymous identity (enterprise) Optional

The WIFI: prefix tells the scanner "this isn't a URL or plain text — treat it as a WiFi config." Two trailing semicolons close the structure.

The auth types

The T: field accepts:

  • WPA — covers WPA, WPA2, and WPA3 (most home and business networks)
  • WEP — legacy, broken since 2007, you shouldn't see it but you will
  • nopass — open networks, no password (P field omitted)

There's no separate WPA2 or WPA3 — they all go under WPA. The actual encryption negotiated is whatever the access point supports; the QR just tells the device "expect a password-protected network."

Examples:

Open coffee-shop WiFi:
WIFI:T:nopass;S:Cafe Free WiFi;;

Standard home network:
WIFI:T:WPA;S:MyHomeWiFi;P:correcthorsebatterystaple;;

Hidden network:
WIFI:T:WPA;S:OfficeNet;P:s3cret;H:true;;
Enter fullscreen mode Exit fullscreen mode

The escaping rules (where naive implementations break)

Here's where it gets interesting. Five characters are special and must be backslash-escaped if they appear in the SSID or password:

  • \ (backslash) — escape character itself
  • ; (semicolon) — field separator
  • , (comma) — used in some implementations
  • : (colon) — field key/value separator
  • " (double quote) — used to wrap special values

Common case: passwords with semicolons. MyP;ssword must be encoded as MyP\;ssword in the QR. Without the escape, the parser thinks the password is MyP and ssword is a new field.

Here's a real escape function:

function escapeWifi(value) {
  return value.replace(/[\\;,:"]/g, '\\$&')
}
Enter fullscreen mode Exit fullscreen mode

The $& in the replacement is the matched character itself, prefixed with a backslash.

There's a second, weirder rule: if the SSID or password is entirely a hexadecimal string (e.g., "DEADBEEF1234"), it must be wrapped in double quotes to disambiguate from a hex-encoded value. This is rare in practice but spec-compliant tools do handle it:

function isHex(s) {
  return /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0
}

function formatField(value) {
  const escaped = escapeWifi(value)
  return isHex(value) ? `"${escaped}"` : escaped
}
Enter fullscreen mode Exit fullscreen mode

If you're testing a QR generator, try a password of 1234567890ABCDEF — that's the case where the quotes matter.

Hidden networks and why they're optional-but-not

The H:true field tells the device "this is a hidden network, don't expect to see it in the scan list." Without it, scanning a hidden-network QR sometimes works, sometimes doesn't, depending on whether the device can find the SSID by passive scanning.

Practical advice: if you've configured your network as hidden (which I'd argue you shouldn't — it's security theater and breaks discovery), include H:true. If your network is broadcasting normally, omit it. Some Android implementations behave oddly when H:false is explicitly set vs omitted; safer to leave it out unless needed.

Enterprise networks (EAP)

For 802.1X / WPA-Enterprise networks (most large offices and universities), the format extends:

WIFI:T:WPA;S:CorpNet;E:PEAP;P:password;I:username;;
Enter fullscreen mode Exit fullscreen mode

The E field specifies the EAP method (PEAP, TTLS, TLS). The I field is the user identity. Anonymous identity (A) is sometimes used.

In practice, this is often easier to handle through a profile (.mobileconfig for iOS, eap_config for Android) than through a QR code, because enterprise auth has too many knobs (CA certs, server name validation, inner auth method) to fit into a QR comfortably. Most tools that "support enterprise QR" generate something that works on the latest Android and quietly fails on iOS.

Building one in 30 lines

Putting it all together, here's a complete generator:

function escapeWifi(value) {
  return value.replace(/[\\;,:"]/g, '\\$&')
}

function isHex(s) {
  return s.length > 0 && /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0
}

function formatField(value) {
  const escaped = escapeWifi(value)
  return isHex(value) ? `"${escaped}"` : escaped
}

function buildWifiString({ ssid, password, auth = 'WPA', hidden = false }) {
  if (!ssid) throw new Error('SSID is required')
  if (auth !== 'nopass' && !password) {
    throw new Error('Password required for ' + auth)
  }

  const fields = [`T:${auth}`, `S:${formatField(ssid)}`]
  if (auth !== 'nopass') fields.push(`P:${formatField(password)}`)
  if (hidden) fields.push('H:true')

  return 'WIFI:' + fields.join(';') + ';;'
}

// Convert to QR — using any QR library
import QRCode from 'qrcode'

async function makeWifiQR(opts) {
  const text = buildWifiString(opts)
  return await QRCode.toDataURL(text)
}

// Usage
const dataUrl = await makeWifiQR({
  ssid: 'CafeWiFi',
  password: 'latte;art',  // semicolon will be properly escaped
  auth: 'WPA',
})
Enter fullscreen mode Exit fullscreen mode

That's a real, spec-compliant WiFi QR code generator. About 30 lines including the QR rendering. The qrcode npm package handles the actual visual rendering; the format string is the part that matters.

iOS vs Android: where they differ

Both platforms support the format, but with subtle differences:

  • iOS (since iOS 11) reads WiFi QR codes through the Camera app — you point, a notification appears, tap to join. Works seamlessly for WPA and nopass. Hidden networks (H:true) often don't auto-join on iOS — the network has to already be known.

  • Android scanners vary. The native camera works on most modern Android versions. Some manufacturer skins (older Samsung, some Xiaomi) require a third-party scanner. Hidden networks generally work better on Android than iOS.

  • Special characters sometimes survive iOS but break Android, or vice versa. Test with &, %, and emoji-bearing SSIDs (yes, those exist) before assuming a code is universal.

The reliable subset: T:WPA or T:nopass, ASCII passwords, broadcasting (non-hidden) networks. Stick to that and you'll have no issues across phones.

The WPA3 transition

WPA3 networks are starting to roll out. The QR format doesn't change — T:WPA still works. But there's a new mode called "WPA3-Personal SAE" that requires the device to support SAE authentication. Older devices joining a WPA3-only network from a QR code will fail to connect even though the QR scans correctly.

Most WPA3 routers are configured for "WPA2/WPA3 transition mode" by default, which means older devices fall back to WPA2. If you're building a public WiFi QR code (cafe, hotel), keep your AP in transition mode unless you have a reason not to. WPA3-only networks will silently fail for a chunk of guests.

Beyond WiFi: the format pattern

The WiFi QR format is one of several "structured QR" specs. Others you'll see:

  • vCard: BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\n... — for contact cards
  • mailto: mailto:hello@example.com?subject=Hi&body=... — for email
  • tel: tel:+15555555555 — for phone numbers
  • geo: geo:37.7749,-122.4194 — for map locations
  • SMSTO: SMSTO:+15555555555:Hello — for pre-composed SMS

All of them are just text. The "smart" behavior happens because phone scanners recognize the prefix and offer the appropriate action.

When you need to make one quickly

For one-offs — sharing your home WiFi with a guest, posting one for an office network, generating a batch for a hotel — there's no need to write code. qrtools.renderlog.in has a WiFi QR code generator that handles the escaping correctly, plus generators for vCard (contact info), UPI (payment QRs popular in India), and bulk QR generation for if you ever need to print 200 different ones at once. Everything renders client-side, so passwords don't end up in any server log.

TL;DR

  • WiFi QR codes are just text in this format: WIFI:T:WPA;S:name;P:password;;
  • Escape \, ;, ,, :, " in the SSID and password — the most common bug in homemade generators.
  • Hex-only strings should be wrapped in double quotes.
  • T:WPA covers WPA2 and WPA3; there's no separate WPA3 mode.
  • iOS reads them via the Camera; Android via the native scanner. Both handle the basics, both have edge cases with hidden networks and special characters.
  • The whole thing is ~30 lines of JavaScript on top of a QR library.
  • For one-offs, browser-based generators handle the escaping correctly and don't ship your password through a server.

The format has lasted 15 years without a major revision, which is rare in tech. Worth understanding once, useful forever.


If this was useful, I've also built a handful of other free, browser-based tools — no signup, no uploads, everything runs client-side:

Top comments (0)