Skip to content

Core library: @qrforge/core ​

@qrforge/core is a deterministic QR code generator for browsers and Node.js with no runtime dependencies. The Studio, the React components and the CLI are built on it.

  • QR encoder (ISO/IEC 18004, versions 1 to 40, all four error correction levels).
  • SVG, PNG and vector PDF output, with styling, logos, frames and labels.
  • Payload builders for URL, text, Wi-Fi, vCard, email, SMS, phone, location and calendar events.
  • Scan reliability analysis.

QR content is never sent over the network. A logo URL you provide is loaded by the browser for PNG and PDF export; in Node.js, URLs are never fetched.

bash
npm install @qrforge/core

Requires Node.js 18+ (PNG and PDF output use CompressionStream) or a modern browser.

Usage ​

js
import { generateQR } from "@qrforge/core";

// SVG string (default format)
const svg = await generateQR({ data: "https://example.com" });

// PNG bytes (Uint8Array)
const png = await generateQR({ data: "https://example.com", format: "png", size: 1024 });

// Data URL, for example for an <img>
const url = await generateQR({ data: "https://example.com", format: "png", output: "dataURL" });

// Vector PDF on an A4 page
const pdf = await generateQR({ data: "https://example.com", format: "pdf", pageSize: "A4" });

Writing a file in Node.js:

js
import { writeFile } from "node:fs/promises";
import { toPNG } from "@qrforge/core";

await writeFile("qr.png", await toPNG({ data: "https://example.com", size: 800 }));

Structured content ​

Set type and pass the fields in data. The matching payload builder validates the input and produces the payload.

js
await generateQR({
  type: "wifi",
  data: { ssid: "Cafe", password: "espresso", encryption: "WPA" },
  format: "svg",
});

Functions ​

FunctionReturnsDescription
generateQR(options)Promise<string | Uint8Array>Render in any format.
toSVG(options)stringSynchronous SVG rendering.
toPNG(options)Promise<Uint8Array>PNG bytes.
toPDF(options)Promise<Uint8Array>Single-page vector PDF.
analyzeReliability(options){ level, warnings, version, modules }Estimates how reliably the styled code will scan.
encode(data, { ecc, minVersion, maxVersion, mask }){ version, size, ecc, mask, modules }Raw module matrix; modules[y][x] is true for dark modules.
layoutQR(options){ content, matrix, options, layout }Encode and lay out without rendering, for custom renderers.
byteCapacity(version, ecc)numberMaximum payload size in bytes for a version and ECC level.
buildPayload(type, input)stringBuild the payload for a structured type.
decodePNG(bytes), encodePNG(image)Minimal PNG codec used for logos.
contrastRatio(a, b)numberWCAG contrast ratio of two hex colors.
presets, getPreset(id)Built-in design presets.

Errors are QRForgeError instances with a stable code (EMPTY_DATA, DATA_TOO_LONG, INVALID_OPTION, INVALID_PAYLOAD, INVALID_IMAGE, UNSUPPORTED_FEATURE or UNSUPPORTED_ENVIRONMENT). Errors caused by input also set details.field.

js
import { generateQR, QRForgeError } from "@qrforge/core";

try {
  await generateQR({ data: "x".repeat(5000) });
} catch (e) {
  if (e instanceof QRForgeError && e.code === "DATA_TOO_LONG") {
    // Shorten the content or lower the error correction level.
  }
}

Options ​

Content and output ​

OptionTypeDefaultDescription
datastring | Uint8Array | objectContent to encode, or structured fields when type is set. Required.
type"url" | "text" | "wifi" | "vcard" | "email" | "sms" | "phone" | "location" | "calendar"Builds the payload from the fields in data.
format"svg" | "png" | "pdf""svg"Output format (generateQR only).
output"string" | "buffer" | "dataURL""string" for SVG, otherwise "buffer""string" is available for SVG only.
titlestring"QR code"SVG aria-label and PDF title.
renderer"auto" | "canvas" | "raster""auto"PNG renderer. auto uses a canvas in browsers and the JavaScript rasterizer elsewhere.
dpinumberPNG pixel density metadata (JavaScript rasterizer only).
pageSize"fit" | "A4" | "Letter" | "A5""fit"PDF page size. With fit, the page matches the code (pixels × 0.75 pt); other sizes center the code at 60% of the page width.
minVersion, maxVersion1–401, 40Constrain the QR version.
mask0–7automaticForce a mask pattern.

Text is encoded in numeric, alphanumeric or byte (UTF-8) mode, using the most compact single mode that fits the whole string.

Style ​

OptionTypeDefaultDescription
sizenumber (32–8192)512Output width in pixels. Labels and frames add height.
margininteger (0–32)4Quiet zone in modules.
ecc"L" | "M" | "Q" | "H""M"Error correction level; recovers about 7%, 15%, 25% or 30% of the code.
foregroundhex color"#000000"Module color: #rgb, #rgba, #rrggbb or #rrggbbaa.
backgroundhex color or "transparent""#ffffff"Background color.
moduleStyle"square" | "rounded" | "soft" | "dots""square"Shape of data modules. rounded and soft round only exposed corners, so adjacent modules join.
cornerStyle"square" | "rounded" | "circle""square"Outer ring of the three finder patterns.
cornerDotStyle"square" | "rounded" | "circle""square"Center of the finder patterns.
cornerColorhex color or nullforegroundFinder pattern color.
logoobject | stringnullSee below. A string is shorthand for { src }.
labelobject | stringnullText below the code. A string is shorthand for { text }.
frameobject | stringnull"box" or "banner" frame.

With non-square module styles, alignment patterns are drawn as solid shapes so decoders can locate them.

FieldDefaultDescription
srcData URL, URL or image bytes (Uint8Array). In Node.js, URLs work for SVG output only.
pixelsPre-decoded RGBA image { width, height, data }, for PNG and PDF.
size0.22Logo width as a fraction of the code (0.05–0.35).
padding1Clear space around the logo in modules (0–4).
background"#ffffff"Color of the plate behind the logo, or null or false for none.
radius0.2Plate corner radius as a fraction of its size (0–0.5).
excavatetrueRemove the modules behind the logo.

Use ecc: "H", or at least "Q", with logos. Modules hidden by the logo consume error correction capacity.

label and frame ​

FieldDefaultDescription
label.textUp to 64 characters.
label.colorautomaticA color that is readable on the frame, or the foreground color.
label.size1Font size multiplier (0.5–2).
frame.style"none", "box" (outline) or "banner" (solid frame with the label in a bottom bar).
frame.colorforegroundFrame color.
js
await generateQR({
  data: "https://example.com/menu",
  format: "svg",
  frame: { style: "banner", color: "#0a0a0a" },
  label: "SCAN FOR THE MENU",
});

Presets ​

presets contains six designs, all with strong contrast and a full quiet zone: minimal, business, modern, rounded, dark (a dark banner frame around a standard dark-on-light code) and elegant.

js
import { generateQR, getPreset } from "@qrforge/core";

await generateQR({ data: "https://example.com", ...getPreset("modern").options });

Payload builders ​

Each builder validates its input and throws QRForgeError with code INVALID_PAYLOAD and details.field when a value is invalid.

BuilderInputOutput
url(url)string or { url }Adds https:// when no scheme is given; only http and https are accepted.
text(text)string or { text }Unchanged text.
wifi({ ssid, password, encryption, hidden })encryption: WPA (default), WEP or nopassWIFI:T:WPA;S:…;P:…;; with special characters escaped.
vcard({ firstName, lastName, organization, title, phone, mobile, email, website, street, city, region, postalCode, country, note })A name or organization is requiredvCard 3.0.
email({ to, subject, body, cc, bcc })mailto: URI.
sms({ phone, message })SMSTO:number:message.
phone(number)string or { phone }tel: URI with separators removed.
location({ latitude, longitude } | { address }, format)format: geo (default), osm, google or applegeo: URI or map link.
calendar({ title, start, end, allDay, location, description })Dates as Date objects or ISO strings; all-day events accept YYYY-MM-DDiCalendar VEVENT in UTC.
js
import { vcard, generateQR } from "@qrforge/core";

const payload = vcard({ firstName: "Ada", lastName: "Lovelace", email: "ada@example.com" });
const svg = await generateQR({ data: payload });

Scan reliability ​

js
import { analyzeReliability } from "@qrforge/core";

const r = analyzeReliability({ data: "https://example.com", foreground: "#bbbbbb" });
// { level: "poor", version: 2, modules: 25,
//   warnings: [{ code: "LOW_CONTRAST", severity: "danger", message: "…" }] }

level is "good", "fair" (warnings present) or "poor" (at least one danger). The checks cover contrast (LOW_CONTRAST), inverted colors (INVERTED), the quiet zone (NO_QUIET_ZONE, SMALL_QUIET_ZONE), pixels per module (TOO_SMALL, SMALL), mixed finder shapes (CORNER_MISMATCH), logo coverage relative to ECC (LOGO_TOO_LARGE, LOGO_LARGE, LOGO_ECC, LOGO_OVERLAP) and transparency (TRANSPARENT_BACKGROUND, TRANSLUCENT_BACKGROUND).

Browser and Node.js differences ​

FeatureBrowserNode.js
SVGYesYes
PNGCanvas renderer by defaultJavaScript rasterizer (deterministic)
PNG with labelYesNo; throws UNSUPPORTED_FEATURE. Use SVG or PDF.
Logo formats for PNG and PDFAny format the browser decodes (PNG, JPEG, WebP, GIF, SVG)PNG only, or pre-decoded pixels
Logo URLs such as /logo.svgSVG, PNG and PDF (loaded by the browser; cross-origin images need CORS headers)SVG output only (referenced, never fetched)
PDFYesYes

PDF labels use the built-in Helvetica font; characters outside Latin-1 are replaced with ?. SVG labels use the system sans-serif font.

Determinism ​

The same input always produces the same module matrix and the same SVG string. With renderer: "raster", the default in Node.js, PNG pixels are identical across runs.

Custom renderers ​

layoutQR() returns a format-independent list of rectangles (with per-corner radii) and circles, optionally with holes, in module units. shapePath(shape) converts a shape to an SVG path string. The React package renders directly from this layout.

Released under the MIT License.