ESC/POS Commands: a Practical Reference

The ESC/POS commands that matter in production — init, text style, feed, cut, drawer kick, status — with raw bytes and the quirks between brands.

Published 2026-07-24 · For developers

ESC/POS is Epson's escape-code command language for receipt printers, and the de facto standard the whole industry forked. A job is a one-way byte stream: printable text mixed with commands that start with ESC (0x1B) or GS (0x1D), ending in a feed and a cut. This is a reference to the commands that actually matter in production — with the raw bytes, and the quirks that bite when you move between printer brands.

How ESC/POS works

Three things explain most of the protocol's behavior:

  1. It is stateful. Commands like bold or center-alignment set a mode that persists until you unset it. Every job should therefore start with ESC @ to reset the printer to a known state — otherwise you inherit whatever the last job left behind.
  2. It is (almost) one-way. The printer never acknowledges anything. You write bytes; it prints them. The exceptions are the real-time status queries (DLE EOT), which get a single reply byte — covered below.
  3. Text is just text. Anything that is not a recognized command sequence prints as characters in the current code page, and LF (0x0A) prints the line buffer and advances the paper.

A minimal, complete receipt:

1B 40                          ESC @        initialize
1B 61 01                       ESC a 1      center
1B 45 01                       ESC E 1      bold on
43 4F 46 46 45 45 0A           "COFFEE" LF  print a line
1B 45 00                       ESC E 0      bold off
1B 64 05                       ESC d 5      feed 5 lines
1D 56 00                       GS V 0       full cut

The commands that matter

The subset below covers the overwhelming majority of real receipts. Everything else in the 200-plus-page vendor manuals is images, page mode, kanji handling, and model-specific extras.

CommandBytesWhat it does
Initialize1B 40Reset all modes to defaults. Start every job with this
Line feed0APrint the buffer, advance one line
Feed n lines1B 64 nPrint, then feed n lines
Align1B 61 n0 left, 1 center, 2 right
Bold1B 45 n1 on, 0 off
Underline1B 2D n0 off, 1 thin, 2 thick
Print mode1B 21 nBitfield: bold, double-height, double-width, underline
Character size1D 21 nHigh nibble width ×1–8, low nibble height ×1–8
Full cut1D 56 00Cut across the full width
Partial cut1D 56 01Leave a small tab of paper
Feed and cut1D 56 42 nFeed n motion units, then partial cut
Drawer kick1B 70 m t1 t2Pulse the RJ12 drawer connector
Barcode1D 6B m n d1..dn1D barcode, system m, n data bytes
QR code1D 28 6B ...Multi-step 2D sequence (below)
Status query10 04 nReal-time status, single-byte reply
Code page1B 74 nSelect character table for bytes 0x80–0xFF

Initialize: ESC @

1B 40 clears print modes, resets line spacing, and empties the line buffer. It does not clear the receive buffer or recover from an error state — a printer mid-paper-jam ignores it. Cheap habit, real payoff: jobs become order-independent.

Text styling: ESC ! and GS !

There are two size/style systems, and brands differ on which they honor fully.

ESC ! n (1B 21 n) sets several attributes in one byte — a bitfield where 0x08 is bold, 0x10 double height, 0x20 double width, 0x80 underline. So 1B 21 38 gets you bold double-height double-width in one command.

GS ! n (1D 21 n) is the finer-grained size command: the high nibble selects width multiplier 1–8, the low nibble height 1–8. 1D 21 11 is double width and double height; 1D 21 00 returns to normal.

In practice: use ESC ! for the common emphasis cases, GS ! when you need big totals. Test both on your target hardware — some budget printers implement only multipliers 1 and 2, and some ignore the width nibble entirely.

Alignment is ESC a n (1B 61 n) and applies to entire lines: it must be sent at the start of a line, before any printable characters, or it silently applies to the next line.

Feeding and cutting: GS V and friends

GS V has accumulated variants over 30 years:

VariantBytesBehavior
Function A, full1D 56 00Full cut, no feed
Function A, partial1D 56 01Partial cut (one tab of paper left)
Function B, feed + cut1D 56 42 nFeed n motion units, then cut
Legacy1B 69 / 1B 6DOld ESC-based full/partial cut, still honored by many models

The trap everyone hits once: the cutter blade sits several millimeters above the print head. Cut immediately after your last line and the blade slices through the bottom of the receipt. You must feed the head-to-cutter distance first — typically 3 to 6 lines depending on the mechanism. GS V 66 n bakes the feed into the cut; on printers where it misbehaves, an explicit ESC d n before 1D 56 00 does the same job.

This clearance is a property of the printer, not the receipt — which is why hardcoding it into your app's templates goes wrong the moment hardware changes. (On a ProxyNode it is a config value on the device: the node feeds its configured clearance before every cut, and dialing in a new printer is a curl to PUT /config, not an app release.)

Cash drawer: ESC p

Cash drawers do not speak ESC/POS — they are a solenoid wired to an RJ12 jack on the printer. ESC p m t1 t2 (1B 70 m t1 t2) fires a pulse on connector pin m (0 for pin 2, 1 for pin 5), with t1/t2 setting the pulse on/off time in 2 ms units:

1B 70 00 19 FA     ESC p 0 25 250 — kick pin 2, 50 ms on, 500 ms off

Too short a pulse and heavier drawers do not open; too long and you can overheat the solenoid. Wiring, timings, and the "drawer opens but status says closed" cases get a full treatment in the cash drawer kick guide.

Barcodes: GS k

One-dimensional barcodes use GS k (1D 6B). Prefer "Function B", which takes an explicit length byte instead of a NUL terminator — it is unambiguous and handles Code 128's full byte range:

1D 68 50           GS h 80      barcode height: 80 dots
1D 77 02           GS w 2       module width: 2
1D 6B 49 0C ...    GS k 73 12   Function B, system 73 (Code 128), 12 data bytes

Common system IDs under Function B: 65 UPC-A, 67 EAN-13, 69 Code 39, 73 Code 128. Code 128 has one extra quirk: many printers require the data to start with a code-set selector like {B before your ASCII payload — omit it and you get either nothing or garbage, depending on brand.

QR codes: GS ( k

QR is not one command but a conversation with the printer's symbol buffer — select model, set module size, store data, print. All four steps use the GS ( k (1D 28 6B) envelope with a little-endian length:

1D 28 6B 04 00 31 41 32 00     select QR model 2
1D 28 6B 03 00 31 43 06        module size 6
1D 28 6B 10 00 31 50 30 ...    store data (len = payload + 3)
1D 28 6B 03 00 31 51 30        print the stored symbol

The length field trips people up: for the store-data step it counts the payload plus the three header bytes (31 50 30). A 13-byte URL means a length of 16 — 10 00 little-endian. Get it wrong by one and the printer either eats your next command as QR data or prints a truncated symbol.

Budget printers are also where QR support is patchiest: some ignore GS ( k entirely, and the only path to a QR is rendering it as a raster image.

Real-time status: DLE EOT

The one place ESC/POS talks back. Send 10 04 n and the printer replies with a single status byte, even mid-job — n selects printer state (1), offline causes (2), error causes (3), or paper sensors (4). In every valid reply, bits 1 and 4 are fixed high and bits 0 and 7 fixed low, so a healthy idle printer answers 0x12 to all four queries.

10 04 01     printer status  → bit 3 set = offline
10 04 02     offline cause   → bit 2 cover open, bit 5 paper out
10 04 04     paper sensors   → bits 2+3 near-end, bits 5+6 paper end

There is enough depth here — parsing replies out of a stream that also contains 0x10 bytes inside image data, printers that never answer, paper sensors that lie — that it has its own guide.

Code pages and international text

Bytes 0x00–0x7F are ASCII. What 0x80–0xFF mean depends on the selected code page: ESC t n (1B 74 n) picks a table, but the mapping of n to table varies by manufacturer. Page 0 is CP437 and page 16 is usually Windows-1252 on Epson — on a generic printer, all bets are off. Most printers do not accept UTF-8 at all; you must transcode to the target code page or render text as an image. If your receipts show é as graphics characters, this is why.

Brand quirks: the standard everyone forks

ESC/POS is a specification only in the sense that Epson publishes one. Every other manufacturer implements an approximation, and the differences are exactly where production bugs live:

  • Cut variants differ — some honor only the legacy ESC i, some only GS V 66 n.
  • ESC ! bitfields are partially implemented; size multipliers cap at 2 on many budget models.
  • Code page numbering is vendor-specific beyond page 0.
  • QR support ranges from full GS ( k to nothing.
  • DLE EOT replies range from spec-accurate to never-answers.

The community's answer is escpos-printer-db, the capabilities database maintained by the python-escpos project — a machine-readable record of which model supports which features and code pages. If you support arbitrary hardware, build against it rather than rediscovering each quirk from support tickets.

This fragmentation is the reason cloud POS vendors keep short certified-printer whitelists, and it is the problem ProxyNodes exists to absorb: your app targets one JSON API, and the node owns the byte-level dialect of whatever printer is attached. A survey of the alternatives is in the receipt printer API guide.

Testing commands without a printer

You do not need hardware to develop against this reference. Two zero-hardware options:

  • Pipe bytes through a decoder and eyeball the command stream (any hex dump plus this page works).
  • Run a receipt printer simulator that accepts real ESC/POS on port 9100, renders the receipt, answers DLE EOT queries, and can fault-inject paper-out and cover-open — so your error handling gets exercised, not just your happy path. More on emulator options in the ESC/POS emulator guide, and more printing guides on the developers hub.

Frequently asked questions

What is the ESC/POS command to cut the paper?
GS V — bytes 1D 56 00 for a full cut, 1D 56 01 for a partial cut, or 1D 56 42 n to feed n units and then cut. Feed 3 to 6 lines before cutting, because the blade sits above the print head and will otherwise cut through your last lines of text.
How do I center or bold text in ESC/POS?
Center with ESC a 1 (bytes 1B 61 01), sent at the start of a line. Bold on with ESC E 1 (1B 45 01) and off with 1B 45 00. Both are modes that persist until changed, so reset styles after each block or start jobs with ESC @ (1B 40).
Why does my receipt print correctly on one printer but garble on another?
ESC/POS is forked by every manufacturer. The usual culprits are code page numbering, unsupported cut or QR commands, and partial ESC ! implementations. Check the escpos-printer-db capabilities database for the target model, or test against an emulator that mimics the target.
Does ESC/POS support UTF-8?
Generally no. Printers interpret bytes above 0x7F through a selected code page (ESC t n), so you must transcode text to that code page or render it as an image. A handful of recent models add a UTF-8 mode, but you cannot rely on it across hardware.
How do I know if the printer is out of paper?
Send the real-time query DLE EOT 4 (bytes 10 04 04) and read the one-byte reply: bits 5 and 6 set mean paper end, bits 2 and 3 mean near-end. Not every printer answers, and cheap ones sometimes lack the near-end sensor, so treat silence as unknown rather than as OK.

Related reading