qr-image and qrcode are both npm packages used to generate QR codes in JavaScript environments, but they differ significantly in architecture, output formats, and runtime compatibility. qr-image is a Node.js-focused library that generates QR codes as image streams (PNG, SVG, EPS, PDF) using server-side APIs like Streams and Buffers. qrcode, by contrast, is a universal library designed to work consistently across both browser and Node.js environments, supporting canvas, data URLs, terminal output, and UTF-8 text rendering without relying on Node-specific constructs.
Both qr-image and qrcode solve the same basic problem — generating QR codes from strings — but their internal designs, supported environments, and output capabilities diverge sharply. Understanding these differences is crucial when building anything from a simple marketing site to a complex full-stack application.
qr-image produces QR codes exclusively as image streams or buffers, targeting file-based outputs:
png-js)It uses Node.js Streams under the hood, so every render returns a ReadableStream or Buffer. This makes it ideal for writing QR codes directly to disk or piping into HTTP responses on the server.
// qr-image: Node.js-only stream output
const qr = require('qr-image');
const fs = require('fs');
const qrStream = qr.image('https://example.com', { type: 'png' });
qrStream.pipe(fs.createWriteStream('qrcode.png'));
qrcode supports multiple rendering strategies tailored to different environments:
<canvas> element (browser)This flexibility means you can embed a QR code directly in an HTML page without touching the filesystem.
// qrcode: Browser-friendly canvas rendering
import QRCode from 'qrcode';
QRCode.toCanvas(document.getElementById('qrcode'), 'https://example.com', (error) => {
if (error) console.error(error);
});
qr-image depends heavily on Node.js built-ins like stream, buffer, and zlib. It will fail immediately in browser environments because these modules don’t exist outside Node.js. Even with bundlers like Webpack, polyfilling these dependencies adds unnecessary bloat and complexity.
// This breaks in the browser
import qr from 'qr-image'; // ❌ ReferenceError: stream is not defined
qrcode is written to be runtime-agnostic. It avoids Node-specific APIs entirely and detects available features at runtime (e.g., HTMLCanvasElement in browsers, process.stdout in terminals). This makes it safe to use in React, Vue, Svelte, Electron, Cloudflare Workers, or plain Node.js — all with the same API.
// Works everywhere
import QRCode from 'qrcode';
// In browser
QRCode.toDataURL('hello', (err, url) => {
document.getElementById('img').src = url;
});
// In Node.js
QRCode.toString('hello', { type: 'terminal' }, (err, qrcode) => {
console.log(qrcode);
});
qr-image uses a synchronous/streaming model with minimal error handling. Invalid input usually throws an exception, and there’s no callback or Promise interface. You’re expected to manage streams manually.
// qr-image: No async support, just streams
try {
const svg = qr.imageSync('data', { type: 'svg' }); // Returns Buffer
} catch (e) {
// Handle error
}
qrcode provides consistent async/sync variants for every method (toCanvas, toString, toDataURL, etc.), with optional callback or Promise support. Errors are passed explicitly rather than thrown, making integration with modern async patterns straightforward.
// qrcode: Flexible async handling
const url = await QRCode.toDataURL('hello');
// or
QRCode.toDataURL('hello', (err, url) => { /*...*/ });
While neither package is officially deprecated, qr-image has seen minimal updates in recent years and lacks support for newer QR code standards (like Micro QR or structured append). Its reliance on outdated stream patterns also makes it feel increasingly out of step with modern Node.js practices.
qrcode, on the other hand, receives regular updates, supports UTF-8 mode for compact text rendering, and actively addresses edge cases (e.g., very long URLs, special characters). Its test suite covers both browser and Node environments thoroughly.
If you’re building an Express route that serves QR code images:
With qr-image:
app.get('/qr.png', (req, res) => {
res.type('png');
qr.image(req.query.text, { type: 'png' }).pipe(res);
});
With qrcode:
app.get('/qr.png', async (req, res) => {
const buffer = await QRCode.toBuffer(req.query.text);
res.type('png').send(buffer);
});
Both work, but qrcode gives you a clean Buffer without stream plumbing.
Only qrcode works here:
import { useEffect, useRef } from 'react';
import QRCode from 'qrcode';
function QrComponent({ value }) {
const canvasRef = useRef();
useEffect(() => {
QRCode.toCanvas(canvasRef.current, value);
}, [value]);
return <canvas ref={canvasRef} />;
}
Trying this with qr-image would crash the app.
Despite their differences, both libraries:
qr-image may require manual encoding in some cases)// Both support error correction
qr.image('text', { ec_level: 'H' }); // qr-image
QRCode.toDataURL('text', { errorCorrectionLevel: 'H' }); // qrcode
| Feature | qr-image | qrcode |
|---|---|---|
| Browser Support | ❌ No | ✅ Yes |
| Output Types | 🖼️ PNG/SVG/EPS/PDF (streams) | 🖼️ Canvas / Data URL / Terminal / UTF-8 |
| Runtime | 🖥️ Node.js only | 🌍 Universal (browser + Node) |
| API Style | 📦 Stream-based, sync-only | ⏳ Async/sync, callback or Promise |
| Maintenance | 🕒 Low activity | 🔁 Actively maintained |
| Use Case Fit | 🖨️ Server file generation only | 🧩 Anywhere QR codes are needed |
For new projects, default to qrcode. Its universal compatibility, modern API, and active maintenance make it the clear choice for almost every scenario — whether you’re building a static site, a React SPA, a CLI tool, or a server-rendered app.
Reserve qr-image only for legacy Node.js systems where you’re already deeply invested in stream pipelines and have no browser requirements. Even then, consider migrating to qrcode for future-proofing.
In short: if your code might ever touch a browser — even indirectly — skip qr-image entirely.
Choose qr-image only if you're working in a pure Node.js backend environment and need to generate QR codes as downloadable image files (especially PNG or SVG) using Streams or Buffers. It’s a good fit for server-rendered reports or file exports where you don’t need browser compatibility. However, avoid it for any frontend or isomorphic use case—it won’t work in the browser and hasn’t seen active maintenance in recent years.
Choose qrcode when you need reliable QR code generation across both frontend and backend contexts. It supports canvas rendering for web apps, data URLs for inline embedding, terminal output for CLI tools, and UTF-8 fallbacks for low-dependency environments. Its consistent API and lack of Node.js-specific dependencies make it the safer, more versatile choice for modern full-stack or client-side applications.
This is yet another QR Code generator.
png, svg, eps and pdf formats;npm install qr-image
Example:
var qr = require('qr-image');
var qr_svg = qr.image('I love QR!', { type: 'svg' });
qr_svg.pipe(require('fs').createWriteStream('i_love_qr.svg'));
var svg_string = qr.imageSync('I love QR!', { type: 'svg' });
qr = require('qr-image')
qr.image(text, [ec_level | options]) — Readable stream with image data;qr.imageSync(text, [ec_level | options]) — string with image data. (Buffer for png);qr.svgObject(text, [ec_level | options]) — object with SVG path and size;qr.matrix(text, [ec_level]) — 2D array.text — text to encode;ec_level — error correction level. One of L, M, Q, H. Default M.options — image options object:
ec_level — default M.type — image type. Possible values png (default), svg, pdf and eps.size (png and svg only) — size of one module in pixels. Default 5 for png and undefined for svg.margin — white space around QR image in modules. Default 4 for png and 1 for others.customize (only png) — function to customize qr bitmap before encoding to PNG.parse_url (experimental, default false) — try to optimize QR-code for URLs.zlib.deflateSync instead of pako.