Skip to content
Docs menu · Receipt commands and templates
Guides / Receipt commands and templates

Receipt commands and templates

Describe a receipt as a list of JSON commands. CatzPrint turns them into ESC/POS for the printer's paper width, so you never deal with control codes.

How commands work

Pass a list of commands to your SDK's printReceipt (print_receipt in Python and Rust, PrintReceipt in Go). Each command has a type and only the fields that type uses. Styles never leak: after every text command the printer is reset to normal, left-aligned text.

Layout adapts to the printer's paper: 80 mm paper has 48 characters per line (576 dots wide) and 58 mm has 32 (384 dots). Text is printed in code page 858 (Western European, with the euro sign); characters it can't show print as ?.

Sending commands to an office or photo printer? CatzPrint prints the same content as a simple PDF instead.

Command reference

typeFieldsNotes
texttext, align, bold, underline, invert, size, fontalign: left | center | right. size 1–8 multiplies width and height. font: a (default) or the smaller b. Up to 10,000 characters.
feedlinesBlank lines, 1–20. Defaults to 1.
linecharA full-width rule. char defaults to -.
columnscolumns: [text, width, align]1–6 cells on one row. width is a fraction of the line (0.5 = half); cells without one share what's left. Long cells wrap onto extra rows.
barcodeformat, data, aligncode128 (default, printable ASCII, up to 80), ean13 (12 or 13 digits) or code39 (up to 40). Human-readable text prints below.
qrdata, size, alignUp to 2,000 bytes. size is the module size, 1–16 (default 6). Error correction M.
imagedata, alignPNG or JPEG as base64. Scaled to fit the paper and dithered to black and white.
cutpartialFeeds 3 lines, then a full cut, or a partial cut with "partial": true.
drawerpinOpens the cash drawer. pin 2 (default) or 5.
beep—Sounds the printer's buzzer, where it has one. Handy for kitchen tickets.
eachitems, commandsTemplates only — see below.

A full receipt

await cz.printReceipt(
  [
    { type: "text", text: "CORNER CAFE", align: "center", size: 2, bold: true },
    { type: "text", text: "12 Main Street", align: "center", font: "b" },
    { type: "line", char: "=" },
    { type: "columns", columns: [{ text: "2 x Flat white", width: 0.7 }, { text: "9.00", align: "right" }] },
    { type: "columns", columns: [{ text: "1 x Banana bread", width: 0.7 }, { text: "5.50", align: "right" }] },
    { type: "line" },
    { type: "text", text: "TOTAL  14.50", align: "right", bold: true, size: 2 },
    { type: "feed", lines: 1 },
    { type: "barcode", format: "code128", data: "A-1042", align: "center" },
    { type: "qr", data: "https://cornercafe.example/r/A-1042", size: 6, align: "center" },
    { type: "drawer", pin: 2 },
    { type: "cut", partial: true },
  ],
  { role: "receipt", reference: "A-1042", idempotencyKey: "A-1042-receipt" },
);

The SDKs send the list as JSON. The Python SDK also has small builders in catzprint.commands, Go has catzprint.Line(), Columns, QR and Cut, and Rust has Command::text(…) and friends. On the wire the same receipt is:

commands
[
  { "type": "text", "text": "CORNER CAFE", "align": "center", "size": 2, "bold": true },
  { "type": "text", "text": "12 Main Street", "align": "center", "font": "b" },
  { "type": "line", "char": "=" },
  { "type": "columns", "columns": [
    { "text": "2 x Flat white", "width": 0.7 },
    { "text": "9.00", "align": "right" }
  ] },
  { "type": "columns", "columns": [
    { "text": "1 x Banana bread", "width": 0.7 },
    { "text": "5.50", "align": "right" }
  ] },
  { "type": "line" },
  { "type": "text", "text": "TOTAL  14.50", "align": "right", "bold": true, "size": 2 },
  { "type": "feed", "lines": 1 },
  { "type": "barcode", "format": "code128", "data": "A-1042", "align": "center" },
  { "type": "qr", "data": "https://cornercafe.example/r/A-1042", "size": 6, "align": "center" },
  { "type": "drawer", "pin": 2 },
  { "type": "cut", "partial": true }
]

Templates

A template is a saved list of commands with {{placeholders}}. Create templates in the console under Templates, where you can preview them on paper. Then print with your SDK's printTemplate (print_template in Python and Rust, PrintTemplate in Go), passing the template ID and the data.

  • Placeholders work in text, data (barcodes, QR codes) and each column's text.
  • Use dotted paths for nested values: {{order.customer.name}}. Names are letters, digits and underscores.
  • Missing values print as nothing. Whole numbers print without decimals; other numbers print with two decimals.
  • each repeats its commands for every element of the list at items. Inside, the element is item — {{item.name}}. Loops can be nested up to 3 deep, with up to 500 items per list.
Template
{
  "name": "Kitchen ticket",
  "paper": "80mm",
  "body": [
    { "type": "text", "text": "KITCHEN", "align": "center", "size": 2, "bold": true },
    { "type": "text", "text": "#{{order.number}} · Table {{order.table}}", "align": "center" },
    { "type": "line", "char": "-" },
    { "type": "each", "items": "order.items", "commands": [
      { "type": "columns", "columns": [
        { "text": "{{item.qty}} x", "width": 0.15 },
        { "text": "{{item.name}}" }
      ] },
      { "type": "text", "text": "  {{item.note}}", "font": "b" }
    ] },
    { "type": "line", "char": "-" },
    { "type": "text", "text": "{{order.placed_at}}", "align": "right", "font": "b" },
    { "type": "beep" },
    { "type": "cut" }
  ]
}

Print it with your order's data:

await cz.printTemplate(
  templateId,
  {
    order: {
      number: "A-1042",
      table: 7,
      placed_at: "09:14",
      items: [
        { qty: 2, name: "Eggs on toast", note: "+ bacon" },
        { qty: 1, name: "Banana bread", note: "" },
      ],
    },
  },
  { role: "kitchen", locationId, reference: "A-1042", idempotencyKey: "A-1042-kitchen" },
);
Tiptemplates.list() returns your templates (it needs a key with jobs:write), so your integration can look up a template's ID by name.

Limits and errors

A job can have up to 2,000 commands. If a command is invalid the job is refused with 400 invalid, and error.fields.commands says which command and why — for example command 4: qr size must be 1 to 16. Command numbers start at 1. The SDKs raise this as a validation error with the same fields.

Without an SDK

Send the commands as commands in the body of POST /v1/orgs/{org}/jobs, or a template as template_id and data (see the HTTP (cURL) tabs above). Templates can also be created with POST /v1/orgs/{org}/templates and a body of name, paper (80mm or 58mm) and body, and listed with GET /v1/orgs/{org}/templates.