Open-source MIT-licensed JavaScript/TypeScript barcode generator and barcode reader SDK for encoding and decoding 1D linear and 2D matrix barcodes. It has zero runtime dependencies and runs in browsers, Web Workers and Node.js.
Original Sythos implementation, zero runtime dependencies, MIT. It runs unmodified in Node, in browsers
(including Safari on iOS) and in web workers. The core requires no canvas, no filesystem and no
DOM — images go in and come out as plain { data, width, height } RGBA objects, which is exactly
what an ImageData is.
The code is complete and entirely human-readable. The full source ships. There is no WebAssembly, no native addon, no compiled artefact, no binary blob and no minified file anywhere in this repository — every tracked file is text you can open and read.
That includes the prebuilt bundles. They are generated, concatenated and wrapped from src/ by
the project's own bundler, but nothing is stripped in the process:
bundle/sythos-barcode.js runs to roughly 7,900 lines, about a third
of them comments, averaging a little over 30 characters a line. Open it anywhere and you are
reading the same annotated code as the source, in the same order — a convenience, not a black box.
Don't take that on trust either; it takes one command:
awk '{ n += length($0) } END { print "lines:", NR, " avg length:", int(n/NR) }' bundle/sythos-barcode.jsA minified bundle gives you a handful of lines averaging thousands of characters. This one does not, and that is the whole point.
This is deliberate. A barcode library decides what a scanner believes a label says, so it belongs in the category of code you can audit rather than have to trust. Every constant table, every check digit and every error-correction step is here in full, with the reasoning next to it.
import { encode, decode, toSVG, toImageData } from './src/index.js';
const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
const svg = toSVG(matrix, { scale: 8 });
const found = decode(toImageData(matrix, { scale: 4 }), { formats: ['qr'] });
console.log(found[0].text); // 'https://example.com'There are four ways in, and none of them needs a build step.
npm install @sythos/js_barcode_universalyarn add @sythos/js_barcode_universal and pnpm add @sythos/js_barcode_universal do the same thing. Nothing is installed
alongside it — there are no runtime dependencies, no postinstall script and no native build. The
package is plain ESM ("type": "module") and asks for Node 24 or newer.
import { encode, decode, toSVG, toImageData } from '@sythos/js_barcode_universal';
const code = encode('SYT-2026-0042', { format: 'code128' });
const svg = toSVG(code, { scale: 2, margin: 10, barHeight: 60 });
// '<svg xmlns="http://www.w3.org/2000/svg" width="374" height="100" …'
const found = decode(toImageData(code, { scale: 4, margin: 10 }), { formats: ['code128'] });
console.log(found[0].text); // 'SYT-2026-0042'Subpath exports hand you one layer instead of the whole surface, which is what lets a tree-shaking bundler drop everything you did not ask for. Importing only the QR writer and only the SVG renderer never pulls in the 1D formats, the PNG encoder or the read pipeline:
import { encodeQR } from '@sythos/js_barcode_universal/qr';
import { toSVG } from '@sythos/js_barcode_universal/render/svg';
const svg = toSVG(encodeQR('https://example.com', { ecc: 'M' }), { scale: 8 });
// a 25×25 module symbol — 264×264 px at scale 8 with the default 4-module quiet zone| Subpath | What it exports |
|---|---|
@sythos/js_barcode_universal |
The whole surface: encode, decode, every renderer, every error type |
@sythos/js_barcode_universal/core |
BitMatrix, GaloisField, Reed–Solomon, the error classes |
@sythos/js_barcode_universal/image |
LuminanceSource, the binarizers, grid sampling, PerspectiveTransform |
@sythos/js_barcode_universal/oned |
The per-format 1D writers (encodeEAN13, encodeCode128, …) and decodeOneD |
@sythos/js_barcode_universal/qr |
encodeQR, decodeQR, detectQR, detectAndDecodeQR |
@sythos/js_barcode_universal/datamatrix |
encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix |
@sythos/js_barcode_universal/aztec |
encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec |
@sythos/js_barcode_universal/aztecrune |
encodeAztecRune, decodeAztecRune, detectAztecRune, detectAndDecodeAztecRune |
@sythos/js_barcode_universal/pdf417 |
encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 |
@sythos/js_barcode_universal/compactpdf417 |
encodeCompactPDF417, decodeCompactPDF417, detectCompactPDF417, detectAndDecodeCompactPDF417 |
@sythos/js_barcode_universal/databar |
GS1 DataBar GTIN/AI codecs plus Omnidirectional/Truncated physical helpers |
@sythos/js_barcode_universal/micropdf417 |
encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417 |
@sythos/js_barcode_universal/microqr |
encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR |
@sythos/js_barcode_universal/rmqr |
encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR |
@sythos/js_barcode_universal/frameqr |
encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR |
@sythos/js_barcode_universal/render |
Every renderer plus isWebGL2Available / isWebGPUAvailable |
@sythos/js_barcode_universal/render/svg |
toSVG, toSVGDataURI |
@sythos/js_barcode_universal/render/png |
toPNG, toPNGDataURI |
@sythos/js_barcode_universal/render/image-data |
toImageData, toCanvas |
@sythos/js_barcode_universal/bundle |
The prebuilt ESM bundle, as one file |
@sythos/js_barcode_universal/bundle/iife |
The prebuilt IIFE bundle, for a <script> tag |
The unpkg and jsdelivr fields point at the IIFE bundle, so a CDN needs no install at all:
<script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
<script src="https://unpkg.com/@sythos/js_barcode_universal@1.5.13"></script>
<script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.5.13"></script>Pin the version for anything you ship; the unpinned form resolves to latest and will move under
you.
Both CDNs serve the same file the repository ships in
bundle/sythos-barcode.js — byte for byte, since that is exactly what
npm publishes.
bundle/sythos-barcode.js is a self-contained IIFE that exposes a
single global, SythosBarcode. It works straight from file:// — open an HTML file off your
disk and it runs.
<script src="bundle/sythos-barcode.js"></script>
<script>
var encode = SythosBarcode.encode;
var toSVGDataURI = SythosBarcode.toSVGDataURI;
var img = new Image();
img.src = toSVGDataURI(encode('https://example.com', { format: 'qr' }), { scale: 8 });
document.body.appendChild(img);
</script>bundle/sythos-barcode.esm.js is the same code as a single ES
module, for <script type="module">, a bundler, or Node.
import { encode, toSVG, toPNG } from './bundle/sythos-barcode.esm.js';
const ean = encode('5901234123457', { format: 'ean13' });
const svg = toSVG(ean, { scale: 3, margin: 10, barHeight: 80 });
// '<svg xmlns="http://www.w3.org/2000/svg" width="345" height="141" …'
toPNG(ean, { scale: 3, barHeight: 80 }).then((bytes) => {
// Uint8Array — a 1-bit palette PNG
});src/index.js is the generated ESM facade emitted from the TypeScript source
tree. The source layout is deliberately explicit:
src/ts/contains the TypeScript runtime sources and their adjacent machine-readable.d.tsdeclarations, includingsrc/ts/index.tsandsrc/ts/index.d.ts.src/js/contains the compiled JavaScript runtime modules used by Node, browsers and the CDN bundles.src/index.jsandsrc/index.d.tsare the stable package-root facades.
From the development workspace, npm run build:ts compiles src/ts/ into src/js/ and the
root JavaScript facade; npm run build then regenerates both bundles. The published package
keeps both source languages visible while retaining zero runtime dependencies.
The package subpath exports therefore pair compiled runtime modules under src/js/ with their
TypeScript sources and declarations under src/ts/; direct source imports should continue to use
the JavaScript facade above.
import { encode, decode, toImageData, listFormats } from './src/index.js';
const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
const image = toImageData(matrix, { scale: 4, margin: 4 });
const found = decode(image, { formats: ['qr'] });
// [ { text: 'https://example.com', format: 'qr', version: 2, ecc: 'M', … } ]Decoding takes anything ImageData-shaped, so a canvas, an OffscreenCanvas,
createImageBitmap, or an image library's raw buffer all satisfy it without an adapter:
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
for (const hit of decode(ctx.getImageData(0, 0, canvas.width, canvas.height))) {
console.log(hit.format, hit.text);
}decode returns an array, empty when nothing is found. A frame with no barcode is an ordinary
outcome for a camera loop, not an error, so the common case needs no try/catch. Use
decodeStrict when absence really is a failure.
For a live camera loop, opt into the stricter 1D policy:
decode(frame, { formats, profile: 'camera', tryHarder: true })The profile requires a compatible quiet zone and the same complete 1D symbol on at least two
scan samples. It evaluates the eight fixed in-plane orientations 0°, 45°, 90°, 135°,
180°, 225°, 270° and 315° when the native orientation has no validated read. The same
orientation set is available to the supported 2D detector passes in the strict camera profile.
Code 11 and MSI require a verified check digit in this profile; other formats retain their own
structural and checksum validation. A frame without a validated barcode still returns []. No
partial, structurally inconsistent or low-confidence value is emitted to the caller. This is a
finite in-plane retry policy, not a guarantee for arbitrary perspective, curved media, severe
occlusion or multi-symbol scenes.
Camera-profile 1D results add confidence (0–1), bounds, rotation, and
quality: { quietZone, checksum, rows, consistency }. bounds is reported in the raster
orientation that was scanned; unavailable quality data is represented by null where applicable.
Generated from listFormats(), which reports writing and reading as separate capabilities.
Writing a symbology is a table lookup; reading one needs a detector that finds it in a
photograph. The two lists legitimately differ, and saying so here is better than failing at call
time.
| Format | id |
Kind | Write | Read |
|---|---|---|---|---|
| EAN-13 | ean13 |
1D | ✅ | ✅ |
| EAN-8 | ean8 |
1D | ✅ | ✅ |
| UPC-A | upca |
1D | ✅ | ✅ |
| UPC-E | upce |
1D | ✅ | ✅ |
| ISBN (Bookland) | isbn |
1D | ✅ | ✅ 1 |
| Code 128 | code128 |
1D | ✅ | ✅ |
| GS1-128 | gs1128 |
1D | ✅ | ✅ 1 |
| Code 39 | code39 |
1D | ✅ | ✅ |
| Code 93 | code93 |
1D | ✅ | ✅ |
| ITF (Interleaved 2 of 5) | itf |
1D | ✅ | ✅ |
| ITF-14 | itf14 |
1D | ✅ | ✅ 1 |
| Codabar | codabar |
1D | ✅ | ✅ |
| Code 11 | code11 |
1D | ✅ | ✅ |
| MSI Plessey | msi |
1D | ✅ | ✅ |
| Pharmacode | pharmacode |
1D | ✅ | — |
| QR Code | qr |
2D | ✅ | ✅ |
| Data Matrix ECC 200 | datamatrix |
2D | ✅ | ✅ |
| Aztec Code | aztec |
2D | ✅ | ✅ |
| PDF417 | pdf417 |
2D | ✅ | ✅ |
| MicroPDF417 | micropdf417 |
2D | ✅ | ✅ |
| Micro QR Code | microqr |
2D | ✅ | ✅ |
| rMQR Code | rmqr |
2D | ✅ | ✅ |
| Sythos Canvas QR profile — not DENSO FrameQR® compatible | frameqr |
2D | ✅ | ✅ |
| Aztec Rune | aztecrune |
2D | ✅ | ✅ |
| Compact PDF417 | compactpdf417 |
2D | ✅ | ✅ |
| GS1 DataBar Omnidirectional / Truncated | gs1databar14 |
1D | ✅ | ✅ |
| EAN-2 supplement | ean2 |
1D | ✅ | ✅ 2 |
| EAN-5 supplement | ean5 |
1D | ✅ | ✅ 2 |
Twenty-eight listed formats are writable and twenty-seven are readable (EAN-2 and EAN-5 are parent-bound supplements). Pharmacode remains intentionally write-only in the generic image pipeline. Code 11 and MSI Plessey use the scanline reader; GS1 DataBar uses the Omnidirectional/Truncated scanline layer over the verified GTIN decoder. PDF417 exposes direct matrix decoding, automatic camera localization and an assisted quadrilateral sampler through its subpath. Its detector is validated on degraded synthetic photographs and real Pixel 10/Chrome and iPhone 17/Safari camera tests; external black-box vectors from ZXing 3.5.3 and bwip-js also pass in both directions. Text and Numeric vectors are covered bidirectionally; binary byte-for-byte interop remains explicitly unclaimed until a dedicated external byte corpus is added.
The generic image pipeline now recognizes Code 11 and MSI Plessey through the existing
scanline reader. Check-digit validation is opt-in with decode(image, { checkDigit: true });
without that option the physical grammar is still required, while the literal check character is
preserved for MSI and reliably stripped for Code 11 when its C/K grammar is unambiguous. The
reader keeps Pharmacode write-only because its unframed narrow/wide grammar is not safe for
unrestricted image autodetection.
datamatrix writes and reads the 30 classic ECC 200 square and rectangular symbol sizes. The
encoder supports ASCII compaction (including numeric pairs), Base256 binary payloads, automatic
or forced square/rectangular shape, Reed–Solomon error correction and GS1 FNC1 in the first
position. DMRE is not included.
import { encodeDataMatrix, decodeDataMatrix } from '@sythos/js_barcode_universal/datamatrix';
const symbol = encodeDataMatrix('0101234567890128', { gs1: true, shape: 'square' });
const result = decodeDataMatrix(symbol);
console.log(result.text, result.gs1); // 0101234567890128 trueBinary content is accepted as a Uint8Array with encoding: 'base256'. The current high-level
decoder handles ASCII and Base256 codewords; C40, Text, X12 and EDIFACT input symbols are not yet
decoded. The current detector accepts axis-aligned square or rectangular symbols; with
profile: 'camera', the decode pipeline evaluates the eight fixed in-plane orientations at 45°
steps. Arbitrary perspective and perspective-skewed Data Matrix photographs are not yet guaranteed.
aztec writes Compact layers 1–4 and Full layers 1–32, selecting a fitting symbol automatically
unless layers and compact are forced. It supports the five Aztec text tables and UTF-8 byte
payloads through Binary Shift, with eccPercent (default 23) controlling the requested
error-correction level.
ECI is not yet a configurable public option.
import { encodeAztec, decodeAztec } from '@sythos/js_barcode_universal/aztec';
const symbol = encodeAztec('Greetings My Lord Sythos 👋', { eccPercent: 23 });
const result = decodeAztec(symbol);
console.log(result.text); // Greetings My Lord Sythos 👋The image detector handles the eight fixed camera-profile orientations, inverted polarity and quadrilateral sampling around the central bull’s-eye. Severe photographic perspective remains an interoperability and robustness gate rather than a guaranteed capability.
pdf417 supports PDF417 Text, Byte and Numeric compaction, ECI 3 (ISO-8859-1) and ECI 26
(UTF-8), ECC levels 0–8, row-height inference and Reed–Solomon erasure correction. The direct
matrix decoder is available from @sythos/js_barcode_universal/pdf417.
The image helper handles clean module-aligned raster symbols, integer scale, fixed 45°-step
camera orientations, automatic perspective estimation, mild blur/noise and an application-supplied
quadrilateral. Results expose bytes and ordered segments for byte-preserving payloads. Real
device validation covers Pixel 10/Chrome and iPhone 17/Safari with printed symbols and continuous
camera capture. Extreme glare, severe occlusion, curved media and multi-symbol scenes remain
outside the validated robustness envelope.
import { encodePDF417, decodePDF417 } from '@sythos/js_barcode_universal/pdf417';
const symbol = encodePDF417('AAMVA SAMPLE', { eccLevel: 3 });
console.log(decodePDF417(symbol).text);micropdf417 writes and reads the 34 fixed MicroPDF417 variants. It supports Text, Byte and
Numeric compaction, plus Byte-compaction ECI 3 (ISO-8859-1) and 26 (UTF-8). columns,
rowHeight and aspectRatio let callers constrain automatic variant selection.
import { encodeMicroPDF417, decodeMicroPDF417 } from '@sythos/js_barcode_universal/micropdf417';
const symbol = encodeMicroPDF417('MICRO PDF417', { compaction: 'text' });
console.log(decodeMicroPDF417(symbol).text);The detector accepts clean, integer-scaled raster symbols and the eight fixed camera-profile orientations. Arbitrary perspective, severe photographic degradation and multi-symbol scenes are not yet claimed as robust capabilities.
microqr implements the M1–M4 family with Numeric, Alphanumeric, ISO-8859-1 Byte and Kanji
payloads, BCH format protection, the four Micro QR masks and Reed–Solomon correction. M1 is
detection-only. ECI, FNC1/GS1 and Structured Append are intentionally outside the current API.
The detector accepts clean scaled rasters, the eight fixed camera-profile orientations, inverted
polarity and mild projective sampling, and rejects normal QR Model 2 symbols. Arbitrary perspective
and curved-media robustness are not claimed.
import { encodeMicroQR, decodeMicroQR } from '@sythos/js_barcode_universal/microqr';
const symbol = encodeMicroQR('12345', { version: 'M2', ecc: 'L' });
console.log(decodeMicroQR(symbol).text);rmqr implements all 32 standard rectangular geometries, M/H ECC, Numeric, Alphanumeric, Byte,
Kanji and ECI payloads. The detector accepts clean integer-scaled rasters, quiet zones and the
eight fixed camera-profile orientations; arbitrary photographic perspective and multi-symbol
scenes are not claimed.
import { encodeRMQR, decodeRMQR } from '@sythos/js_barcode_universal/rmqr';
const symbol = encodeRMQR('rMQR SAMPLE', { ecc: 'M' });
console.log(decodeRMQR(symbol).text);frameqr is an explicitly scoped, non-certified Sythos Canvas QR profile — not DENSO FrameQR® compatible. It reserves a bounded
square, circle or diamond artwork canvas inside an ECC-H QR Model 2 symbol. It is not a native
DENSO FrameQR encoder or decoder, and the package makes no DENSO interoperability claim. The
profile can be read from clean rendered rasters and is exposed through the normal encode/decode
API and the frameqr subpath.
import { encodeFrameQR, decodeFrameQR } from '@sythos/js_barcode_universal/frameqr';
const symbol = encodeFrameQR('https://www.sythos.net/', {
canvas: { shape: 'square', size: 5 },
});
console.log(decodeFrameQR(symbol).text);The canonical examples/create.html preview loads https://www.sythos.net/favicon.ico and
falls back to https://www.sythos.net/apple-touch-icon.png. The image is never copied into the
repository; if browser CORS prevents safe compositing, the page keeps a preview overlay and
exports the QR symbol without embedding the remote artwork.
aztecrune implements the fixed 11×11 Rune values 0–255 with clean raster
detection, inversion and the eight fixed camera-profile orientations. Its matrices were compared
exhaustively with ZXing-C++ as an independent black-box runtime; no ZXing
source or table is shipped.
compactpdf417 implements the truncated PDF417 geometry with Text, Byte and
Numeric compaction. It has a clean raster detector and direct matrix decoder.
EAN-2 and EAN-5 are writable supplements exposed by the oned subpath and by
the generic ean2 and ean5 format IDs. The image reader recognizes them only
when attached to a validated EAN/UPC parent; use the composition helpers with
an EAN/UPC base symbol.
EAN-2 and EAN-5 are parent-bound supplements, never standalone image results. They may be
listed with EAN-13, EAN-8, UPC-A, UPC-E or Bookland ISBN in formats: the parent remains valid
without a supplement, and a valid requested supplement is exposed only through result.addon.
If only ean2 or ean5 is requested, a validated EAN/UPC parent is still required and remains
the returned format; an absent, malformed or unrequested supplement never rejects the parent.
The databar subpath exposes original GS1 GTIN/AI codecs plus physical
Omnidirectional and Truncated writers, scanline readers and clean-matrix decoders. Four GTIN
vectors were compared bit-for-bit with Zint 2.16.0 as a black box. Limited,
Stacked, Stacked Omnidirectional and Expanded physical layouts remain planned;
their data-layer helpers do not imply complete scanner support.
GS1 DataBar physical support currently covers Omnidirectional and Truncated writing plus
scanline and clean-matrix decoding; Limited, Stacked and Expanded physical layouts remain planned. MaxiCode is
not implemented. Data Matrix ECC
200 is implemented for its classic square and rectangular symbols;
DMRE remains outside the current scope. See PLAN.md for the remaining symbologies.
Two self-contained pages, each loading the IIFE bundle with a plain <script> tag. Both open
directly from disk — double-click the file, no server and no build. This examples/ directory is
the single canonical source; the development workspace references these files instead of keeping
a second copy.
Pick any writable format, type a payload, and watch the symbol redraw as you type; download it as PNG or SVG. For QR it adds a content-type builder that assembles the payload for you across URL, email, phone, SMS, Wi-Fi network, contact card (both vCard and MeCard), geo location and calendar event — with correct escaping for each — and shows you the exact string it produced, so you can see what a Wi-Fi or vCard QR actually contains. ECC level, version, scale, margin and both colours are exposed.
Decode from an image: drop one onto the page, or click to choose a file. It then offers a live camera loop that decodes continuously from the video stream.
The file and drag-drop path works anywhere,
file://included. The camera needs http(s), becausegetUserMediarequires a secure context and refuses to run fromfile://. Serve the folder over localhost for that half; the page detects the situation and says so rather than failing silently.
The full, searchable documentation lives on GitHub Pages
and is built from the checked-in docs/ tree with MkDocs Material. It includes the
API reference,
format catalogue,
camera and image guides,
practical recipes,
FAQ and
troubleshooting.
Every documentation change is checked for local links, navigation coverage and registry drift in CI before the Pages build is published. The compact README remains the versioned project overview; the Pages site is the place for the longer explanations and copy-ready examples.
Two functions carry the whole surface. Everything else is a renderer or a format-specific escape hatch.
encode(text, options?) → BitMatrixoptions: format (default 'qr'), ecc ('L'|'M'|'Q'|'H'), version (QR 1–40, auto if
omitted), checkDigit, fullAscii (Code 39 extended), gs1 (emit a leading FNC1). Data Matrix
ECC 200 accepts shape: 'any' | 'square' | 'rectangular' and encoding: 'ascii' | 'base256'.
Aztec accepts layers, compact and eccPercent; it transports UTF-8 byte payloads through
Binary Shift, and it does not expose configurable ECI yet.
MicroPDF417 accepts compaction: 'auto' | 'text' | 'byte' | 'numeric', ECI 3 or 26 for Byte
compaction, and optional columns, rowHeight and aspectRatio constraints.
Micro QR accepts version: 'M1' | 'M2' | 'M3' | 'M4', its legal ECC level and mask; its
unsupported ECI, FNC1/GS1 and Structured Append features are rejected explicitly. rMQR accepts
ecc: 'M' | 'H', optional geometry/version constraints and ECI for byte payloads. The FrameQR
Code profile accepts canvas: { shape: 'square' | 'circle' | 'diamond', size, width, height, centerX, centerY, angle }; it is non-certified and separate from DENSO FrameQR.
encode('5901234123457', { format: 'ean13' })
encode('ABC-123', { format: 'code39', fullAscii: true, checkDigit: true })
encode('https://example.com', { format: 'qr', ecc: 'H', version: 7 })
encode('0101234567890128', { format: 'datamatrix', gs1: true })
encode('Greetings My Lord Sythos 👋', { format: 'aztec', eccPercent: 23 })
encode('MICRO PDF417', { format: 'micropdf417', compaction: 'text' })
encode('12345', { format: 'microqr', version: 'M2', ecc: 'L' })
encode('rMQR SAMPLE', { format: 'rmqr', ecc: 'M' })
encode('https://www.sythos.net/', {
format: 'frameqr',
canvas: { shape: 'square', size: 5 },
})decode(image, options?) → Result[]
decodeStrict(image, options?) → Result // throws NotFoundError instead of returning []image is { data, width, height } with RGBA bytes. options: formats (restrict the search,
and go faster), tryHarder (retry inverted, default true), binarizer
('global' | 'hybrid' | 'auto'). A Result carries at least text and format; QR results also
carry bytes, version and ecc.
For larger clean QR Code and PDF417 rasters, auto and hybrid retain their primary local-threshold pass and retry once with the global threshold only when that pass finds no result. An explicit binarizer: 'global' request remains single-pass.
listFormats() → { id, label, canWrite, canRead, kind }[]The table above is this function's output. Read it at runtime rather than hard-coding a format list — that is how the demo pages build their dropdowns.
toSVG(matrix, options?) → string // one merged <path>, not a rect per module
toSVGDataURI(matrix, options?) → string // data: URI for an <img src>
toImageData(matrix, options?) → { data, width, height }
toPNG(matrix, options?) → Promise<Uint8Array> // 1-bit palette PNG
toPNGDataURI(matrix, options?) → Promise<string>
toCanvas(matrix, canvas, options?) → boolean // 2D context
renderToCanvasAuto(matrix, canvas, options?) → { backend: 'webgl2' | '2d' | 'none' }
renderToCanvasAutoAsync(matrix, canvas, options?) → Promise<{ backend: 'webgpu' | 'webgl2' | '2d' | 'none' }>The two PNG functions are async because they use the platform's deflate — node:zlib or
CompressionStream — and fall back to stored blocks where neither exists.
renderToCanvasAuto is synchronous and therefore cannot reach WebGPU: acquiring an adapter is
asynchronous, and a synchronous function can never wait for one. Use renderToCanvasAutoAsync
when you want WebGPU in the chain. Both fall through to the 2D context, which always exists.
All renderers share the same options:
| Option | Default | Meaning |
|---|---|---|
scale |
8 |
Pixels per module |
margin |
4 |
Quiet-zone modules on every side |
dark |
'#000000' |
Colour of set modules |
light |
'#ffffff' |
Colour of clear modules; 'none' for transparent |
barHeight |
auto | 1D only: total bar height in pixels |
Also exported: BitMatrix, the error types (BarcodeError, EncodeError, NotFoundError,
FormatError, ChecksumError), the per-format writers (encodeEAN13, encodeCode128, …), the
QR entry points (encodeQR, decodeQR, detectQR, detectAndDecodeQR), the image primitives
(LuminanceSource, binarize, binarizeGlobal, binarizeHybrid), and the capability probes
isWebGL2Available / isWebGPUAvailable.
BitMatrix is the interchange type. Every writer produces one, every reader consumes one,
every renderer draws one. That single currency is what keeps symbologies and output targets
independent of each other — adding a format touches no renderer, and adding a renderer touches no
format. Storage is row-packed into a Uint32Array: one allocation, cache-friendly row scans, and
cheap whole-row operations for the 1D readers.
A set bit is a dark module. This matches how every specification describes its symbols; renderers invert where their medium needs it.
encode returns no quiet zone. The margin is a rendering decision, not an encoding one — how
much white space a symbol needs depends on where it is going — so the renderers add it and the
matrix stays the pure symbol. For the same reason, linear symbols come back exactly one module
tall: height carries no information in a 1D barcode, so encoding one would be inventing data.
The renderer stretches the single row to barHeight before applying the quiet zone, so the
margin ends up uniform on all four sides.
The read pipeline is a straight line, each stage a separate module:
RGBA bytes → luminance → binarize → detect → sample → error-correct → decode
Luminance conversion flattens the image to greyscale. Binarization turns that into a BitMatrix,
either globally or with a hybrid local threshold that survives uneven lighting. Detection locates
a symbol and its corners in that bit plane. Detectors that recover four perspective-aware corners
(currently QR) sample the symbol back through a perspective transform. Data Matrix currently uses
an axis-aligned bounding box; the strict camera profile evaluates the eight fixed in-plane
orientations before error correction repairs what the camera lost. Only then is the payload decoded.
Reed–Solomon is generic over the finite field. The GaloisField class is constructed with a
field order and a primitive polynomial rather than hard-coding GF(256), which is what lets one
implementation serve QR, and the prime field GF(929) that PDF417 needs. Prime fields are the
subtle case: in a binary field addition and subtraction are both XOR, so a decoder that inlines
^ for field addition passes every binary field and fails only the prime one.
The WebGL2 and WebGPU backends accelerate drawing a barcode, not computing one. That is worth stating plainly, because "GPU barcode generation" naturally suggests the latter.
Encoding is sequential integer work: Reed–Solomon polynomial division, mask penalty scoring, bit placement along a zig-zag path. Each step depends on the one before it, which is precisely the shape a GPU cannot exploit. A complete QR encode takes well under a millisecond on the CPU — less time than dispatching a compute shader and reading the result back would cost. Moving it to the GPU would make it slower.
So encoding stays on the CPU because that is the correct engineering answer, not because something is missing. Where the GPU genuinely earns its place is drawing large symbols, or many symbols per frame, straight into a canvas without a CPU-side pixel buffer — and it would earn it again on the read side, where per-frame greyscale conversion and block statistics over a 4K camera image are both the real bottleneck and embarrassingly parallel.
The syntax floor is iOS Safari 15. No Array.prototype.at, no top-level await, no
Object.groupBy.
OffscreenCanvas, WebGL2 and WebGPU are feature-detected, never assumed. The 2D canvas path
always exists, so nothing is unreachable on an older device — renderToCanvasAuto degrades to it
and reports which backend actually drew.
The repository is covered by GitHub's configured CodeQL default setup for JavaScript and TypeScript code-scanning analysis. GitHub manages the analysis schedule and the security-events upload; this avoids running a duplicate advanced workflow alongside the repository-level default setup.
Dependabot checks the development TypeScript toolchain and GitHub Actions references weekly. Updates are repository-development controls only; the published SDK remains zero-dependency at runtime.
These security workflows report findings and propose maintenance updates. They do not replace review of barcode conformance, licensing, patent status or release attestations.
Security reports follow the security policy. Please use GitHub's private reporting
channel for every suspected vulnerability; public Issues are for non-security bugs after security
impact has been ruled out. Reports involving code execution, data or secret exposure, CI/release/npm
integrity or host/runner compromise should also be sent to devsec@sythos.net. Exploit details are
better kept private than turned into an accidental community fireworks show.
The image I/O boundary treats camera and file rasters as untrusted input. It validates finite positive dimensions, bounds allocations to 16,777,216 pixels — still a high limit, roughly twice the pixel count of a standard 4K image — and accepts only byte-valued channels, and snapshots greyscale buffers before decoding. Malformed or oversized rasters are rejected before detector work begins.
Rendering applies the same allocation discipline before creating an SVG, PNG, ImageData, canvas,
WebGL or WebGPU output. Matrix dimensions, scale, margin and barHeight must be safe integers
within documented bounds, and the final image must be no larger than 16,384 pixels on either side
or 16,777,216 pixels in total. Invalid, fractional or oversized values are rejected with
RangeError; callers that expose rendering controls should handle that result rather than silently
coercing it.
The browser examples treat decoded payloads and browser error messages as text. They create DOM
nodes and set textContent instead of interpolating those values into HTML, so scanned content is
not interpreted as markup.
The package publication workflow in .github/workflows/npm-publish.yml
uses npm provenance when publishing to the public npm registry. npm provenance and GitHub Artifact
Attestations are related but separate records:
- npm provenance is issued by npm for a package publication and links the published package to its source repository and trusted build workflow;
- GitHub Artifact Attestations bind a build artifact, its digest and its build context to the GitHub Actions workflow that produced it. They can be verified independently of the npm registry.
Every third-party GitHub Action referenced by the repository workflows is pinned to its immutable commit SHA. Dependabot tracks the action references, but an update is still reviewed and then records a new explicit SHA rather than relying on a movable tag.
The development toolchain is also reproducible: package-lock.json records the resolved packages,
TypeScript is declared at an exact version, and the pull-request and release validation workflows
install the lockfile with npm ci --ignore-scripts. The published SDK remains free of runtime
dependencies.
The release-specific attestation workflow is expected at
.github/workflows/release.yml. Release assets must be verified
against this repository and their exact local file contents:
gh attestation verify path/to/release-asset.tgz -R Sythos/JS_Barcode_UniversalThe same command can be used for any other attested release asset by replacing the path. An attestation confirms build provenance; it is not an ISO barcode-conformance certificate, a patent clearance, or a guarantee that the implementation is vulnerability-free.
Release automation validates the package version against the selected Git tag; it does not invent
or increment versions by itself. The current release is 1.5.13.
MIT © 2026 Sythos (https://www.sythos.net). Every source file carries the header.
The implementation is original Sythos work. No third-party barcode source code is copied into
or shipped by this package. The symbologies are implemented from published descriptions of the
formats and from original Sythos data structures. MicroPDF417, Micro QR, rMQR and the Sythos
Canvas QR profile carry provenance and pending legal review in NOTICE.md. Independent
implementations and public technical material may be consulted for engineering review or
black-box verification; no third-party source code is copied or shipped. The distributed package has no runtime third-party
dependencies. See NOTICE.md and the per-format files in licenses/.
Public DENSO FrameQR material was consulted only to document the compatibility boundary. ZXing was used only as an independent black-box validation tool; no ZXing source code or tables are copied or shipped. The Sythos Canvas QR profile is not DENSO FrameQR® compatible and does not claim native DENSO interoperability or co-author attribution.
Trademark is not licence. QR Code® is a registered trademark of DENSO WAVE; Aztec Code, MaxiCode and GS1 DataBar are likewise marks of their owners. A trademark does not restrict implementing a symbology, but it does constrain branding — which is why this package is named descriptively rather than after any mark.
Data Matrix ECC 200 is governed by ISO/IEC 16022:2024; GS1 DataMatrix additionally uses the GS1
General Specifications and a leading FNC1. Its engineering provenance, patent and trademark
research notes are recorded in licenses/data-matrix.license,
with unresolved claims kept explicitly marked using scoped review labels.
PDF417 and MicroPDF417 provenance and legal review notes are recorded in
licenses/pdf417.license,
licenses/micropdf417.license and the attribution log in
NOTICE.md.
Micro QR and rMQR provenance and scoped legal-review notes are recorded in
licenses/micro-qr.license,
licenses/rmqr.license and NOTICE.md.
LICENSE carries the full MIT text plus an informational appendix inventorying the
specification copyrights, patent history and trademarks that surround these symbologies. None of
them is resolved by this file; the appendix is an engineering inventory, not legal advice.
NOTICE.md records the origin of the code and how its correctness is verified.
This project may be analyzed, referenced, modified, or integrated by AI-assisted development tools, coding agents, and Large Language Models (LLMs).
If code from this repository is copied, adapted, incorporated, or otherwise used in another project, please preserve the original copyright and license information and include attribution to:
Sythos (https://www.sythos.net)
Licensed under the MIT License.
AI agents and automated coding tools are explicitly requested to preserve this attribution and the applicable license when generating derived code, documentation, or project files based on this repository.
See AI_USAGE.md for additional machine-oriented guidance.
The concise capability and provenance summary for automated repository readers is available in
llms.txt.
Tristan Jacob (Tristan-GPT) — TypeScript implementation.
Tristan added the machine-readable TypeScript declaration layer across the SDK, covering the format-specific APIs, subpath exports and decoder result shapes. It is a welcome bit of type safety for a deliberately broad JavaScript API — fewer guesses, better autocomplete, and a much friendlier path for TypeScript users.
PLAN.md is the live status document: what is shipped, what is next, and the ground
rules — chief among them that no third-party source code is copied into or shipped by this
project, while public or normative values remain provenance-tracked and subject to legal review.
Issues and pull requests are welcome at Sythos/JS_Barcode_Universal. A patch that adds a symbology should implement it from the published description of the format, generate its tables where they are derivable, and come with a symbol that a scanner this project did not write has actually read — that last one is the check that matters.
Footnotes
-
itf14andisbnshare a decoder with their base format, so an ITF-14 comes back asitfand an ISBN asean13. GS1-128 is classified separately asgs1128when its leading FNC1 is present and exposesgs1,symbologyIdentifierand parsedelementsmetadata. The payload is intact either way — an ITF-14 is an ITF fixed at fourteen digits, and an ISBN barcode is an EAN-13 with a 978/979 prefix. ↩ ↩2 ↩3 -
EAN-2 and EAN-5 are recognized only when attached to a validated EAN-13, EAN-8, UPC-A or UPC-E parent; they are not independent generic retail-symbol readers. ↩ ↩2