fastqrjs

Documentation

Rendered from the repository README - same source of truth as the package docs.

fastqrjs

Fast QR code generation in JavaScript with native Node.js bindings, WebAssembly support, and advanced styling options.

Features

  • Dual runtime support: Native bindings for Node.js, WASM for browsers and edge runtimes
  • Unified API: Single generateSvg() function handles both simple and styled QR codes
  • Advanced styling: Custom dot shapes, gradients, corner patterns, and image overlays
  • Multiple output formats: SVG, PNG, matrix data, Unicode, base64 data URI
  • Two browser payloads: A compact PNG/data build and a complete styled build
  • High performance: Up to 50× faster than pure JavaScript alternatives for PNG generation
  • Zero dependencies: Self-contained with optimized PNG encoder

[!WARNING] Experimental — This library is under active development. APIs may change between versions, and edge cases may not be handled correctly. Not recommended for production use yet.

Installation

npm install fastqrjs
# or
yarn add fastqrjs
# or
bun add fastqrjs

Quick Start

Unified SVG API (Recommended)

The generateSvg() function handles everything from simple black-and-white QR codes to fully styled ones with gradients and custom shapes:

import { generateSvg, generateDataUri, generateQRMatrix } from "fastqrjs";

// Simple SVG (default square modules, black/white)
const svg = await generateSvg("https://fastqrjs.com");

// With basic styling (colors and shapes)
const styledSvg = await generateSvg("https://fastqrjs.com", {
  shape: "rounded", // 'square' | 'circle' | 'rounded'
  color: { dark: "#1a73e8", light: "#f8f9fa" },
  margin: 4,
});

// With advanced styling (gradients, custom corners, images)
const fancySvg = await generateSvg("https://fastqrjs.com", {
  width: 400,
  dots: {
    type: "rounded",
    color: {
      type: "linear",
      rotation: 45,
      colorStops: [
        { offset: 0, color: "#FF6B6B" },
        { offset: 1, color: "#4ECDC4" },
      ],
    },
  },
  cornersSquare: {
    type: "dot",
    color: "#e74c3c",
  },
  background: {
    color: "#ffffff",
    round: 0.1,
  },
});

// PNG as base64 data URI for <img> tags
const dataUri = await generateDataUri("https://fastqrjs.com", {
  scale: 10,
  color: { dark: "#000000", light: "#ffffff" },
});

// Raw matrix data for custom rendering
const matrix = await generateQRMatrix("Hello World");

Compact PNG and Worker API

Use fastqrjs/smol when a browser or worker only needs PNG, matrix data, or Unicode output. This entry never pulls the SVG or styled renderer into its module graph.

import {
  generatePng,
  generateDataUri,
  generateQRMatrix,
  generateQRData,
  generateQRUnicode,
} from "fastqrjs/smol";

const png = await generatePng("https://fastqrjs.com", { scale: 4 });
const matrix = await generateQRMatrix("https://fastqrjs.com");

Use fastqrjs/styled when a browser only needs simple or styled SVG output:

import { generateSvg } from "fastqrjs/styled";

const svg = await generateSvg("https://fastqrjs.com", {
  dots: { type: "rounded" },
});

The default fastqrjs export and the explicit fastqrjs/full export provide the complete API by composing both builds. Node.js and Bun use native bindings when available and compose the two filesystem-based WASM fallbacks otherwise. In browsers, import fastqrjs/smol or fastqrjs/styled directly when payload size matters. A root import can cause Vite to emit both WASM assets even when JavaScript tree-shaking removes the unused implementation, because Vite discovers new URL(..., import.meta.url) assets before its final tree-shaking pass.

Output Examples:

Simple Styled Fancy (Gradient)
Simple QR Styled QR Fancy QR

Direct Native Bindings (Node.js only)

For maximum performance with synchronous APIs:

The install automatically selects an optional native package on Linux glibc (x64 or ARM64) and macOS Apple Silicon. Other targets do not fail installation: npm skips the incompatible optional packages and the unified API falls back to WASM.

import {
  generateQrSvg,
  generateQrStyledSvg,
  generateQrPng,
  generateQrPngDataUri,
  generateQrMatrix,
} from "fastqrjs/napi";

// All functions are synchronous
const svg = generateQrSvg("https://fastqrjs.com", { shape: "square" });
const styledSvg = generateQrStyledSvg("https://fastqrjs.com", {
  dotsType: "rounded",
});
const pngBytes = generateQrPng("Hello World", { scale: 10 });
const dataUri = generateQrPngDataUri("Hello World", { scale: 10 });
const matrix = generateQrMatrix("Hello World");

Direct WASM

The raw bindings are available for advanced integrations. fastqrjs/wasm remains an alias for the complete build.

import init, {
  generate_qr_svg,
  generate_qr_styled_svg,
  generate_qr_matrix,
} from "fastqrjs/wasm/full";

await init();

const svg = generate_qr_svg("https://fastqrjs.com", wasmOptions);
const styledSvg = generate_qr_styled_svg("https://fastqrjs.com", styledOptions);
const matrix = generate_qr_matrix("Hello World");

For the compact raw build:

import init, {
  PngOptions,
  generate_qr_png,
  generate_qr_matrix,
  generate_qr_unicode,
} from "fastqrjs/wasm/smol";

await init();
const png = generate_qr_png("Hello World", new PngOptions());

The raw entries use the standard wasm-bindgen browser initializer. In Node.js, prefer the high-level fastqrjs or fastqrjs/smol entry, which selects native code and provides a filesystem-based WASM fallback automatically.

API Reference

SVG Generation (generateSvg)

The unified SVG API automatically uses the appropriate backend based on your options.

Simple Mode

For basic QR codes with colors and simple shapes:

const svg = await generateSvg("https://fastqrjs.com", {
  width: 200, // Output width in pixels
  height: 200, // Output height in pixels
  margin: 4, // Quiet zone margin in modules (default: 4)
  shape: "rounded", // 'square' | 'circle' | 'rounded'
  ecl: "M", // Error correction: 'L' | 'M' | 'Q' | 'H'
  version: 5, // QR version 1-40 (auto-selected if not specified)
  mask: 0, // Mask pattern 0-7 (auto-selected if not specified)
  color: {
    dark: "#000000", // Hex color for dark modules
    light: "#ffffff", // Hex color for light modules
  },
});

Advanced Mode

When you provide dots, cornersSquare, cornersDot, background.round, or image options, the API automatically switches to styled mode:

const svg = await generateSvg("https://fastqrjs.com", {
  width: 400,
  height: 400,
  margin: 20,

  // Dot styling (enables styled mode)
  dots: {
    type: "square" | "square-rounded" | "rounded" | "dots",
    color:
      "#000000" |
      {
        type: "linear" | "radial",
        rotation: 45, // For linear gradients (degrees)
        colorStops: [
          { offset: 0, color: "#FF6B6B" },
          { offset: 1, color: "#4ECDC4" },
        ],
      },
  },

  // Corner square styling (finder patterns)
  cornersSquare: {
    type: "square" | "rounded" | "dot",
    color: "#000000",
  },

  // Corner dot styling (inner finder pattern)
  cornersDot: {
    type: "square" | "dot",
    color: "#000000",
  },

  // Background styling
  background: {
    color: "#ffffff",
    round: 0.1, // Border radius (0.0 to 1.0)
  },

  // Image overlay
  image: {
    source: "https://fastqrjs.com/logo.png", // URL or base64
    size: 0.4, // Size as fraction of QR code (0.0 to 1.0)
    margin: 0, // Margin around image
  },
});

How it works:

  • If you only use shape and/or color, you get the fast simple SVG mode
  • If you use dots, cornersSquare, cornersDot, background.round, or image, you get the full styled SVG mode

PNG Generation (generatePng)

const pngBytes = await generatePng("https://fastqrjs.com", {
  scale: 10, // Pixel scale per module (default: 10)
  width: 300, // Target width in pixels (calculates scale automatically)
  quietZone: 4, // Quiet zone margin in modules (default: 4)
  ecl: "M", // Error correction level
  version: 5, // QR version 1-40
  mask: 0, // Mask pattern 0-7
  color: {
    dark: "#000000", // Hex color (uses RGB mode when specified)
    light: "#ffffff", // Hex color (uses RGB mode when specified)
  },
});

// Save to file (Node.js)
import { writeFileSync } from "fs";
writeFileSync("qr.png", Buffer.from(pngBytes));

PNG Examples (Different Scales):

Scale 1 (29×29px) Scale 5 (145×145px)
Scale 1 Scale 5

Optimization: Automatically uses fast 1-bit grayscale for black & white at scale=1, switches to RGB when colors are specified or scale > 1.

Base64 Data URI (generateDataUri)

Generates PNG as base64 data URI, ready for <img> tags.

const dataUri = await generateDataUri('https://fastqrjs.com', {
  scale: 10,
  color: { dark: '#000000', light: '#ffffff' }
});

// Use directly in HTML
<img src={dataUri} alt="QR Code" />

Matrix Data (generateQRMatrix)

Returns 2D boolean array for custom rendering.

const matrix = await generateQRMatrix("Hello World");
// matrix[y][x] = true/false

// Example: Render to canvas
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const moduleSize = canvas.width / matrix.length;

for (let y = 0; y < matrix.length; y++) {
  for (let x = 0; x < matrix.length; x++) {
    ctx.fillStyle = matrix[y][x] ? "#000" : "#fff";
    ctx.fillRect(x * moduleSize, y * moduleSize, moduleSize, moduleSize);
  }
}

Flat Data Array (generateQRData)

Returns 1D boolean array (row-major order).

const data = await generateQRData("Hello World");
// data is boolean[] where index = y * size + x

Unicode/Terminal (generateQRUnicode)

Returns Unicode block characters for terminal display.

const unicode = await generateQRUnicode("Hello World");
console.log(unicode); // Prints QR code in terminal

Terminal Output Preview:

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█ ▄▄▄▄▄ █▄ ▄ ██ ▄▄▄▄▄ █
█ █   █ █ ▄▄█ █ █   █ █
█ █▄▄▄█ █▀▄█▄██ █▄▄▄█ █
█▄▄▄▄▄▄▄█ █▄▀▄█▄▄▄▄▄▄▄█
█▀ █ ▀ ▄▄▄▀  ▀▄  ▀▄▄▀ █
██▀█▄█▀▄█▀ ▀▄ █▀▄▀ ▄▀ █
█▄██▄█▄▄▄▀▀   ▄█▄ █▀███
█ ▄▄▄▄▄ █ ▀▄▄▀█▄ ▄▄▄▄▀█
█ █   █ █▀▄▀██▄█▀▀▀▀ ▀█
█ █▄▄▄█ █▀█ ▀▄▀▄█▄█▄█▄█
█▄▄▄▄▄▄▄███▄█▄█▄▄█▄█▄██

Options

Error Correction Levels

Level Recovery Description
L (Low) ~7% Minimal redundancy, smallest QR size
M (Medium) ~15% Default level, good balance
Q (Quartile) ~25% Higher redundancy
H (High) ~30% Maximum redundancy, largest QR size
const svg = await generateSvg("https://fastqrjs.com", {
  ecl: "H", // 30% error recovery
});

QR Code Versions

Versions range from 1 to 40, determining the QR code's size and data capacity:

  • Version 1: 21×21 modules
  • Version 40: 177×177 modules

By default, the minimum version needed for your data is automatically selected.

const svg = await generateSvg("Small text", {
  version: 5, // Force 37×37 modules
});

Simple Module Shapes

  • square (default): Traditional square modules
  • circle: Circular modules
  • rounded: Rounded square modules (SVG only)
const svg = await generateSvg("https://fastqrjs.com", {
  shape: "rounded", // or 'circle' or 'square'
});

Shape Examples:

Square Circle Rounded
Square Circle Rounded

Advanced Dot Types

Available when using styled mode (via dots option):

  • square: Traditional square dots
  • square-rounded: Rounded square corners
  • rounded: Fully rounded dots
  • dots: Circular dots
const svg = await generateSvg("https://fastqrjs.com", {
  dots: { type: "rounded" },
});

Dot Type Examples:

Square-Rounded Dots Radial Gradient
Square Rounded Dots Radial Gradient

Colors

Supports hex colors in #RGB, #RRGGBB, or #RRGGBBAA format:

const svg = await generateSvg("https://fastqrjs.com", {
  color: {
    dark: "#1a73e8", // Blue modules
    light: "#f8f9fa", // Light gray background
  },
});

When colors are specified for PNG output, the encoder automatically switches to RGB mode.

Mask Patterns

8 mask patterns (0-7) can be specified or auto-selected for optimal data distribution:

const svg = await generateSvg("https://fastqrjs.com", {
  mask: 3, // Force mask pattern 3
});

Gradients

Styled mode supports linear and radial gradients:

const svg = await generateSvg("https://fastqrjs.com", {
  dots: {
    color: {
      type: "linear", // or 'radial'
      rotation: 45, // Degrees for linear gradients
      colorStops: [
        { offset: 0, color: "#FF6B6B" },
        { offset: 0.5, color: "#FFE66D" },
        { offset: 1, color: "#4ECDC4" },
      ],
    },
  },
});

Encoding Modes

The underlying fast_qr library automatically detects and uses the most efficient encoding mode:

  • Numeric: Digits 0-9 only (most compact)
  • Alphanumeric: 0-9, A-Z, space, and special characters `$%*./:+-?.=``
  • Byte: Any data including UTF-8 and binary (most flexible)
// Auto-detects Numeric mode (compact)
await generateSvg("1234567890");

// Auto-detects Alphanumeric mode
await generateSvg("HELLO WORLD 123");

// Auto-detects Byte mode (handles UTF-8, emojis, etc.)
await generateSvg("Hello 🌍 世界");

JPEG/WebP Output

For JPEG or WebP formats, use the SVG output with a conversion library:

import { generateSvg } from "fastqrjs";
import { Resvg } from "@resvg/resvg-js"; // or similar

const svg = await generateSvg("https://fastqrjs.com");
const resvg = new Resvg(svg);
const pngData = resvg.render();
// Convert PNG to JPEG/WebP with sharp, canvas, etc.

Platform Support

Node.js

  • Native bindings: Best performance via NAPI-RS
  • WASM fallback: Available if native module fails to load
  • Requires: Node.js 14+

Browsers

  • WebAssembly: Choose the compact PNG/data build or complete styled build
  • No dependencies: Self-contained
  • Works in: All modern browsers (Chrome, Firefox, Safari, Edge)

Performance

All benchmarks run on Apple M1 Pro, Bun 1.3.10 runtime.

Native Bindings (NAPI) Benchmarks

Operation Average Time Notes
SVG Generation
Simple (basic shapes/colors) 45-93 µs Fast path for simple styling
Styled (advanced) 68-85 µs With gradients, corners, etc.
PNG Generation
Scale 1 (1-bit B/W) 38 µs Fastest - 1-bit grayscale
Scale 10 118 µs 290×290px typical output
Scale 20 342 µs 580×580px
Scale 50 1.89 ms 1450×1450px large output
Colored RGB scale 10 117 µs Custom dark/light colors
Colored RGB scale 20 342 µs Custom colors at scale 20
Data Operations
Matrix (2D array) 46 µs For custom rendering
Data (flat array) 45 µs Row-major boolean array
Unicode/Terminal 39 µs Block character output
Data URI
PNG to base64 120 µs Ready for <img> tags

vs qrcode npm package (Pure JavaScript)

Operation fastqrjs (native) qrcode (npm) Speedup
SVG 61 µs 86 µs 1.4× faster
PNG Buffer 352 µs 2.58 ms 7.3× faster
Data URI 380 µs 2.54 ms 6.7× faster

PNG Scale Comparison: fastqrjs vs qrcode

Scale fastqrjs (native) qrcode (npm) Speedup
1 (29×29px) 39 µs 265 µs 6.8× faster
4 (116×116px) 54 µs 973 µs 18× faster
10 (290×290px) 119 µs 4.82 ms 40× faster
20 (580×580px) 348 µs 17.82 ms 51× faster

The performance advantage increases dramatically with output size due to fastqrjs's optimized Rust-based PNG encoder.

Styled QR Code Comparison

Comparison with @loskir/styled-qr-code-node for styled SVG generation:

Style fastqrjs (NAPI) styled-qr-code-node Speedup
Square dots 68 µs 2.28 ms 33× faster
Rounded dots 83 µs 13.28 ms 160× faster
Circular dots 67 µs 42.21 ms 630× faster
With gradients 70-85 µs 12-14 ms 140-180× faster

Native vs WebAssembly

Operation NAPI (Native) WASM Overhead
SVG 59 µs 68 µs +15%
Styled SVG (square) 68 µs 87 µs +28%
Matrix 46 µs 44 µs Similar
Data 44 µs 44 µs Similar
Unicode 39 µs 41 µs +5%
PNG scale 10 117 µs 215 µs +84%
PNG scale 1 38 µs 44 µs +16%
Data URI 123 µs 157 µs +28%

Note: Native bindings provide the best performance, especially for PNG generation where encoding overhead is significant. WASM is still very fast and provides excellent browser compatibility.

Development

# Install dependencies
bun install

# Build everything
./build.sh

# Build individual components
bun run build:ts      # TypeScript
bun run build:native  # Native bindings
bun run build:wasm    # WASM bindings

# Run tests
bun test

# Run benchmarks
bun run bench
bun run bench:compare  # Compare with qrcode npm

# Clean build artifacts
bun run clean

The release WASM build is reproducible and pinned by wasm/rust-toolchain.toml. It rebuilds the standard library with immediate abort panics, uses size optimization and one codegen unit, then runs wasm-opt -Oz. Install Rust through rustup; the pinned nightly toolchain, rust-src, and WASM target are installed automatically on first use.

Architecture

fastqrjs/
├── src/                  # Conditional browser/Node entry points
├── napi/                 # NAPI-RS native Node.js bindings
├── wasm/full/            # Complete styled WebAssembly build
├── wasm/smol/            # Compact PNG/data WebAssembly build
└── fast-qr-core/         # Shared Rust core
    ├── PNG encoder
    ├── SVG generator
    └── Styled QR generator

Credits

This project uses the fast_qr Rust library by erwanvivien to generate the QR codes.

The advanced styling options are inspired by qr-code-styling library by kozakdenys.

License

MIT