Lean QR Library

Lean QR is a lightweight yet fully-featured library for generating QR Codes. Also available as an online tool.

Quickstart

Choose your platform:

NodeJS

Install the dependency:

npm install --save lean-qr

Display QR Code in terminal output:

import { generate } from 'lean-qr';

const code = generate('LEAN-QR LIBRARY');

process.stdout.write(code.toString({
  on: '\u001B[7m  \u001B[0m', // ANSI escape: inverted
}));

Generate SVG source for serving from a webserver:

import { generate } from 'lean-qr';
import { toSvgSource } from 'lean-qr/extras/svg';

const code = generate('LEAN-QR LIBRARY');

const svg = toSvgSource(code, {
  // set to false if you want to embed the QR Code inside a HTML document
  xmlDeclaration: true,
});

Generate PNG for serving from a webserver:

import { generate } from 'lean-qr';
import { toPngBuffer } from 'lean-qr/extras/node_export';

const code = generate('LEAN-QR LIBRARY');

const png = toPngBuffer(code, { scale: 8 });

Note: SVG is the recommended file export format as it will automatically scale without blurring, but you can also render PNGs using the CSS: image-rendering: pixelated.

Browser Native

Install the dependency:

npm install --save lean-qr

The recommended in-browser approach is to render directly to a canvas element, and size it using CSS:

<canvas id="my-canvas" />

<script type="module">
  import { generate } from 'lean-qr';

  const code = generate('LEAN-QR LIBRARY');
  code.toCanvas(document.getElementById('my-canvas'));
</script>

<style>
  #my-canvas {
    width: 100%;
    image-rendering: pixelated;
  }
</style>

Providing a download link

If you want to offer users a way to download the QR code as an image file, you can use a data URL:

<a href="#" download="qr.png" id="download-link">Download</a>

<script type="module">
  import { generate } from 'lean-qr';

  const code = generate('LEAN-QR LIBRARY');

  const dataUrl = code.toDataURL({ scale: 10 });
  document.getElementById('download-link')
    .setAttribute('href', dataUrl);
</script>

Note that providing an SVG download is preferable for letting users scale the image without it blurring, but this may not be supported by all image editors. The default format is PNG.

React

Install the dependency:

npm install --save lean-qr

A convenience wrapper component is provided for React:

import { generate } from 'lean-qr';
import { makeAsyncComponent } from 'lean-qr/extras/react';
import * as React from 'react';

// creates a react component (do this at the top-level)
const QR = makeAsyncComponent(React, generate);

// example usage:
const MyComponent = () => (
  <section>
    Scan this QR Code!
    <QR content="LEAN-QR LIBRARY" className="qr-code" />
  </section>
);

You should apply some styling to the QR Code to set its size:

.qr-code {
  width: 300px;
}

Note that this uses canvas rendering, which is optimal on the client-side, but will not work if you want to perform server-side rendering (the QR Code will not be visible until client-side hydration has completed). If you need server-side rendering, use makeSyncComponent instead:

import { generate } from 'lean-qr';
import { toSvgDataURL } from 'lean-qr/extras/svg';
import { makeSyncComponent } from 'lean-qr/extras/react';
import * as React from 'react';

const QR = makeSyncComponent(React, generate, toSvgDataURL);

const MyComponent = () => (
  <section>
    Scan this QR Code!
    <QR content="LEAN-QR LIBRARY" className="qr-code" />
  </section>
);

Preact

Install the dependency:

npm install --save lean-qr

A convenience wrapper component is provided for Preact:

import { generate } from 'lean-qr';
import { makeAsyncComponent } from 'lean-qr/extras/react';
import { createElement } from 'preact';
import * as hooks from 'preact/hooks';

// creates a preact component (do this at the top-level)
const QR = makeAsyncComponent({ createElement, ...hooks }, generate);

// example usage:
const MyComponent = () => (
  <section>
    Scan this QR Code!
    <QR content="LEAN-QR LIBRARY" class="qr-code" />
  </section>
);

You should apply some styling to the QR Code to set its size:

.qr-code {
  width: 300px;
}

If you want to reduce the build size further, you can provide just the useRef and useEffect hooks rather than all hooks to enable tree shaking optimisations during the build. Note that the set of required hooks may change in future versions.

Shell (CLI)

Though not recommended for production use, it is possible to use the built-in commandline interface to generate QR Codes from shell scripts:

npx lean-qr 'MY MESSAGE HERE'

By default this will print the QR Code to stdout using ANSI escape sequences, so it is recommended that you set a maximum version to ensure the QR Code does not exceed the width of the screen (the output width will be (17 + version * 4) * 2 characters):

npx lean-qr --max-version 5 'MY MESSAGE HERE'

You can also customise the output format (e.g. to generate a file):

npx lean-qr --format svg '漢字' > my-qr-code.svg

For full documentation, run:

npx lean-qr --help

API Reference

generate(content[, options])

import { generate, correction, mode } from 'lean-qr';

const code = generate('LEAN-QR LIBRARY', {
  minVersion: 1,
  maxVersion: 40,
  minCorrectionLevel: correction.L,
  maxCorrectionLevel: correction.H,
  mask: null,
  trailer: 0xEC11,
  modes: [
    mode.numeric,
    mode.alphaNumeric,
    mode.ascii,
    mode.iso8859_1,
    mode.shift_jis,
    mode.utf8,
  ],
});

The options shown are the defaults. See the following sections for details on their meaning.

Returns a Bitmap2D containing the generated QR Code, or throws an error if it is not possible to generate a QR Code with the requested content / options.

Versions (minVersion / maxVersion)

QR Code versions refer to the size of the output (the size will be 17 + version * 4, plus outer padding).

By default, all versions can be used. To restrict this, you can specify a minimum and/or maximum version:

const code = generate('LEAN-QR LIBRARY', {
  minVersion: 10,
  maxVersion: 20,
});

Versions must be integers in the range 1–40 (inclusive).

If there is too much data for the maxVersion size, an error will be thrown.

Correction Levels (minCorrectionLevel / maxCorrectionLevel)

QR Code correction levels control the amount of damage the QR Code can sustain before it becomes unreadable.

You can specify minimum and maximum correction levels:

const code = generate('LEAN-QR LIBRARY', {
  minCorrectionLevel: correction.M,
  maxCorrectionLevel: correction.Q,
});
correction level error tolerance data overhead
correction.L ~7.5% ~25%
correction.M ~15.0% ~60%
correction.Q ~22.5% ~120%
correction.H ~30.0% ~190%

generate will pick the smallest version (size) which supports the minCorrectionLevel, then within this version will use the highest possible correction level up to maxCorrectionLevel.

Generally it does not make sense to customise the maxCorrectionLevel unless you are targetting a reader which does not support a particular correction level, or you are forcing a particular level for stylistic purposes.

Masks (mask)

QR Code masks are patterns which are applied to the resulting QR Code in an effort to maximise readability for scanners (e.g. by avoiding data sections that look like a locator pattern, or large areas of the same colour). To achieve this, ISO 18004 requires that the mask is chosen according to a specific algorithm (and this is the default behaviour), but it is also possible to explicitly specify a non-optimal mask:

const code = generate('LEAN-QR LIBRARY', {
  mask: 5,
});

Valid masks are integers in the range 0–7 (inclusive).

Note that this may make your QR Code more difficult to scan, and should generally be left as the default (null).

checkerboard pattern
Mask 0
checkerboard pattern
Mask 1
checkerboard pattern
Mask 2
checkerboard pattern
Mask 3
checkerboard pattern
Mask 4
checkerboard pattern
Mask 5
checkerboard pattern
Mask 6
checkerboard pattern
Mask 7

Modes

The content you are encoding into a QR Code can be represented using a variety of "modes" (character encodings). These can be combined to achieve some amount of compression, allowing more content to fit in a smaller space.

By default, the optimal encoding mode is chosen to minimise the resulting QR Code size (this includes switching modes part way through a message if it reduces the size).

If you want to change the modes considered during this optimisation (perhaps if you are targeting a reader which does not support certain modes, or if you want to support a non-standard mode), you can specify a list of options:

const code = generate('LEAN-QR LIBRARY', {
  modes: [
    mode.numeric,
    mode.alphaNumeric,
    mode.ascii,
    mode.iso8859_1,
    // mode.shift_jis, - example: exclude Shift-JIS
    mode.utf8,
    myCustomMode, // see Custom Modes below
  ],
});

Or for full control, you can define the modes before passing the content in, avoiding the automatic optimisation entirely:

import { generate, mode } from 'lean-qr';

const code = generate(mode.alphaNumeric('LEAN-QR LIBRARY'));

Note that if you specify a mode explicitly, it is your responsibility to ensure the content you are encoding conforms to the accepted character set. If you provide mismatched content, the resulting QR Code will likely be malformed. The available modes and their supported character sets are:

mode bits / char charset
mode.numeric 10 / 3 0-9
mode.alphaNumeric 11 / 2 0-9A-Z $%*+-./:
mode.ascii 8 / 1 7-bit ASCII
mode.iso8859_1 8 / 1 ISO-8859-1
mode.shift_jis 13 / 1 Double-byte Shift-JIS characters
mode.utf8 varies Unicode

There are also some meta-modes:

multi(...modes)

mode.multi enables switching modes during a message:

const code = generate(mode.multi(
  mode.iso8859_1('https://example.com/'),
  mode.numeric('123456789012345678901234567890'),
  mode.alphaNumeric('/LOOKUP'),
));
eci(value) / bytes(data)

mode.eci lets you switch the Extended Channel Interpretation of the message (Wikipedia includes a list of recognised values). After setting this, subsequent mode.bytes will be interpreted in the specified character set.

const code = generate(mode.multi(
  mode.eci(24), // Arabic (Windows-1256)
  mode.bytes([0xD3]), // Shin character
));

mode.eci will avoid outputting additional switches if the ECI already matches the requested value.

Note that mode.iso8859_1 sets ECI 3 for its content, and mode.utf8 sets ECI 26. mode.ascii does not set an explicit ECI mode, as readers are supposed to default to ECI 3 (and even though some default to ECI 26 instead, these share the same codepoints for all ASCII values).

If you set an ECI which is not compatible with ASCII, do not follow it with a mode.ascii section (prefer mode.iso8859_1 or mode.utf8, as these will explicitly set the ECI for their content).

auto(text[, options])

mode.auto will pick the optimal combination of modes for the message. This is used by default if you provide a plain string to generate, but you can also use it explicitly (e.g. if you want to control part of a message but still have automatic compression for other parts):

const code = generate(mode.multi(
  mode.auto('FOOBAR', {
    modes: [mode.numeric, mode.iso8859_1], // disallowing alphaNumeric mode
  }),
  mode.bytes([0x12, 0x34]), // finish message with non-standard custom data
));

Trailer

ISO 18004 requires 0b11101100_00010001 (0xEC11) be used as padding bytes at the end of a message, but you can customise this otherwise dead space with any 16-bit value (0x00000xFFFF).

const code = generate('LEAN-QR LIBRARY', {
  trailer: 0x0000,
});

Combining a large minVersion with a trailer of 0x0000 will reveal the pattern of the chosen mask, which may be desirable for artistic reasons. In general, this should be left as the default value to maximise readability.

.with(...modes)

Adds one or more custom modes to the list of default modes.

const myGenerate = generate.with(myCustomMode);
const code = myGenerate('text');

This is equivalent to adding the custom mode(s) to the modes argument in all calls:

const code = generate('LEAN-QR LIBRARY', {
  modes: [
    mode.numeric,
    mode.alphaNumeric,
    mode.ascii,
    mode.iso8859_1,
    mode.shift_jis,
    mode.utf8,
    myCustomMode,
  ],
});

Note that you only need to register custom modes if you want to use automatic encoding. There is no need to register a custom mode if you call it explicitly:

const code = generate(myCustomMode('FOOBAR'));

Bitmap2D

This class is returned by generate and allows you to display the output in several ways:

.toString([options])

Converts the QR Code to a string (e.g. for printing to the terminal)

console.log(code.toString({
  on: '##',
  off: '  ', // note: this is 2 spaces
  lf: '\n',
  padX: 4,
  padY: 4,
}));

The options shown are the defaults. Note that for portability, the defaults only use ASCII characters, but this will not result in a successful read (QR Code scanners require well-defined edges between cells). As such, if you want to print a scannable QR Code, you will need to replace at least on with an alternative value for your environment (see below).

ISO 18004 requires 4-cell padding to guarantee a successful read, but you can change it to any value if you want.

Customising on and off

If you are printing to a terminal which supports ANSI escape codes, you can use them to define the cell colour:

// Inverted "on" cells:
console.log(code.toString({
  on: '\u001B[7m  \u001B[0m', // ANSI escape: inverted
}));

// Or explicit black / white cells:
console.log(code.toString({
  on: '\u001B[40m  ',   // ANSI escape: black background
  off: '\u001B[107m  ', // ANSI escape: white background
  lf: '\u001B[0m\n',    // ANSI escape: default
}));

Ensure the on and off strings have the same printed length or the resulting QR Code will be misaligned.

For displays which do not support ANSI escapes, but do have a line height equal to the character height, you could use Unicode box drawing characters instead:

// Unicode box drawing characters: (not recommended)
console.log(code.toString({
  on: '\u2588\u2588',
}));

.toCanvas(canvas[, options])

Renders the QR Code inside an existing canvas on the page.

const targetCanvas = document.getElementById('my-canvas');
code.toCanvas(targetCanvas, {
  on: [0x00, 0x00, 0x00, 0xFF], // black
  off: [0x00, 0x00, 0x00, 0x00], // transparent
  padX: 4,
  padY: 4,
});

The options shown are the defaults.

This will replace the image in targetCanvas (which must be a <canvas> element) with a copy of the current QR Code. The result is always at a scale of 1 pixel per module (the canvas will be resized to the correct size automatically). To display this image at a reasonable size, it is recommended that you use the following CSS:

#my-canvas {
  width: 100%;
  image-rendering: pixelated;
}

The values of on and off should be arrays in [red, green, blue, alpha?] format. If alpha is omitted, 255 is assumed.

.toImageData(context[, options])

If you do not want to replace the entire content of a canvas, you can can use toImageData instead. This returns an ImageData representation of the QR Code (created using context.createImageData).

const myContext = myCanvas.getContext('2d');

const imageData = code.toImageData(myContext, {
  on: [0x00, 0x00, 0x00, 0xFF], // black
  off: [0x00, 0x00, 0x00, 0x00], // transparent
  padX: 4,
  padY: 4,
});

// later
myContext.putImageData(imageData, 200, 100);

The options shown are the defaults.

.toDataURL([options])

Returns a string which can be used as a href, e.g. for downloading;

const url = code.toDataURL({
  type: 'image/png',
  on: [0x00, 0x00, 0x00, 0xFF], // black
  off: [0x00, 0x00, 0x00, 0x00], // transparent
  padX: 4,
  padY: 4,
  scale: 1,
});

const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'my-qr.png');
document.body.append(link);

The options shown are the defaults. You will probably want to set scale to a larger integer as a convenience to your users.

This URL can also be used as an img source, but this is not recommended (for best results use toCanvas — this will avoid blurry edges on high resolution displays and if the user zooms in).

Note that this is only available in-browser; it will fail if called in NodeJS.

.get(x, y)

For custom output formats, you can inspect the data directly:

for (let y = 0; y < code.size; y++) {
  for (let x = 0; x < code.size; x++) {
    process.stdout.write(code.get(x, y) ? '##' : '  ');
  }
  process.stdout.write('\n');
}

Requests outside the range 0 ≤ x < size, 0 ≤ y < size will return false.

.size

An integer representing the size of the QR Code, excluding any padding. This is equal to 17 + version * 4.

extras/svg

This is not included in the main library to keep it small, but if you need SVG output, you can access it from a separate import (adds ~2kB).

toSvg(code, target[, options])

Creates or replaces an svg element with the current QR Code

import { toSvg } from 'lean-qr/extras/svg';

const mySvg = document.getElementById('my-svg');
toSvg(code, mySvg, {
  on: 'black',
  off: 'transparent',
  padX: 4,
  padY: 4,
  width: null,
  height: null,
  scale: 1,
});

The options shown are the defaults.

This will replace the image in mySvg (which must be an svg element) with a copy of the current QR Code. The result is always at a scale of 1 SVG unit per module (the viewBox will be resized to the correct size automatically). You can define a different size for the SVG element to scale the image.

If target is the document object, this will create and return a new SVG entity associated with the document (but not attached to it):

const mySvg = toSvg(code, document, {/* options */});

document.body.append(mySvg);

If width / height is given, the root SVG element will have the explicit size applied. If these are not specified, they will be auto-calculated by multiplying the QR Code size + padding by scale. You can override this for display by setting CSS properties (e.g. mySvg.style.width = '100%'; mySvg.style.height = 'auto';).

toSvgSource(code[, options])

Like toSvg but returns the source code for an SVG, rather than manipulating DOM nodes (can be called inside NodeJS).

import { toSvgSource } from 'lean-qr/extras/svg';

const svgSource = toSvgSource(code, {
  on: 'black',
  off: 'transparent',
  padX: 4,
  padY: 4,
  width: null,
  height: null,
  xmlDeclaration: false,
  scale: 1,
});

The options shown are the defaults, and match toSvg.

Returns a complete SVG document which can be written to a standalone file or included inside a HTML document.

If writing to a file, you should set xmlDeclaration to true (this prefixes the source with <?xml version="1.0" encoding="UTF-8" ?>).

toSvgDataURL(code[, options])

Like toSvg but returns a data:image/svg+xml URL containing the image data, suitable for displaying in an img tag or downloading from an a tag. Can be called inside NodeJS.

import { toSvgDataURL } from 'lean-qr/extras/svg';

const dataURL = toSvgDataURL(code, {
  on: 'black',
  off: 'transparent',
  padX: 4,
  padY: 4,
  width: null,
  height: null,
  scale: 1,
});

The options shown are the defaults, and match toSvg.

toSvgPath(code)

A raw SVG path definition for the QR Code. Used by toSvg and toSvgSource.

import { toSvgPath } from 'lean-qr/extras/svg';

const svgPath = toSvgPath(code);
// e.g. "M1 2L1 1L2 1L2 2ZM3 3L3 2L4 2L4 3Z"

The returned path is always at a scale of 1 SVG unit to 1 module, with no padding. The path will define the whole QR Code in a single string suitable for use inside <path d="[path here]">, and can be used with fill-rule of either evenodd or nonzero. The path is optimised to ensure only true edges are defined; it will not include overlapping edges (and will not have "cracks" between pixels). No other guarantees are made about the structure of this string, and the details could change in later versions.

extras/node_export

This is not included in the main library as it only applies to NodeJS, but if you need PNG output from NodeJS, you can access it from a separate import (adds ~1kB).

toPngBuffer(code[, options])

Returns a Buffer containing a PNG file for the QR Code.

import { toPngBuffer } from 'lean-qr/extras/node_export';

const pngBuffer = toPngBuffer(code, {
  on: [0, 0, 0, 255],
  off: [0, 0, 0, 0],
  padX: 4,
  padY: 4,
  scale: 1,
});

The options shown are the defaults.

Returns a complete PNG Buffer which can be written to a standalone file.

toPngDataURL(code[, options])

Like toPngBuffer but returns a data:image/png URL containing the image data, suitable for displaying in an img tag or downloading from an a tag.

import { toPngDataURL } from 'lean-qr/extras/node_export';

const dataURL = toPngDataURL(code, {
  on: [0, 0, 0, 255],
  off: [0, 0, 0, 0],
  padX: 4,
  padY: 4,
  scale: 1,
});

The options shown are the defaults, and match toPngBuffer.

extras/react

This import provides convenience wrapper components for React and Preact.

makeAsyncComponent(framework, generate)

Call makeAsyncComponent from the global scope (not inside a render method) to generate a component which can be rendered later:

import { generate } from 'lean-qr';
import { makeAsyncComponent } from 'lean-qr/extras/react';
import * as React from 'react';

export const QR = makeAsyncComponent(React, generate);

All the configuration options documented for generate and toCanvas can be passed to the wrapper component, plus a className for the rendered canvas:

<QR
  content="Hello!"
  minVersion={1}
  maxVersion={40}
  minCorrectionLevel={correction.L}
  maxCorrectionLevel={correction.H}
  mask={null}
  trailer={0xEC11}
  padX={4}
  padY={4}
  on={[0, 0, 0, 255]}
  off={[0, 0, 0, 0]}
  className=""
/>

The options shown are the defaults. All properties are optional except content.

The component will render to a <canvas> from a useEffect hook (this is the most performant option if the QR Code will change dynamically), but this cannot be server-side rendered (nothing will appear on the client until hydration completes). If server-side or synchronous rendering is important to you (at the expense of some performance), use makeSyncComponent instead.

You can also change the default values for the component by passing an extra argument to makeAsyncComponent. For example, if you want all QR Codes to use at least correction level H:

const QR = makeAsyncComponent(React, generate, {
  minCorrectionLevel: correction.H,
});

makeSyncComponent(framework, generate, toSvgDataURL)

Call makeSyncComponent from the global scope (not inside a render method) to generate a component which can be rendered later:

import { generate } from 'lean-qr';
import { makeSyncComponent } from 'lean-qr/extras/react';
import { toSvgDataURL } from 'lean-qr/extras/svg';
import * as React from 'react';

export const QR = makeSyncComponent(React, generate, toSvgDataURL);

All the configuration options documented for generate and toSvgDataURL can be passed to the wrapper component, plus a className for the rendered img:

<QR
  content="Hello!"
  minVersion={1}
  maxVersion={40}
  minCorrectionLevel={correction.L}
  maxCorrectionLevel={correction.H}
  mask={null}
  trailer={0xEC11}
  padX={4}
  padY={4}
  on="black"
  off="transparent"
  className=""
/>

Note: the API for this has some differences compared to makeAsyncComponent: on and off take string values for the colour, rather than arrays:

<QR content="Hello!" on="black" off="rgba(0,0,0,0)" />

The component will render to an <img> synchronously, using useMemo as an optimisation. This is not the most performant option if the code will change frequently (use makeAsyncComponent instead if that is your use-case, to avoid rendering lag), but supports server-side rendering and avoids the QR Code "flickering" when it first appears.

extras/errors

All errors reported by the library use codes rather than messages to keep the library size small. You can check the meaning of these codes in the Error Reference. If you want to display the errors to users, this import provides a convenience mapping into English error messages.

readError(error)

Converts errors into human-readable messages. If the error is a lean-qr library error, it will return a description rather than the code. If the error is from another library, this will extract the message property.

import { readError } from 'lean-qr/extras/errors';

try {
  const code = generate('LEAN-QR LIBRARY');
} catch (e) {
  const message = readError(e);
  window.alert(message);
}

Error Reference

Errors are reported as numbers to save space. Errors contain a stable code property which can be used to look up the type of error:

code message meaning
1 lean-qr error 1 No content provided
2 lean-qr error 2 maxVersion must be ≥ minVersion
3 lean-qr error 3 maxCorrectionLevel must be ≥ minCorrectionLevel
4 lean-qr error 4 content exceeds maximum capacity of maxVersion
5 lean-qr error 5 content cannot be encoded using the chosen modes
6 lean-qr error 6 Invalid framework provided to the React wrapper
7 lean-qr error 7 Invalid generate function provided to the React wrapper
8 lean-qr error 8 Invalid toSvgDataURL function provided to the React wrapper

If you want to display errors in a moderately human-readable way, you can use the readError extra.

Custom Modes

If you want to support a non-standard data format, you can write a custom mode:

const myMode = (value) => (data, version) => {
  // call data.push zero or more times to encode the value:
  data.push(0b101010, 6); // value, bits (supports up to 24-bits)
};

const code = generate(myMode('foobar'));

If you want your custom mode to be compatible with auto, you need to provide a pair of properties:

// a function taking a character and returning true if it is suppoerted
myMode.test = RegExp.prototype.test.bind(/[0-9a-zA-Z]/);
// or
myMode.test = (c) => /[0-9a-zA-Z]/.test(c);

// a function which estimates the number of bits required for an input
// (fractional results will be rounded up)
myMode.est = (value, version) => (12 + value.length * 8);

For example, the implementation of ascii:

const ascii = (value) => (data, version) => {
  data.push(0b0100, 4);
  data.push(value.length, version < 10 ? 8 : 16);
  [...value].forEach((c) => data.push(c.codePointAt(0), 8));
};
ascii.test = RegExp.prototype.test.bind(/[\u0000-\u007F]/);
ascii.est = (value, version) => (
  4 + (version < 10 ? 8 : 16) +
  value.length * 8
);

See modes for details on how to register your custom mode.

Version 2 Migration Guide

Version 2 brings many advantages, including better support for QR features, faster performance, and reduced code size. For basic use, updating from version 1.x to 2.x should have no issues, but if you are using more advanced customisation options or relying on specific behaviours, you may need to update your code:

Comparison With Other Libraries

Many other QR Code generating libraries exist, with some offering additional out-of-the-box visual features over lean-qr:

library size / compressed performance1
(codes per second)
supported encodings supported outputs
num alnum latin-1 utf-8 SJIS automatic text canvas SVG PNG API other
lean-qr (demo) 7.2kB / 3.7kB 2800 / 66 / 35 2 3 -
qrcode 23.4kB / 9.0kB 3200 / 59 / 23 4 ⚠️5 6 -
qr.js 25.9kB / 6.5kB 730 / ❌ / ❌ -
qrcode-generator (demo) 56.7kB / 11.7kB 950 / ❌ / 17 ⚠️5 7 ⚠️8 img
qr-creator (demo) 12.1kB / 5.1kB 670 / ❌ / ❌ 9 ⚠️5 Styling: Rounded edges
awesome-qr 45.8kB / 16.0kB untested ⚠️5 Styling: Background image, animated GIF
QR-Code-Generator (demo) 45.4kB / 13.3kB 620 / ❌ / 21 10 ⚠️5 11 ⚠️12 13 13 -

1 Measured in codes per second (higher is better). See methodology.

2 Requires extra import, adds 1.3kB.

3 Requires extra import, adds 1.1kB.

4 Takes ~13ms to initialise when imported.

5 Non-standard: does not set ECI 26.

6 Requires extra import, adds 21kB.

7 Requires extra import, adds 39kB.

8 In-browser only.

9 Tested in Chrome, as this library does not support NodeJS. Takes ~10ms to initialise when imported.

10 Crudely measured using online demo.

11 Only available in Java version.

12 Automatic mixed modes only available in Java version.

13 Source code contains an example of this output, but it is not part of the library.

Performance Methodology

The performance of each library has been evaluated by running in NodeJS 18.14.0 on a "2.5 GHz Quad-Core Intel Core i7" by repeatedly generating codes (including the output of a string or image, but not including displaying or saving) for each test message with 8 random digits on the end (running "hot" but avoiding caching), and using as much default configuration as possible. Timings have been averaged over a number of samples, with the reported number of codes per second being calculated from the average time. Values are listed to 2 significant figures.

Load / startup time has also been checked, and is noted in a footnote for libraries where it is > 5ms.

The test messages used:

  • THIS IS MY MESSAGE (18 characters + 8 random digits)
  • The value of π is 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214… (accoring to https://oeis.org/A000796). Having so many digits means it will probably be advantageous to encode it using numeric mode, even though it has Unicode characters around it. WE CAN ALSO USE BLOCK CAPITALS FOR PARTS OF THE MESSAGE, WHICH WILL PROBABLY BE BEST ENCODED AS ALPHANUMERIC. Finally we can use some £ characters which are best encoded in ISO8859-1, though the cost of switching ECI from Unicode means we will need quite a few of them to make it worthwhile ££££££££££££££££££££££££££££££. repeated 5 times (3155 characters + 8 random digits)
  • A1 repeated 2100 times (4200 characters + 8 random digits)

Useful Resources