TL;DR
qrcode (the qrcode npm package) is the most-used QR code library — works in Node.js and browsers, outputs SVG, PNG, and data URLs, with no external dependencies. qrcode.react is the React-specific library — renders a QR code as an SVG or canvas element directly in JSX, perfect for React apps. node-qrcode is actually the same package as qrcode — it's just an older alias. For React apps: use qrcode.react. For server-side PNG/SVG generation: use qrcode. For full-featured QR with logos, colors, and styles: use qr-code-styling.
Key Takeaways
- qrcode: ~2.5M weekly downloads — Node.js + browser, SVG/PNG/UTF8 output, Promise-based
- qrcode.react: ~2M weekly downloads — React component, renders directly in JSX, SVG or canvas
- node-qrcode: same package as
qrcode— alias for legacy compat - All three support error correction levels: L (7%), M (15%), Q (25%), H (30%)
- Higher error correction = larger QR code but tolerates more damage/logo overlap
- For QR codes with custom styling/logos:
qr-code-stylingorreact-qr-code
QR Code Concepts
Error Correction Levels:
L — 7% damage recovery → smallest QR code, no logo
M — 15% damage recovery → light logos possible
Q — 25% damage recovery → standard logo usage
H — 30% damage recovery → large logo overlap
QR Output Formats:
SVG → scalable, lightweight, perfect for web display
PNG → rasterized, needed for email attachments, PDF
Canvas → browser-only, real-time rendering
UTF8 → terminal-friendly ASCII art QR code
Terminal → renders QR in terminal using block characters
qrcode
qrcode — the standard QR library for Node.js and browsers:
Node.js usage (server-side)
import QRCode from "qrcode"
// Generate PNG file:
await QRCode.toFile("qr.png", "https://pkgpulse.com/compare/react-vs-vue", {
errorCorrectionLevel: "H",
margin: 4,
width: 300,
color: {
dark: "#000000",
light: "#ffffff",
},
})
// Generate PNG as Buffer:
const pngBuffer = await QRCode.toBuffer("https://pkgpulse.com", {
type: "png",
width: 400,
})
// pngBuffer is a Buffer — write to file, send as response, etc.
// Generate SVG string:
const svg = await QRCode.toString("https://pkgpulse.com", {
type: "svg",
})
// "<svg xmlns=\"http://www.w3.org/2000/svg\"...>..."
// Generate data URL (for HTML img src):
const dataUrl = await QRCode.toDataURL("https://pkgpulse.com", {
type: "image/png",
width: 300,
errorCorrectionLevel: "M",
})
// "data:image/png;base64,iVBOR..."
// Generate UTF8 for terminal output:
const ascii = await QRCode.toString("https://pkgpulse.com", {
type: "terminal",
small: true, // Use smaller blocks
})
console.log(ascii) // Prints QR as ASCII art in terminal
Express API endpoint
import express from "express"
import QRCode from "qrcode"
const app = express()
// Endpoint: GET /qr?url=https://pkgpulse.com&size=300
app.get("/qr", async (req, res) => {
const { url, size = "300", format = "png" } = req.query
if (!url || typeof url !== "string") {
return res.status(400).json({ error: "url parameter required" })
}
try {
if (format === "svg") {
const svg = await QRCode.toString(url, {
type: "svg",
errorCorrectionLevel: "M",
})
res.setHeader("Content-Type", "image/svg+xml")
return res.send(svg)
}
const buffer = await QRCode.toBuffer(url, {
type: "png",
width: Number(size),
errorCorrectionLevel: "H", // High for logos
})
res.setHeader("Content-Type", "image/png")
res.send(buffer)
} catch (err) {
res.status(500).json({ error: "QR generation failed" })
}
})
Validate QR data capacity
import QRCode from "qrcode"
// QR codes have capacity limits — validate before generating:
async function createQR(data: string, options?: QRCode.QRCodeOptions) {
try {
return await QRCode.toDataURL(data, options)
} catch (err) {
if ((err as Error).message.includes("data length")) {
throw new Error(`Data too long for QR code: ${data.length} chars`)
}
throw err
}
}
// Maximum data capacity (version 40, error correction L):
// Numeric: 7089 digits
// Alphanumeric: 4296 chars
// Binary: 2953 bytes
// For URLs, typically fine — only very long URLs approach limits
Next.js route handler (server-side QR)
// app/api/qr/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const url = searchParams.get("url")
const format = searchParams.get("format") ?? "svg"
if (!url) {
return Response.json({ error: "url required" }, { status: 400 })
}
const QRCode = (await import("qrcode")).default
if (format === "svg") {
const svg = await QRCode.toString(url, { type: "svg", errorCorrectionLevel: "Q" })
return new Response(svg, {
headers: { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=86400" },
})
}
const buffer = await QRCode.toBuffer(url, { type: "png", width: 300 })
return new Response(buffer, {
headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400" },
})
}
qrcode.react
qrcode.react — React QR component:
Basic usage
import { QRCodeSVG, QRCodeCanvas } from "qrcode.react"
// SVG output (recommended — scalable, lightweight):
function PackageQR({ packageUrl }: { packageUrl: string }) {
return (
<QRCodeSVG
value={packageUrl}
size={256}
level="H" // Error correction: L, M, Q, H
includeMargin={true}
/>
)
}
// Canvas output:
function PackageQRCanvas({ packageUrl }: { packageUrl: string }) {
return (
<QRCodeCanvas
value={packageUrl}
size={256}
level="H"
/>
)
}
Styled QR with logo
import { QRCodeSVG } from "qrcode.react"
// QR code with logo overlay:
function BrandedQR({ url }: { url: string }) {
return (
<QRCodeSVG
value={url}
size={256}
level="H" // High error correction needed for logo
includeMargin={true}
// Custom colors:
bgColor="#ffffff"
fgColor="#000000"
// Logo in center:
imageSettings={{
src: "/logo.png",
x: undefined, // Auto-center horizontally
y: undefined, // Auto-center vertically
height: 48,
width: 48,
excavate: true, // Remove QR modules under the image
}}
/>
)
}
Download as PNG
import { QRCodeCanvas } from "qrcode.react"
import { useRef } from "react"
function DownloadableQR({ url }: { url: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
function downloadQR() {
const canvas = canvasRef.current?.querySelector("canvas")
if (!canvas) return
const link = document.createElement("a")
link.download = "qrcode.png"
link.href = canvas.toDataURL("image/png")
link.click()
}
return (
<div>
<div ref={canvasRef}>
<QRCodeCanvas value={url} size={300} level="H" />
</div>
<button onClick={downloadQR}>Download QR Code</button>
</div>
)
}
Dynamic QR (updates with input)
import { QRCodeSVG } from "qrcode.react"
import { useState } from "react"
function QRGenerator() {
const [input, setInput] = useState("https://pkgpulse.com")
return (
<div className="flex gap-8">
<div className="flex-1">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter URL or text..."
className="w-full border p-2 rounded"
/>
</div>
<div>
{input && (
<QRCodeSVG
value={input}
size={200}
level="M"
includeMargin={true}
/>
)}
</div>
</div>
)
}
qr-code-styling (for custom designs)
qr-code-styling — full custom styling beyond qrcode/qrcode.react:
import QRCodeStyling from "qr-code-styling"
// Highly customized QR code:
const qrCode = new QRCodeStyling({
width: 300,
height: 300,
data: "https://pkgpulse.com",
image: "/logo.png",
dotsOptions: {
color: "#FF8800", // PkgPulse orange
type: "rounded", // dot shape: square, dots, rounded, classy, classy-rounded, extra-rounded
},
backgroundOptions: {
color: "#ffffff",
},
cornersSquareOptions: {
type: "extra-rounded",
color: "#000000",
},
imageOptions: {
crossOrigin: "anonymous",
margin: 20,
},
})
// Download:
qrCode.download({ name: "pkgpulse-qr", extension: "svg" })
// Append to DOM:
qrCode.append(document.getElementById("qr-container")!)
Feature Comparison
| Feature | qrcode | qrcode.react | qr-code-styling |
|---|---|---|---|
| React component | ❌ | ✅ | ✅ (React wrapper) |
| Server-side | ✅ | ❌ | ❌ |
| SVG output | ✅ | ✅ | ✅ |
| PNG output | ✅ | ✅ (canvas) | ✅ |
| Logo support | ❌ | ✅ | ✅ |
| Custom dot shapes | ❌ | ❌ | ✅ |
| Custom colors | ✅ (fg/bg) | ✅ (fg/bg) | ✅ Full |
| Error correction | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| Bundle size | ~30KB | ~15KB | ~50KB |
When to Use Each
Choose qrcode if:
- Server-side QR generation (Node.js, Next.js API routes)
- PNG output for emails, PDFs, print materials
- CLI tools that print QR codes to terminal
- Non-React apps (vanilla JS, Vue, Svelte)
Choose qrcode.react if:
- React apps where QR renders in the component tree
- SVG output that scales with the UI
- Simple logo overlay needs
- Real-time QR that updates as user types
Choose qr-code-styling if:
- Marketing or brand-critical QR codes needing custom dot shapes
- You need the most polished, customizable QR output
- Rounded dots, classy corners, gradient fills
Community Adoption in 2026
qrcode (the qrcode package on npm) leads with approximately 2 million weekly downloads, reflecting its universal compatibility — it works in Node.js (as a CLI tool, in API routes, and in batch generation scripts) and in browsers. It is the choice when QR code generation needs to happen server-side, for example generating QR codes for ticket systems, print-ready marketing materials, or embedding in PDF reports. Its API is straightforward and well-documented after a decade of maintenance.
qrcode.react reaches approximately 1.5 million weekly downloads, serving the large market of React developers who need to display QR codes in web UIs. The QRCodeSVG component is the most commonly used export — SVG scales cleanly at any size, requires no canvas API, and integrates naturally with React's rendering model. The logo overlay feature (imageSettings) makes it the standard choice for brand-conscious QR displays without reaching for the heavier qr-code-styling package.
qr-code-styling at approximately 500,000 weekly downloads serves a premium niche: QR codes intended for marketing materials where a plain black-and-white grid is aesthetically insufficient. Its dot shape customization (rounded, classy, extra-rounded) and corner style options produce QR codes that are still machine-scannable but visually distinctive — common in retail, events, and brand campaigns. At 50KB it is the heaviest option in this comparison, but teams using it are accepting that trade-off for design flexibility.
Error Correction and Advanced Configuration
QR code generation has more configuration surface than most developers realize, and the defaults of each library make different tradeoffs between code size and scan reliability.
Error correction levels are the most important configuration option. All three libraries support QR code error correction levels L (7% damage recovery), M (15%), Q (25%), and H (30%). Higher error correction produces larger QR codes with more modules, but makes them scannable even when partially damaged or obscured by a logo overlay. For marketing QR codes with a brand logo covering the center, error correction level H is essential — without it, the logo destroys the code. For simple URL redirects where the code will be printed clearly, level M is the standard default.
Version selection (QR code size) is typically automatic — the library chooses the smallest version that can encode the given content at the specified error correction level. However, explicit version control matters for printed materials where the physical QR code size is constrained. A version 5 QR code (37×37 modules) at 300 DPI printed at 1cm × 1cm will produce modules smaller than a typical laser printer's dot pitch, causing scan failures in low-light conditions. For print use cases, requesting a larger version or ensuring the rendered output meets the 10-module quiet zone requirement is important.
node-qrcode's UTF-8 terminal output mode is genuinely useful for server-side development workflows. When building a CLI tool that displays a QR code for device pairing or authentication, rendering the QR code directly to the terminal with qrcode.toString(data, { type: 'utf8' }) avoids the need to serve an image. The terminal output uses Unicode block characters to render the code inline, scannable from most modern smartphone cameras at close range.
qrcode-react's customization API allows overriding individual module colors, which enables creative uses beyond black-and-white codes: gradient fills, dot-style modules instead of squares, and transparent backgrounds for overlay use cases. These aesthetic customizations must be tested for scannability — high-contrast modules remain essential. The library's imageSettings prop handles logo embedding with automatic quiet zone preservation.
For dynamic content (user-specific URLs, one-time tokens), always generate QR codes server-side and cache by content hash — QR code generation is CPU-bound and adds latency if done synchronously on each request.
TypeScript Integration and Accessibility
All three libraries ship TypeScript declarations, but the type quality differs. The qrcode package's types accurately reflect all output format options — the overloaded signatures for toBuffer, toString, and toDataURL ensure you get the right return type based on the type parameter. qrcode.react's types are integrated directly into the package (not in a separate @types/ package) and accurately type the QRCodeSVG and QRCodeCanvas component props, including the imageSettings overlay configuration.
Accessibility for QR codes in web interfaces requires careful attention. A QR code image rendered by qrcode.react or in an HTML img tag should include an aria-label describing what the QR code encodes — for example, "QR code linking to your account dashboard". Screen readers cannot interpret visual QR codes, so the underlying URL or content should always be available as a visible link or button alongside the QR code. For payment QR codes or authentication flows where the QR is the primary interaction, providing a fallback mechanism (manual entry, copy-paste) is essential for accessibility compliance.
Caching and Performance for Server-Side Generation
Server-side QR code generation with the qrcode package involves CPU computation for matrix construction and SVG or PNG rendering. For static content — a product page's QR code that links to a fixed URL — caching the generated output eliminates this cost after the first request. A simple in-process LRU cache keyed by the content string and configuration parameters provides effective caching for low-cardinality QR codes. For user-specific QR codes (unique invitation links, personalized URLs), CDN caching or an external cache (Redis, Cloudflare KV) with the content hash as the cache key is more appropriate. The qrcode package's toBuffer() output is deterministic for identical inputs, making content-addressed caching straightforward to implement correctly.
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on qrcode v1.x, qrcode.react v4.x, and qr-code-styling v1.x.
Compare React and JavaScript utility packages on PkgPulse →
See also: React vs Vue and React vs Svelte, acorn vs @babel/parser vs espree.