Spec & internals
ECI: how QR codes declare a character encoding
ECI (Extended Channel Interpretation) is an optional header (mode indicator 0111 plus a numeric designator) that declares how byte-mode data should be interpreted. UTF-8 is ECI 26 and ISO-8859-1 is ECI 3. Most modern decoders assume UTF-8 even without it, while some older readers fail on ECI headers entirely.
The problem ECI solves
Byte mode stores octets, not characters. The bytes
0xC3 0xA9 are é in UTF-8, é in ISO-8859-1 and something else again in Shift-JIS,
and nothing in the base symbol says which reading is intended. QR's original 1990s context
assumed Japanese 8-bit conventions; the wider world needed a way to label the payload.
ECI is that label: a header that switches the "channel interpretation" for everything after it.
The wire format
An ECI header is the mode indicator 0111 followed by a designator whose length depends
on its value:
| Designator value | Encoding of the designator |
|---|---|
| 0–127 | 8 bits: 0xxxxxxx |
| 128–16383 | 16 bits: 10xxxxxx xxxxxxxx |
| 16384–999999 | 24 bits: 110xxxxx … |
The header sits before the data segment it modifies, and the assignments that matter in practice are few:
| ECI | Interpretation |
|---|---|
| 3 | ISO-8859-1 (Latin-1) |
| 20 | Shift-JIS |
| 26 | UTF-8 |
So a UTF-8-labelled payload costs 12 extra bits: 0111 + 00011010.
The awkward truth: mostly nobody uses it
The standard's default interpretation for byte mode without ECI is ISO-8859-1 (earlier editions pointed at JIS 8-bit conventions). Reality diverged years ago:
- Most encoders emit plain UTF-8 bytes with no ECI header. It saves 12 bits and, more importantly, avoids compatibility risk.
- Most decoders sniff. UTF-8 has a distinctive byte structure (invalid sequences are statistically obvious), so libraries like ZXing try UTF-8 first and fall back. Phone cameras behave the same way.
The result is a de facto standard (UTF-8, unlabelled) sitting on top of a de jure one. For URLs and any ASCII payload the question never arises, since ASCII is identical in all the candidate encodings.
When ECI actually breaks things
ECI is where "standards-compliant" and "works everywhere" part ways:
- Old and minimal readers (early dedicated scanners, embedded firmware, quick
hand-rolled decoders) may not implement ECI at all. Some fail the whole scan on the
unknown mode indicator
0111; others emit the raw designator bytes as garbage prepended to your data. - Hardware wedge scanners in warehouses translate scans to keystrokes and can mangle ECI-switched payloads; test with the actual devices: see hardware barcode scanners and QR.
- Non-UTF-8 designators (Cyrillic code pages, UTF-16) are supported so unevenly that the honest advice is: do not ship them.
Practical guidance: encode UTF-8, skip the ECI header unless a system on the reading side specifically requires it, keep mission-critical payloads ASCII where possible, and verify decoding with a real decoder rather than assuming. This is what UseQR does: payloads are encoded as raw UTF-8 in byte mode, no ECI header, matching what phone cameras expect.
FAQ
What is ECI in a QR code?
Extended Channel Interpretation, an optional header (mode indicator 0111 plus a numeric designator) that declares how the byte-mode payload should be interpreted. ECI 26 means UTF-8, ECI 3 means ISO-8859-1, ECI 20 means Shift-JIS.
Do I need an ECI header for UTF-8?
Formally yes, practically no. Nearly all modern decoders assume or detect UTF-8 without the header, and nearly all encoders omit it. Adding it costs 12 bits and risks confusing older readers that never implemented ECI.
What is the default encoding of a QR code without ECI?
The standard says ISO-8859-1 (earlier editions implied JIS 8-bit conventions), but real-world decoders overwhelmingly try UTF-8 first because its byte patterns are easy to detect. For pure ASCII payloads such as URLs, every candidate encoding reads identically.
Why does my QR code show strange characters on some scanners?
An encoding mismatch: the payload's bytes are being interpreted with the wrong character set, or an unhandled ECI header is leaking into the output. Re-encode as plain UTF-8 without ECI and test on the scanners your audience uses.
Try it: free, no signup
Related
- UTF-8 and Unicode in QR codes: emoji, CJK and the legacy trap, QR byte mode carries raw octets, so Unicode works by encoding UTF-8. It scans everywhere modern, but each emoji costs 4 bytes and old readers assume Shift-JIS.
- QR code data encoding modes: numeric, alphanumeric, byte, kanji, Numeric packs 3.33 bits per character, alphanumeric 5.5, byte 8, kanji 13. Mode choice is why HTTPS://USEQR.APP makes a smaller code than the lowercase URL.
- Kanji mode explained: 13 bits per character, Kanji mode packs double-byte Shift-JIS characters into 13 bits (46% denser than UTF-8 byte mode), but few generators emit it and UTF-8 rules in practice.
- Decode a QR code programmatically, Read QR codes from images in code: POST bytes or a URL to the keyless decode API, or decode locally with zxing-cpp, zbar or pyzbar. Snippets included.