README.md
August 10, 2026 · View on GitHub
etiket
Zero-dependency barcode & QR code generator — SVG & PNG output.
40+ formats, styled QR codes, tree-shakeable. Pure TypeScript, works everywhere.
Important
Every symbology is verified against an implementation that is not this one — decoded back with zxing-wasm or jsQR where a decoder exists, compared module for module with bwip-js (BWIPP) where none does. See Verification.
One exception: JAB Code. No JavaScript or WebAssembly decoder exists and neither zxing nor BWIPP implements it, so its output cannot be checked. It is marked experimental and says so in its own API docs.
Contributions welcome! If you find a scanning issue or want to improve an encoder, please open an issue or submit a PR. See Contributing below.
Quick Start
npm install etiket
import { barcode, qrcode } from "etiket"
const svg = barcode("Hello World")
const qr = qrcode("https://example.com", { dotType: "dots", ecLevel: "H" })
CLI
npx etiket list # every supported symbology
npx etiket qr "Hello World" -o qr.svg
npx etiket qr "Hello" --terminal
npx etiket qr "Hello" --size 300 --ec H --dot-type dots
npx etiket qr "Hello" -o qr.png # .png output writes a PNG
npx etiket barcode "4006381333931" --type ean13 --show-text -o barcode.svg
npx etiket postal "SN34RD1A" --type rm4scc -o postal.svg
npx etiket datamatrix "Hello" -o dm.svg
npx etiket pdf417 "Hello" -o pdf.svg
npx etiket aztec "Hello" -o aztec.svg
npx etiket maxicode "Hello" -o maxi.svg
npx etiket wifi "MyNetwork" "secret123" -o wifi.svg
npx etiket contact "Ada Lovelace" --email ada@example.com -o card.svg
Every symbology has a subcommand (qr, microqr, rmqr, barcode, postal,
datamatrix, pdf417, micropdf417, aztec, maxicode, dotcode, hanxin,
codablockf, code16k, jabcode) plus the wifi, contact and link
helpers. Add --png or use a .png output path for raster output.
Tree Shaking
Import only what you need:
import { barcode, barcodeDataURI, barcodeBase64 } from "etiket/barcode"
import { postal, encodePostal } from "etiket/postal"
import { qrcode, qrcodeDataURI, qrcodeBase64, qrcodeTerminal, microqr, rmqr } from "etiket/qr"
import { datamatrix, gs1datamatrix } from "etiket/datamatrix"
import { pdf417, micropdf417 } from "etiket/pdf417"
import { aztec } from "etiket/aztec"
import { maxicode, dotcode, hanxin, codablockf, code16k } from "etiket/2d"
import { barcodePNG, qrcodePNG, postalPNG } from "etiket/png" // PNG output
import { validateBarcode, validateQRInput } from "etiket/validators"
import { EtiketError, InvalidInputError, CapacityError } from "etiket/errors"
Supported Formats
1D Barcodes
| Format | Type | Description |
|---|---|---|
| Code 128 | code128 | Auto charset (A/B/C) |
| Code 39 | code39 | 43-char set, optional check |
| Code 39 Ext | code39ext | Full ASCII |
| Code 93 | code93 | Higher density, 2 check digits |
| Code 93 Ext | code93ext | Full ASCII |
| EAN-13 | ean13 | Auto check digit |
| EAN-8 | ean8 | Auto check digit |
| EAN-5 | ean5 | Addon (book price) |
| EAN-2 | ean2 | Addon (issue number) |
| UPC-A | upca | 12-digit, auto check digit |
| UPC-E | upce | Compressed 8-digit |
| ITF | itf | Interleaved 2 of 5 |
| ITF-14 | itf14 | 14-digit with bearer bars |
| Codabar | codabar | Libraries, blood banks |
| MSI Plessey | msi | Mod10/11/1010/1110 |
| Pharmacode | pharmacode | Pharmaceutical |
| Code 11 | code11 | Telecommunications |
| GS1-128 | gs1-128 | AI parsing, FNC1, 100+ AIs |
| GS1 DataBar | gs1-databar | Omnidirectional, 14-digit GTIN |
| GS1 DataBar Limited | gs1-databar-limited | GTIN starting with 0/1 |
| GS1 DataBar Expanded | gs1-databar-expanded | Variable-length AI data |
| Identcode | identcode | Deutsche Post / DHL |
| Leitcode | leitcode | Deutsche Post routing |
| POSTNET | postnet | USPS legacy postal |
| PLANET | planet | USPS confirmation tracking |
| Plessey | plessey | UK library systems |
| GS1 DataBar Truncated | gs1-databar-truncated | Short-height omnidirectional |
| EAN-14 | ean14 | GTIN-14 as a GS1-128, AI (01) |
| SSCC-18 | sscc18 | Shipping container, AI (00) |
| ISBN | isbn | ISBN-10 or ISBN-13, over EAN-13 |
| ISSN | issn | Serials, with sequence variant |
| ISMN | ismn | Printed music, 9790 prefix |
| Code 32 | code32 | Italian Pharmacode, over Code 39 |
| PZN-7 / PZN-8 | pzn / pzn8 | German pharmaceutical number |
2D Codes
| Format | Function | Description |
|---|---|---|
| QR Code | qrcode() | Versions 1-40, all EC levels, all modes |
| Micro QR | microqr() | M1-M4 (11x11 to 17x17) |
| Data Matrix | datamatrix() | ECC 200, ASCII/C40/Text auto encoding |
| GS1 DataMatrix | gs1datamatrix() | FNC1 + AI parsing |
| PDF417 | pdf417() | Text/Byte/Numeric, 9 EC levels, ISO-8859-15 |
| MicroPDF417 | micropdf417() | Compact PDF417 for small items |
| Aztec | aztec() | Compact + full-range, no quiet zone |
| MaxiCode | `maxicode()$ | 33 \times 30 \text{hexagonal}, \text{UPS} \text{shipping} \text{labels} |
| \text{rMQR} | $rmqr()` | Rectangular Micro QR (R7x43 to R17x139) |
| Codablock F | codablockf() | Stacked Code 128 |
| Code 16K | code16k() | Stacked barcode, 2-16 rows |
| DotCode | dotcode() | Checkerboard dots, high-speed printing |
| Han Xin | hanxin() | Chinese market, 84 versions, 4 finders |
| JAB Code | jabcode() | Polychrome (4/8 color), ISO/IEC 23634 |
Each also has a raw encoder (encodeMicroQR, encodeMaxiCode, …) and, except
for JAB Code, a *PNG() variant.
Postal Barcodes
Postal symbologies are height-modulated — the data lives in each bar's vertical extent, not its width — so they have their own encoder and renderer.
| Format | type | Description |
|---|---|---|
| POSTNET | postnet | USPS ZIP (5, 9 or 11 digits) |
| PLANET | planet | USPS PLANET (11 or 13 digits) |
| RM4SCC | rm4scc | Royal Mail (UK) |
| KIX | kix | PostNL (Netherlands) |
| Australia Post | auspost | Australia Post |
| Japan Post | jppost | Japan Post (Kasutama) |
| USPS IMb | imb | Intelligent Mail (US) |
import { postal, encodePostal, postalPNG } from "etiket"
postal("12345-6789", { type: "postnet" }) // SVG
postal("SN34RD1A", { type: "rm4scc" })
postal("12345678", { type: "auspost", fcc: "59" })
postal("01234567094987654321", { type: "imb", routingCode: "01234567891" })
postalPNG("12345", { type: "postnet" }) // Uint8Array
// Raw bar states: 'T' | 'A' | 'D' | 'F' (4-state), or 1 / 0 (POSTNET, PLANET)
const bars = encodePostal("SN34RD1A", { type: "rm4scc" })
barcode() accepts postnet and planet and routes them to the postal
renderer automatically. The per-format raw encoders (encodeRM4SCC,
encodeKIX, encodeAustraliaPost, encodeJapanPost, encodeIMb,
encodePOSTNET, encodePLANET) remain available.
Usage
Barcodes
import { barcode } from "etiket"
barcode("Hello World") // Code 128 (default)
barcode("4006381333931", { type: "ean13", showText: true })
barcode("00012345678905", { type: "itf14", bearerBars: true })
barcode("(01)12345678901234(17)260101", { type: "gs1-128" })
barcode("HELLO", { type: "code39", code39CheckDigit: true })
| Option | Type | Default | Description |
|---|---|---|---|
type | BarcodeType | 'code128' | Barcode format |
height | number | 80 | Bar height in pixels |
barWidth | number | 2 | Width multiplier per module |
color | string | '#000' | Bar color |
background | string | '#fff' | Background color |
showText | boolean | false | Show human-readable text |
textPosition | 'bottom' | 'top' | 'bottom' | Text position |
fontSize | number | 14 | Text font size |
fontFamily | string | 'monospace' | Text font family |
margin | number | 10 | Margin around barcode |
marginTop | number | margin | Top margin |
marginBottom | number | margin | Bottom margin |
marginLeft | number | margin | Left margin |
marginRight | number | margin | Right margin |
textAlign | 'center' | 'left' | 'right' | 'center' | Text alignment |
rotation | 0 | 90 | 180 | 270 | 0 | Barcode rotation |
bearerBars | boolean | false | Bearer bars (ITF-14) |
barGap | number | 0 | Extra spacing between bars |
unit | 'px' | 'mm' | 'in' | 'cm' | 'px' | Measurement unit |
ariaLabel | string | — | SVG aria-label attribute |
title | string | — | SVG <title> element |
desc | string | — | SVG <desc> element |
QR Codes
import { qrcode } from "etiket"
qrcode("https://example.com")
qrcode("Hello", { size: 300, ecLevel: "H", dotType: "rounded" })
// With gradient
qrcode("Test", {
color: {
type: "linear",
rotation: 45,
stops: [
{ offset: 0, color: "#ff0000" },
{ offset: 1, color: "#0000ff" },
],
},
})
// With corner styling
qrcode("Test", {
dotType: "dots",
corners: {
topLeft: { outerShape: "rounded", innerShape: "dots", outerColor: "#ff0000" },
topRight: { outerShape: "extra-rounded" },
bottomLeft: { outerShape: "dots" },
},
})
| Option | Type | Default | Description |
|---|---|---|---|
size | number | 200 | SVG size in pixels |
ecLevel | 'L' | 'M' | 'Q' | 'H' | 'M' | Error correction level |
version | number | auto | QR version (1-40) |
mode | 'numeric' | 'alphanumeric' | 'byte' | 'auto' | 'auto' | Encoding mode |
mask | 0-7 | auto | Mask pattern |
color | string | GradientOptions | '#000' | Module color |
background | string | GradientOptions | '#fff' | Background color |
margin | number | 4 | Quiet zone in modules |
dotType | DotType | 'square' | Module shape |
dotSize | number | 1 | Module size (0.1-1) |
shape | 'square' | 'circle' | 'square' | Overall QR shape |
corners | object | — | Finder pattern styling |
logo | LogoOptions | — | Center logo embedding |
xmlDeclaration | boolean | false | Add XML declaration |
unit | 'px' | 'mm' | 'in' | 'cm' | 'px' | Measurement unit |
ariaLabel | string | — | SVG aria-label |
title | string | — | SVG <title> element |
desc | string | — | SVG <desc> element |
Dot types: square, rounded, dots, diamond, classy, classy-rounded, extra-rounded, vertical-line, horizontal-line, small-square, tiny-square
2D Codes
Every 2D, stacked and polychrome symbology has a high-level function returning SVG:
import {
datamatrix,
gs1datamatrix,
pdf417,
micropdf417,
aztec,
microqr,
rmqr,
maxicode,
dotcode,
hanxin,
codablockf,
code16k,
jabcode,
} from "etiket"
datamatrix("Hello World")
gs1datamatrix("(01)12345678901231")
pdf417("Hello World", { ecLevel: 4, columns: 5 })
micropdf417("Hello", { columns: 2 })
aztec("Hello World", { ecPercent: 33 })
microqr("12345", { version: 3 })
rmqr("Hello", { ecLevel: "H" })
maxicode("Hello", { mode: 2, postalCode: "123456789", countryCode: 840 })
dotcode("Hello")
hanxin("Hello", { ecLevel: 3 })
// Stacked linear symbologies (rows taller than modules are wide)
codablockf("Hello World", { columns: 8 })
code16k("Hello World")
// Polychrome
jabcode("Hello", { colors: 8 })
Output Formats
import {
barcode,
qrcode,
barcodeDataURI,
qrcodeDataURI,
barcodeBase64,
qrcodeBase64,
qrcodeTerminal,
barcodePNG,
qrcodePNG,
barcodePNGDataURI,
qrcodePNGDataURI,
} from "etiket"
// SVG
const svg = qrcode("Hello") // SVG string
const uri = qrcodeDataURI("Hello") // data:image/svg+xml,...
const b64 = qrcodeBase64("Hello") // data:image/svg+xml;base64,...
const term = qrcodeTerminal("Hello") // Terminal (UTF-8 blocks)
// PNG (zero-dependency raster output — no canvas, no native deps)
const png = qrcodePNG("Hello") // Uint8Array
const pngUri = qrcodePNGDataURI("Hello") // data:image/png;base64,...
const barPng = barcodePNG("12345", { type: "code128" }) // Uint8Array
PNG output is available for every format except JAB Code, each with a matching
*PNGDataURI variant:
barcodePNG, postalPNG, qrcodePNG, microqrPNG, rmqrPNG,
datamatrixPNG, gs1datamatrixPNG, pdf417PNG, micropdf417PNG, aztecPNG,
maxicodePNG, dotcodePNG, hanxinPNG, codablockfPNG, code16kPNG.
Raw Encoding
encode() returns the underlying data for any symbology without rendering:
import { encode } from "etiket"
const result = encode("Hello", { type: "qr", qr: { ecLevel: "H" } })
if (result.type === "1d")
result.bars // bar/space widths
else if (result.type === "2d")
result.matrix // boolean[][]
else result.bars // postal bar states
Convenience Helpers
import { wifi, email, sms, geo, url, phone, vcard, mecard, event } from "etiket"
wifi("MyNetwork", "password123") // WiFi QR
email("test@example.com") // mailto: QR
sms("+1234567890", "Hello!") // SMS QR
geo(37.7749, -122.4194) // Location QR
url("https://example.com") // URL QR
phone("+1234567890") // tel: QR
// vCard QR
vcard({
firstName: "John",
lastName: "Doe",
phone: "+1234567890",
email: "john@example.com",
org: "Acme Inc",
})
// MeCard QR (simpler, used by Android)
mecard({ name: "John Doe", phone: "+1234567890", email: "john@example.com" })
// Calendar event QR
event({
title: "Meeting",
start: "2026-04-01T10:00:00",
end: "2026-04-01T11:00:00",
location: "Office",
})
Batch Generation
import { barcodes, barcodeSheet, qrcodeSheet } from "etiket"
// Many symbols, one call, shared options
const labels = barcodes(["SKU-001", "SKU-002", "SKU-003"], {
type: "code128",
height: 50,
})
// Or a single SVG document holding a grid of them — a label sheet
const sheet = barcodeSheet(
orders.map((o) => o.tracking),
{
type: "code128",
columns: 3,
gap: 12,
labels: orders.map((o) => o.reference),
},
)
// Progress on a long batch
qrcodeSheet(tickets, { columns: 4, onProgress: (done, total) => bar.update(done / total) })
Validation
import { validateBarcode, isValidInput, validateQRInput } from "etiket"
validateBarcode("4006381333931", "ean13") // { valid: true }
validateBarcode("ABC", "ean13") // { valid: false, error: '...' }
isValidInput("HELLO", "code39") // true
Swiss QR Code
Generate QR-bill payment codes (mandatory in Switzerland since 2022):
import { swissQR } from "etiket"
swissQR({
iban: "CH4431999123000889012",
creditor: { name: "Max Muster", postalCode: "8000", city: "Zürich", country: "CH" },
amount: 1949.75,
currency: "CHF",
reference: "210000000003139471430009017",
referenceType: "QRR",
})
Raw Encoders
Access encoding functions directly for custom rendering:
import {
encodeCode128,
encodeEAN13,
encodeQR,
encodeDataMatrix,
encodePDF417,
encodeAztec,
renderBarcodeSVG,
renderQRCodeSVG,
renderMatrixSVG,
renderBarcodePNG,
renderMatrixPNG,
} from "etiket"
const bars = encodeCode128("data") // number[] (bar/space widths)
const matrix = encodeQR("data") // boolean[][] (QR matrix)
const dm = encodeDataMatrix("data") // boolean[][] (Data Matrix)
// SVG rendering
const svg = renderBarcodeSVG(bars, { height: 100 })
const qrSvg = renderQRCodeSVG(matrix, { size: 400, dotType: "dots" })
// PNG rendering
const png = renderBarcodePNG(bars, { height: 100, scale: 2 })
const qrPng = renderMatrixPNG(matrix, { moduleSize: 10, margin: 4 })
Industry Standards
import {
swissQR,
gs1datamatrix,
gs1DigitalLink,
encodeHIBCPrimary,
encodeHIBCSecondary,
} from "etiket"
// Swiss QR-bill (mandatory in Switzerland since 2022)
swissQR({
iban: "CH4431999123000889012",
creditor: { name: "Max Muster", postalCode: "8000", city: "Zürich", country: "CH" },
amount: 1949.75,
currency: "CHF",
})
// GS1 DataMatrix (healthcare, supply chain)
gs1datamatrix("(01)12345678901234(17)260101(10)BATCH01")
// GS1 Digital Link (2027 retail migration)
gs1DigitalLink({ gtin: "09520123456788", batch: "ABC123", serial: "12345" })
// HIBC (medical device labeling, FDA UDI)
const hibc = encodeHIBCPrimary("A123", "PROD456")
barcode(hibc, { type: "code128" }) // Encode in any symbology
// ISBT 128 (blood bank labeling, ISO 7064 Mod 37-2 check character)
const din = encodeISBT128DIN("US", "12345", "26", "000001")
barcode(din, { type: "code128" })
// MaxiCode (UPS shipping labels)
const mc = encodeMaxiCode("Test shipment", {
mode: 2,
postalCode: "12345",
countryCode: 840,
serviceClass: 1,
})
SVG Accessibility
All SVG renderers support accessibility attributes out of the box:
barcode("123456789", {
type: "ean13",
ariaLabel: "EAN-13 barcode for product 123456789",
title: "Product Barcode",
desc: "EAN-13 barcode encoding the GTIN 123456789",
})
qrcode("https://example.com", {
ariaLabel: "QR code linking to example.com",
title: "Website QR Code",
})
// CSS currentColor support for theme-aware barcodes
barcode("HELLO", { color: "currentColor", background: "transparent" })
Framework Integration
etiket generates plain SVG strings — no DOM required. Works with any framework:
React / Next.js
import { qrcode } from "etiket"
function QRCode({ url }: { url: string }) {
const svg = qrcode(url, {
size: 200,
dotType: "dots",
ecLevel: "H",
corners: {
topLeft: { outerShape: "dots", innerShape: "dots", outerColor: "#ff0000" },
topRight: { outerShape: "dots", innerShape: "dots" },
bottomLeft: { outerShape: "dots", innerShape: "dots" },
},
})
return <div dangerouslySetInnerHTML={{ __html: svg }} />
}
Or use a data URI for <img>:
import { qrcodeDataURI } from "etiket"
function QRImage({ url }: { url: string }) {
return <img src={qrcodeDataURI(url)} alt="QR Code" width={200} height={200} />
}
PNG output (useful for downloads or <canvas>):
import { qrcodePNGDataURI } from "etiket/png"
function QRCodePNG({ url }: { url: string }) {
return <img src={qrcodePNGDataURI(url, { size: 200 })} alt="QR Code" width={200} height={200} />
}
Note:
qrcode()is a pure function with zero DOM dependencies — it works in both Server Components (RSC) and Client Components.
Vue
<script setup lang="ts">
import { qrcode } from "etiket"
const props = defineProps<{ url: string }>()
const svg = computed(() => qrcode(props.url, { dotType: "dots", ecLevel: "H" }))
</script>
<template>
<div v-html="svg" />
</template>
Svelte
<script lang="ts">
import { qrcode } from "etiket";
let { url }: { url: string } = $props();
let svg = $derived(qrcode(url, { dotType: "dots", ecLevel: "H" }));
</script>
{@html svg}
Angular
import { Component, Input } from "@angular/core"
import { DomSanitizer, SafeHtml } from "@angular/platform-browser"
import { qrcode } from "etiket"
@Component({
selector: "app-qrcode",
template: `<div [innerHTML]="svg"></div>`,
})
export class QRCodeComponent {
svg: SafeHtml = ""
@Input() set url(value: string) {
this.svg = this.sanitizer.bypassSecurityTrustHtml(
qrcode(value, { dotType: "dots", ecLevel: "H" }),
)
}
constructor(private sanitizer: DomSanitizer) {}
}
Astro
---
import { qrcode } from "etiket";
const svg = qrcode("https://example.com", { dotType: "dots", ecLevel: "H" });
---
<Fragment set:html={svg} />
Features
- Zero dependencies
- Pure ESM, edge-runtime compatible (Cloudflare Workers, Deno, Bun)
- TypeScript-first with strict types (TypeScript 7)
- Tree-shakeable sub-path exports
- CLI tool (
npx etiket) - SVG string output (no DOM required) +
optimizeSVG()for compact inline - PNG raster output (pure JS, zero dependencies) via
etiket/png - SVG accessibility (
ariaLabel,role,title,desc) - Measurement units (
px,mm,in,cm,pt) for print use cases - CSS
currentColorsupport for theme-aware barcodes - Auto EC upgrade to H when QR logo is present (supports PNG, JPEG, SVG, ICO)
- GS1 support (100+ AIs, Digital Link, GS1 QR, GS1 DataMatrix, the full GS1 DataBar family including the stacked variants)
- ECI on QR, Data Matrix, PDF417 and Aztec — non-Latin-1 data is declared, not truncated
- QR kanji mode with the real Shift-JIS table, Structured Append across up to 16 symbols, and optimal multi-segment encoding
- Batch generation and label sheets (
barcodes(),qrcodeSheet()) - HIBC medical device encoding + ISBT 128 blood bank labeling
- Swiss QR-bill payments
- 4-state postal barcodes (RM4SCC, KIX, Australia Post, Japan Post, USPS IMb)
- Works in browser, Node.js, Deno, Bun, Cloudflare Workers
Verification
Producing a symbol is easy; producing one a scanner accepts is not. Every symbology here is checked against something that is not this library:
- Decoded back with zxing-wasm or jsQR — QR, Micro QR, rMQR, Data Matrix, PDF417, MicroPDF417, Aztec, MaxiCode, Code 128, EAN, UPC, Code 39, Code 93, ITF, Codabar, GS1-128 and every GS1 DataBar variant.
- Compared module for module with bwip-js (BWIPP) for the formats no JavaScript decoder implements — Code 16K, Codablock F, DotCode, Han Xin, MSI, Plessey, Code 11, Pharmacode, Identcode, Leitcode, HIBC, POSTNET, PLANET, RM4SCC, KIX, Australia Post, Japan Post, USPS IMb, and every GS1 Composite symbol from its linkage flag to its separator pattern.
- JAB Code is the exception, and says so in its own API docs: no JavaScript or WebAssembly decoder exists and neither zxing nor BWIPP implements it, so its output cannot be verified and is marked experimental.
test/bwip-compare.test.ts keeps the differences visible rather than asserting
them away: a format that diverges is listed with the issue tracking it, and the
test turns red the moment it starts matching.
Comparison
| Feature | etiket | uqr | bwip-js | JsBarcode | qr-code-styling |
|---|---|---|---|---|---|
| Zero dependencies | :white_check_mark: | :white_check_mark: | :x: (1.5MB+) | :x: (xmldom) | :x: (qrcode) |
| TypeScript-first | :white_check_mark: | :white_check_mark: | :x: | :x: | Partial |
| Tree-shakeable | :white_check_mark: | :x: | :x: | :x: | :x: |
| 1D barcodes (40 types) | :white_check_mark: | :x: | :white_check_mark: (100+) | :white_check_mark: (13) | :x: |
| QR Code (v1-40, all EC) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: |
| Data Matrix | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: |
| PDF417 | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: |
| Aztec Code | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: |
| QR dot styling (12 types) | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: |
| QR gradients | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: |
| QR corner styling | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: |
| QR logo embedding | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: |
| CLI tool | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: |
| Terminal output | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: |
| Convenience helpers (WiFi, vCard...) | :white_check_mark: | :x: | :x: | :x: | :x: |
| Input validation | :white_check_mark: | :x: | :x: | :x: | :x: |
| SVG output | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PNG output | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Pure ESM | :white_check_mark: | :white_check_mark: | :x: (CJS) | :x: (CJS) | :x: (CJS) |
| Bundle size (gzip) | ~24KB | ~12KB | ~160KB | ~15KB | ~30KB+deps |
etiket is the only library that combines 1D barcodes + 2D codes + styled QR codes + SVG & PNG output + zero dependencies + tree-shaking in a single package.
Inspiration & Credits
Built from scratch, inspired by these excellent libraries:
- uqr — Pure SVG QR approach, terminal rendering
- bwip-js — Comprehensive barcode format reference (100+ types)
- JsBarcode — Encoding table validation, barcode rendering patterns
- qr-code-styling — QR styling concepts (dot types, gradients, corners, logos)
Standards: ISO/IEC 15417 (Code 128), ISO/IEC 15420 (EAN/UPC), ISO/IEC 18004 (QR), ISO/IEC 16022 (Data Matrix), ISO/IEC 15438 (PDF417), ISO/IEC 24778 (Aztec), ISO/IEC 24724 (GS1 DataBar), ISO/IEC 16023 (MaxiCode), ISO/IEC 23941 (rMQR), ISO/IEC 20830 (Han Xin), ISO/IEC 23634 (JAB Code), AIM ISS DotCode 4.0 (DotCode), USPS-B-3200 (IMb).
Contributing
Contributions are welcome! Here are some areas where help is especially appreciated:
Where help would go furthest:
- JAB Code — full LDPC error correction per ISO/IEC 23634. The one symbology here that cannot be verified against anything, because no decoder and no reference encoder implements it.
- Han Xin GB 18030 Chinese modes — byte mode carries Chinese text today. BWIPP's own Han Xin encoder implements Numeric and Byte and no more, so adding the Chinese modes means finding something that can check them first.
- Telepen and Channel Code — neither has a decoder, and neither yields to a rule: Telepen's 128 character table is reproduced by no deterministic tokenizer or small state machine we could find, and Channel Code's construction is an enumeration. Copying the reference's tables and then comparing against the reference is not verification.
- Code 49, Code One, Ultracode — implementable and comparable against BWIPP, just not written yet.
Also welcome:
- More payloads for
test/encoders-random-differential.test.ts, which is where a mis-taken branch shows up - Documentation improvements
pnpm install # Install dependencies
pnpm dev # Run tests in watch mode
pnpm test # Lint + typecheck + test
pnpm build # Build for production
License
Published under the MIT license.