Skip to content
UseQR
ESC

Jump to

MOVEOPEN50 places

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.

View as MarkdownPaste this page into any AI assistant. It is plain, portable Markdown.

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