Unicode and Code Pages in ZPL (^CI)
How ^CI selects the character set for ZPL text, when to use UTF-8 (^CI28), and how to send accented characters with ^FH.
^CI tells the printer how to interpret the bytes in your ^FD text. If the character set is wrong, you get the wrong glyphs, even with the right font.
Syntax
^CIa
It applies from that point on, so put it near the top of the label (after ^XA).
Common values
| Value | Character set |
|---|---|
| 0 | Zebra Code Page 850 (the long-standing default) |
| 27 | Zebra Code Page 1252 (Windows Latin-1) |
| 28 | Unicode, UTF-8 |
| 29 | Unicode, UTF-16 big-endian |
| 30 | Unicode, UTF-16 little-endian |
| 31 | Shift-JIS |
| 33 to 37 | Windows code pages 1250 (Central European), 1251 (Cyrillic), 1253 (Greek), 1254 (Turkish), 1255 (Hebrew) |
The exact list differs by firmware. Check the ZPL II Programming Guide for
^CIon your version before depending on a value.
Use UTF-8 for most multilingual labels
^XA
^CI28
^CWZ,E:ARIALUNI.TTF
^FO50,50^AZN,50,50^FDCafé, naïve, Zürich, São Paulo^FS
^XZ
Requirements: your host application must send the data as UTF-8 and the font must contain the glyphs. Font 0 does not contain every Unicode character; use a downloaded Unicode TrueType font when in doubt.
Sending special characters safely with ^FH
If your software cannot send raw UTF-8, escape bytes with ^FH and an underscore. The é in UTF-8 is the two bytes C3 A9:
^XA
^CI28
^FO50,50^A0N,50,50^FH^FDCaf_C3_A9^FS
^XZ
With a single-byte code page such as ^CI27, é is the single byte E9: Caf_E9.
^CI and the host encoding must match
| Printer sees | Host sends | Result |
|---|---|---|
^CI28 |
UTF-8 | Correct |
^CI28 |
Windows-1252 | Garbled accents |
^CI27 |
UTF-8 | Garbled, usually two junk characters per accent |
^CI0 |
UTF-8 | Garbled |
Troubleshooting: special characters printing wrong.
Direction and complex scripts
^CI28 selects the encoding but not the layout. Right-to-left text and scripts that need shaping (Arabic, Devanagari) depend on firmware and font support. See language pages.
Why encoding trips everyone up
A computer doesn’t store letters, it stores numbers. Encoding is the agreement about which number means which character. When two sides of a conversation use different agreements, you get nonsense. That’s all a “wrong accent” problem is: your application says “the number 195 followed by 169 means é” and the printer hears “195 means one thing, 169 means another”.
The older single-byte code pages assign one number to each character, which limits them to 256 characters in total. Each page is a different compromise, favouring Western Europe, or Cyrillic, or Greek and so on. UTF-8 solves the limit by using a variable number of bytes per character, so the same encoding can express almost anything. That’s why ^CI28 is the setting most modern setups want.
Choosing between UTF-8 and a code page
If you’re starting fresh, use UTF-8. Modern systems produce it by default, databases mostly store it, and one setting covers every language you’re likely to need. Your font then becomes the only limiting factor.
Stay with a legacy code page if you have old software that can only emit one, or if you’re maintaining labels that have worked for years and you’d like to leave them alone. There’s no prize for converting something that works. Just make sure the ^CI value matches what the software sends, and document it, because the person who inherits the labels won’t guess.
The most useful debugging trick
When accents go wrong, look at what appears instead. The symptom points straight at the cause. If é turns into two odd characters (commonly something like é), UTF-8 data is being read as a single-byte page, so you need ^CI28. If an accented letter becomes a different accented letter, the code page is wrong. If you see a question mark or a box, the character isn’t available in the font or page. Our table in the special characters troubleshooting page lists these patterns in one place.
Another trick is to test with a tiny label that holds only one problem string. Don’t debug with a full production label. Strip it down to ^XA^CI28^FO40,40^A0N,40,40^FDé ñ ü€^FS^XZ and see what prints, then add back complexity one piece at a time.
Where the data can get changed on the way
The path from your database to the printer can have several places that quietly transform text: the programming language’s string handling, a file saved with the wrong encoding, a print spooler that re-encodes, a network gateway. If the printed result is wrong and the ZPL looks right in your editor, the transformation is probably happening after your code. Send a raw ZPL file straight to the printer to rule out the spooler, as mentioned in our UTF-8 checklist.
Also watch for the byte-order mark, a few invisible bytes some editors add at the start of a file. It can show up as a stray character at the top of the label, or confuse the first command.
Hex escapes as a safety net
If you can’t control encoding end to end, ^FH lets you spell out bytes explicitly. It’s verbose, but it’s immune to everything between you and the printer, which makes it popular in shared environments. You can see the technique in the ^CF, ^FD and ^FO basics page, and the label generator produces escaped output automatically.
Languages beyond Western Europe
Setting the character set is necessary but, for complex scripts, not sufficient. Arabic and Hebrew run right to left. Hindi needs letters to combine and reorder. Chinese, Japanese and Korean need large fonts. Each of these has its own wrinkles, which are discussed in language support.
FAQ
Does ^CI change the font? No. It controls interpretation of the bytes. The font still has to contain the glyphs.
Should ^CI go in every label? It’s a good habit. Don’t rely on state left over from an earlier job.
What’s the safest value for plain English? Either ^CI28 or the printer default will work, since plain ASCII is the same in both.
Why do some firmware versions disagree? Not every printer supports every ^CI value. Check your programming guide and test, and see fonts by printer model.
Last reviewed 2026-10-20