Payloads & QR Encoding
payload · encoding
What goes into the symbol and how it is encoded: text or binary payloads, QR version and error correction, masks, modes, ECI and structured append.
Payload
What to encode
The payload object is required for generate, measure and validate. Two payload types are available.
| Member | Values | Default | Notes |
|---|---|---|---|
payload.type | "text" · "binary" | "text" | Structured payload types (url, wifi, vcard, …) are reserved and currently rejected with unsupportedFeature. |
payload.text | UTF-8 string, up to 65,536 bytes | — | Required when type is text. The text is encoded as its UTF-8 bytes. Embedded NUL characters are allowed. Must be well-formed UTF-8. |
payload.base64 | base64 string, up to 65,536 decoded bytes | — | Required when type is binary. Any byte values, 0–255. The text must be standard base64 (A–Z a–z 0–9 + /): white space is ignored and the = padding may be omitted, but anything else, including the URL-safe alphabet (- and _) and misplaced padding, is rejected with wrongType rather than decoded as different bytes. |
{ "payload": { "text": "https://www.chilkatsoft.com" } }{ "payload": { "type": "binary",
"base64": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=" } }capacityExceeded at /payload. Use validate with scope capacity to check ahead of time.Encoding mode
Numeric, alphanumeric and byte
QR has compact encodings for digits and for an upper-case alphanumeric set. By default Chilkat picks the densest mode the whole payload fits. The selected mode is reported in symbols[0].selectedEncoding.mode.
| Mode | Characters | Bits per character | Example |
|---|---|---|---|
numeric | 0–9 | 3⅓ | 0123456789012345 → version 1, 16 digits |
alphanumeric | 0–9, A–Z (upper case only), space, $ % * + - . / : | 5½ | HELLO WORLD 2026 → version 1 |
byte | any UTF-8 text or binary bytes | 8 | Grüße aus München → version 2 |

0123456789012345 — numeric mode, version 1.
HELLO WORLD 2026 — alphanumeric mode, version 1.
Grüße aus München — byte mode (UTF-8), version 2.Force a mode with encoding.qr.mode ("auto", "numeric", "alphanumeric", "byte"). A mode the payload cannot use — for example numeric for "abc" — is rejected with conflictingOptions at /encoding/qr/mode. kanji is reserved and not yet available. Each payload is encoded as one segment in v11.7.0; encoding.qr.optimizeSegments is accepted for forward compatibility.
{ "payload": { "text": "20261024" }, "encoding": { "qr": { "mode": "byte" } } }Error correction
Level L, M, Q or H
Reed–Solomon error correction lets a damaged or partly obscured symbol still be read. Higher levels cost capacity, so the same payload often needs a larger version.
| Level | Recoverable codewords | Typical use |
|---|---|---|
L | ~7% | Clean screens and labels where size matters most. |
M default | ~15% | General purpose. Also what "auto" selects. |
Q | ~25% | Industrial labels, dirt and wear. |
H | ~30% | Maximum robustness; needed if part of the symbol will be covered. |




{ "payload": { "text": "https://www.chilkatsoft.com" },
"encoding": { "qr": { "errorCorrection": "H" } } }boostErrorCorrection
When the chosen version has spare capacity, "boostErrorCorrection": true raises the level as far as the payload still fits in the same version, so robustness improves at no cost in size. The level actually used is reported in selectedEncoding.errorCorrection.
{ "payload": { "text": "x" }, "encoding": { "qr": { "boostErrorCorrection": true } } }
// response: "selectedEncoding": { "version": 1, "errorCorrection": "H", ... }Version (size)
Automatic, exact, or bounded
A QR version is its size in modules: version 1 is 21 × 21, each further version adds 4 modules, up to version 40 at 177 × 177. By default the smallest version that holds the payload at the selected error-correction level is used.
| Member | Values | Default | Notes |
|---|---|---|---|
encoding.qr.version | 1–40 or "auto" | "auto" | An exact version. If the payload does not fit it, capacityExceeded. |
encoding.qr.minVersion | 1–40 | 1 | Lower bound for automatic selection — e.g. to keep all labels in a batch the same size. |
encoding.qr.maxVersion | 1–40 | 40 | Upper bound for automatic selection. Must not be below minVersion, and an exact version must satisfy both. |

12345678 at the automatic version 1.
{ "payload": { "text": "SKU-00042" }, "encoding": { "qr": { "minVersion": 4, "maxVersion": 4 } } }Mask
Automatic by the standard penalty rules, or explicit
Before a QR symbol is finished, one of eight mask patterns is XOR-ed over the data area to break up large uniform regions. ISO/IEC 18004 scores each candidate with four penalty rules and takes the lowest score; Chilkat does exactly that for "auto" (ties are resolved deterministically, so the same request always gives the same symbol). The chosen mask is reported in selectedEncoding.mask.








Set encoding.qr.mask to 0–7 to force a pattern — useful for regression tests or to reproduce a symbol generated elsewhere. Values outside 0–7 are outOfRange.
{ "payload": { "text": "https://www.chilkatsoft.com" }, "encoding": { "qr": { "mask": 5 } } }Character set
UTF-8 and ECI
Text is always encoded as UTF-8 bytes. By default no ECI (Extended Channel Interpretation) header is emitted, which is what virtually every reader expects. Set encoding.eci to 26 to declare UTF-8 explicitly inside the symbol.
| Member | Values | Default | Notes |
|---|---|---|---|
encoding.charset | "utf-8" | "utf-8" | Other charsets are reserved (unsupportedFeature). Not applicable to binary payloads. |
encoding.eci | 26 or "none" | "none" | 26 = UTF-8 (ISO/IEC 18004 ECI assignment). Other assignment numbers are reserved. Not applicable to binary payloads (conflictingOptions). |

"eci": 26: the symbol carries an ECI header announcing UTF-8, and selectedEncoding.eci reports 26.{ "payload": { "text": "日本語のテキスト" }, "encoding": { "eci": 26 } }Structured append
Split a large payload across up to 16 symbols
A structured-append set is a sequence of QR symbols that a reader reassembles into one payload. Each part carries its index, the total count and a parity byte computed over the whole payload. Chilkat splits the payload as evenly as possible.
| Member | Values | Default | Notes |
|---|---|---|---|
encoding.structuredAppend.enabled | bool | false | Turns on splitting. With true and no count, the smallest number of parts (1–maxSymbols) that fits is used; a set of 1 is an ordinary symbol. |
encoding.structuredAppend.maxSymbols | 1–16 | 16 | Upper bound for automatic splitting. |
encoding.structuredAppend.symbolCount | 1–16 | auto | An exact part count. Must not exceed maxSymbols; each part must still hold its share of the payload. |
The response has one symbols[] entry, one measurements[] entry and one outputs[] entry per part, in order. Each symbol reports structuredAppend: { "index", "total", "parity" } (index is 0-based) and its own payloadBytes.



{
"payload": { "text": "...a 120-character string..." },
"encoding": { "structuredAppend": { "enabled": true, "symbolCount": 3 } },
"output": { "return": "none", "pathTemplate": "/labels/part{index}.png" }
}{
"symbols": [
{ "symbology": "qr", "selectedEncoding": { "version": 3, "errorCorrection": "M", "mask": 2, "mode": "byte" },
"dimensions": { "modulesWide": 29, "modulesHigh": 29, ... },
"structuredAppend": { "index": 0, "total": 3, "parity": 120 }, "payloadBytes": 40 },
{ ... "structuredAppend": { "index": 1, "total": 3, "parity": 120 }, "payloadBytes": 40 },
{ ... "structuredAppend": { "index": 2, "total": 3, "parity": 120 }, "payloadBytes": 40 }
],
"outputs": [
{ "index": 0, "format": "png", ..., "path": "/labels/part1.png" },
{ "index": 1, "format": "png", ..., "path": "/labels/part2.png" },
{ "index": 2, "format": "png", ..., "path": "/labels/part3.png" }
], ...
}output.path (a single destination) is a conflictingOptions error; use output.pathTemplate with {index} (1-based), or return the images inline. See writing files.Reading the result
selectedEncoding and dimensions
The response never echoes "auto": selectedEncoding holds the resolved choices, so you always know exactly what was generated.
| Member | Meaning |
|---|---|
symbols[i].symbology | "qr" |
symbols[i].selectedEncoding.version | The QR version used, 1–40. |
symbols[i].selectedEncoding.errorCorrection | L, M, Q or H (after any boost). |
symbols[i].selectedEncoding.mask | The mask pattern, 0–7. |
symbols[i].selectedEncoding.mode | numeric, alphanumeric or byte. |
symbols[i].selectedEncoding.eci | Present only when an ECI header was emitted (26). |
symbols[i].dimensions | modulesWide, modulesHigh and the four quiet-zone widths in modules. |
symbols[i].structuredAppend | Present for a structured-append set: index (0-based), total, parity. |
symbols[i].payloadBytes | Bytes encoded in this symbol. |
int version = jsonOut.IntOf("symbols[0].selectedEncoding.version");
string level = jsonOut.StringOf("symbols[0].selectedEncoding.errorCorrection");
int modules = jsonOut.IntOf("symbols[0].dimensions.modulesWide");
int numParts = jsonOut.SizeOfArray("symbols"); // > 1 for structured appendint version = jsonOut.IntOf("symbols[0].selectedEncoding.version");
const char *level = jsonOut.stringOf("symbols[0].selectedEncoding.errorCorrection");
int modules = jsonOut.IntOf("symbols[0].dimensions.modulesWide");
int numParts = jsonOut.SizeOfArray("symbols"); // > 1 for structured appendversion = jsonOut.IntOf("symbols[0].selectedEncoding.version")
level = jsonOut.StringOf("symbols[0].selectedEncoding.errorCorrection")
modules = jsonOut.IntOf("symbols[0].dimensions.modulesWide")
numParts = jsonOut.SizeOfArray("symbols") # > 1 for structured appendFormats, have the same methods and the same JSON requests in every supported language: C, C++, C#, VB.NET, Java, Objective-C, Swift, Python, PHP, Perl, Ruby, Node.js, Electron, React Native, Dart / Flutter, Go, Rust, Zig, Delphi, C++Builder, Free Pascal / Lazarus, Classic ASP, VB6, VBScript, FoxPro, DataFlex, PowerBuilder, Xojo, Xbase++, PureBasic, AutoIt, B4A / B4J, LabVIEW, SQL Server and Tcl. See Chilkat Downloads for Every Language.