Thermalink

Reference

Receipt JSON

{
  "mode": "image",      // optional: "image" (default) or "text"
  "dots": 576,          // optional: printable width in dots. Default: printer profile, then config.
  "width": 48,          // optional: characters per line (text mode), and the unit for column widths
  "font": "sans",       // image mode: "sans" (default) or "mono"
  "fontSize": 24,       // image mode: pixel height of normal text
  "codepage": "cp437",  // text mode: cp437 (default), cp850, cp858 (has €), cp866 (Cyrillic), cp1252
  "items": [ ...blocks ]
}

Image mode vs text mode

image (default)text
HowAgent draws the receipt with TTF fonts at the printer's width and sends a raster (GS v 0). Cut/drawer are sent as commandsPrinter's built-in font via ESC/POS text commands
Looks the same on all brands✅❌ (fonts, column counts, codepages differ)
LanguagesAnything your font covers (set fontFile in the config, e.g. a Noto font for Cyrillic/Greek/CJK). Scripts that need shaping (Arabic, Urdu, Hindi) should be drawn in the browser and sent as an image blockLimited to the printer's codepages
Data per receipt~10–40 KB~0.2–1 KB
Best forUSB / network printers, consistent brandingBluetooth/serial links, very old printers

In image mode, size scales text (1–4), and column width values are characters of width (default dots / 12), converted to pixels.

BlockFieldsNotes
textvalue, align, bold, underline, invert, size (1–4)Word-wraps to the line width (divided by size). \n starts a new line.
rowleft, right, boldItem/price pair: right side is right-aligned, left side wraps.
columnscolumns: [{text, width?, align?}], boldwidth in characters; columns without a width share the rest. Cells wrap independently.
linechar (default -)Full-width divider.
feedlinesBlank lines.
qrvalue, size (1–16, default 6), alignNative printer QR (GS ( k, model 2, ECC M).
barcodevalue, format (CODE128 default, EAN13, EAN8, UPCA, CODE39), height, hri, alignEAN/UPC values are length/digit checked.
imagedata (base64 or data: URL of PNG/JPEG/GIF), align, size (max width in dots), threshold, ditherScaled to paper width, Floyd–Steinberg dithered, sent as GS v 0 raster in 128-row bands. Transparency prints white.
cutpartialFeeds 3 lines, then cuts.
drawerpin (2 or 5)Kicks the cash drawer connected to the printer.
rawdata (base64)Inject any bytes (vendor-specific commands).

Characters that the selected codepage cannot represent print as ?. Use cp858 for €, cp866 for Cyrillic. CJK/Arabic/Urdu text should be sent as an image (render it to a canvas in the browser, then call Thermalink.imageToDataURL(canvas.toDataURL())).

HTTP API (agent)

Base URL http://127.0.0.1:17777. All bodies are JSON.

Method & pathBodyResponse
GET /v1/healthnone{ok, name, version}. Open to all, used to detect the agent.
GET /v1/printersnone{ok, printers: [...os names], aliases: {name: target}, default}
POST /v1/preview{receipt, printer?}{ok, mode, png, text, bytes}. png is a data URL of exactly what will print (image mode), using that printer's profile
POST /v1/print{receipt} or {raw: base64}, plus printer?, copies? (1–20){ok, printer, bytes, copies, ms}

Errors: {ok:false, error} with status 400 (bad document), 401 (missing/wrong key), 403 (origin or host not allowed), 413 (body > 8 MB), 502 (printer unreachable/failed).

printer may be an alias, an OS printer name, tcp://host[:port] or file://path (writes bytes to a file, useful for tests). Empty means the default printer.

Jobs to the same printer are serialised, so two tabs printing at once never interleave.

Security model

Config file

thermalink config prints the path and contents. Default locations: %APPDATA%\Thermalink\config.json (Windows), ~/Library/Application Support/Thermalink/config.json (macOS), ~/.config/Thermalink/config.json (Linux). Override with the env var THERMALINK_CONFIG.

{
  "port": 17777,
  "allowedOrigins": ["https://pos.yourapp.com"],
  "apiKey": "generated-on-first-run",
  "printers": {
    "receipt": { "target": "EPSON TM-T20III Receipt", "dots": 576 },
    "kitchen": "tcp://192.168.1.50:9100",
    "slip":    { "target": "POS-58", "dots": 384, "cols": 32, "mode": "text" }
  },
  "defaultPrinter": "receipt",
  "mode": "image",
  "paperWidth": 48,
  "paperDots": 576,
  "fontFile": "",
  "fontBoldFile": "",
  "timeoutSeconds": 10
}

Restart the agent after editing the file by hand. The CLI commands (allow, alias, default) also need a restart of a running agent to take effect.

More docs: Quick start · Troubleshooting · Compatibility