Portix.One

concept · esc-pos

ESC/POS Commands Explained

By Portix.One Published
Anatomy of an ESC/POS command broken into prefix, command byte, and parameter

Quick answer

ESC/POS commands are byte sequences composed of a prefix, command code, parameters, and sometimes a length plus payload. ESC @ initializes common modes; LF prints and feeds a line; ESC a n selects justification; GS k prints supported one-dimensional barcodes; and GS ( k handles supported two-dimensional symbols. Names are human-readable notation—the application must send their numeric bytes in the required order.

An ESC/POS print job interleaves printable data with binary commands. Commands can update printer state, format subsequent content, move paper, print stored binary objects, request status, or activate supported hardware. Correct construction requires byte-level handling and a model-specific command reference.

Command anatomy

Simple command:

ESC a n
│   │ └─ parameter: alignment value
│   └─── command byte: "a" = 0x61
└─────── prefix: ESC = 0x1B

Length-prefixed command:

GS ( k pL pH cn fn [parameters or data]
      └─┬─┘
        └── little-endian payload-section length

The meaning and length formula come from the exact command definition. Do not infer them from similar commands.

Common command families

PrefixByteTypical role
Control codeVariesLine feed, tab, real-time controls
ESC0x1BText style, position, modes, initialization
GS0x1DBarcodes, images, cutting, advanced functions
FS0x1CCharacter systems, labels, model-specific groups
DLE0x10Real-time status and requests on supported devices

The categories are helpful conventions, not a complete protocol grammar.

Stateful commands

Many commands affect later content rather than producing output immediately. A robust job explicitly establishes required state:

Initialize
Select character table
Set alignment
Set emphasis and size
Send text
Restore or set next style
Feed
Cut

ESC @ clears the print buffer and resets many modes to power-on values, but Epson documents exceptions: it does not erase items such as NV graphics or user NV memory. Initialization is not the same as factory reset.

Byte-safe construction

Use a byte builder rather than a Unicode string:

const ESC = 0x1b;
const LF = 0x0a;

const command = (...values) => Uint8Array.from(values);
const initialize = command(ESC, 0x40);
const center = command(ESC, 0x61, 0x01);

Text must be encoded separately with the code page or encoding that matches printer state. Binary image or symbol data may contain zero bytes and invalid UTF-8 sequences, so string concatenation can corrupt it.

Buffering and execution

Printers commonly receive bytes into a buffer and execute them in sequence. Some commands require the printer to be at the beginning of a line, have an empty print buffer, be in Standard mode, or be online. Others act in real time through a different processing path.

Transport writes can be split or combined arbitrarily. A correct printer-side parser relies on command lengths, terminators, and state—not on application write boundaries.

Parameters and ranges

A parameter may be documented as:

  • A numeric byte such as 0, 1, 48, or 49.
  • Low and high bytes such as pL and pH.
  • A null-terminated field.
  • A fixed or variable-length payload.
  • A value whose supported range changes by model.

Validate before constructing the stream. Silent clamping, ignored commands, buffer desynchronization, or unexpected output can result from invalid values.

Status commands

Status may be automatic, requested, or real-time depending on printer and interface. A response is binary data and must not be mixed blindly with ordinary application logging or text processing.

Design status handling around explicit questions:

  • Did the transport accept bytes?
  • Is the printer online?
  • Is paper available?
  • Is the cover open?
  • Is a recoverable or unrecoverable error reported?
  • Did the device report a completed operation?

No single answer necessarily proves that the correct physical receipt reached the user.

Cut and peripheral commands

Cutters, drawers, and buzzers are physical actions. Commands and allowed parameters differ across models, and some commands are obsolete even if legacy devices accept them. Consult the supported-command page for the target model and prefer current documented commands.

Guard these actions separately from document formatting. A reprint may need another receipt but not another drawer pulse.

A maintainable command layer

Business document

Layout tokens: text, row, image, barcode, feed, cut

Capability-aware ESC/POS encoder

Validated byte stream

Authorized transport adapter

Keep raw numeric sequences inside a tested encoder. Business code should express intent such as receipt.total() or job.cut() rather than manually appending bytes.

Compatibility strategy

  • Maintain a profile per supported model and firmware family.
  • Generate only commands confirmed by its official reference.
  • Declare defaults instead of relying on prior jobs.
  • Add golden byte-stream tests for known documents.
  • Test output and status on physical devices.
  • Bound payload and response sizes.
  • Record transport, timeout, retry, and idempotency behavior.

Frequently asked questions

Are ESC/POS commands ASCII strings?

Documentation uses ASCII mnemonics, hex, and decimal notation. The transmitted protocol is bytes and can contain arbitrary binary payloads.

Can commands be sent one byte at a time?

A transport may segment writes, but performance and failure behavior vary. Build complete validated sequences or sensible chunks and let the protocol framing define boundaries.

Why does a command print as text?

The prefix or byte encoding may be wrong, the printer may be in another emulation, or the model may not recognize the command.

Should every job begin with ESC @?

Initialization provides a known common state, but it clears buffered print data and does not reset everything. Use it according to job ownership and the model’s documented behavior.

References

Related content