Skip to main content
CodeLint.Dev Dev Tools
Barcode & QR 10 min read

The Complete Guide to QR Codes: Error Correction, Capacity, and Best Practices

QR codes are everywhere — product packaging, restaurant menus, event tickets, payment terminals. Despite their ubiquity, they are systematically misused in ways that result in codes that fail to scan, break in print, or expose users to security risks. This guide covers the QR standard in depth: error correction levels, version sizing, data capacity limits, the most important data formats (URL, vCard, WiFi, payment), code examples in three languages, and the mistakes that make QR codes fail in the real world.

Try the tool
QR Code Generator
Generate a QR code free →

What Is a QR Code? The ISO 18004 Standard

A QR Code (Quick Response code) is a two-dimensional matrix barcode defined in ISO/IEC 18004:2015. It was invented by Denso Wave in 1994 for tracking automotive parts and has since become the dominant 2D barcode format worldwide. Unlike 1D barcodes (UPC, EAN), QR codes encode data in both dimensions, enabling far higher data density in a small printed area.

A QR code consists of:

  • Finder patterns — the three large squares in the corners that let any camera quickly locate and orient the code
  • Alignment patterns — smaller squares that correct for perspective distortion (present from version 2 onwards)
  • Timing patterns — alternating black/white modules that define the module grid
  • Format information — stores error correction level and mask pattern so a decoder can read it even if the data modules are partially damaged
  • Data modules — the actual payload, encoded and interleaved with error correction codewords

The white quiet zone — a margin of at least 4 modules around the entire code — is mandatory. Skipping it is one of the most common reasons QR codes fail to scan when printed close to a border or edge.

Error Correction Levels: L, M, Q, and H

QR codes use Reed–Solomon error correction, which means a partially damaged or obscured code can still be decoded. The standard defines four error correction levels, each trading data capacity for fault tolerance:

Level Name Data recovery Best for
LLow~7%Maximum data capacity; clean digital environments
MMedium~15%General use; most online QR generators default to M
QQuartile~25%Industrial use; codes exposed to dirt or abrasion
HHigh~30%Codes with embedded logos; maximum damage tolerance

Practical guidance:

  • Use M for most digital-only QR codes (websites, app links). It is the right balance of size and reliability.
  • Use Q for codes that will be printed on packaging, outdoor signage, or anywhere with physical wear.
  • Use H when you want to overlay a logo in the centre of the code — the higher redundancy means the logo occluding up to 30% of the modules is survivable. Keep the logo to roughly 20% of the code area.
  • Use L only when you need to maximise the data density for a fixed physical size and the code will always be scanned in pristine digital conditions.

QR Versions and Data Capacity

The ISO standard defines 40 QR versions. Version 1 is a 21×21 module grid; each higher version adds 4 modules per side. Version 40 is a 177×177 module grid. The version is selected automatically based on data length and error correction level — you rarely choose it directly.

Maximum data capacity at each error correction level (alphanumeric characters):

  • Level L: up to 4,296 alphanumeric characters (version 40)
  • Level M: up to 3,391 alphanumeric characters
  • Level Q: up to 2,420 alphanumeric characters
  • Level H: up to 1,852 alphanumeric characters

For numeric-only data the limits are higher (up to 7,089 digits at level L). For binary/UTF-8 data the limits are lower (up to 2,953 bytes at level L).

The key practical takeaway: shorter data = smaller QR code = faster, more reliable scanning. A URL like https://example.com/p/abc123 (34 characters) produces a version 2 code. A URL with UTM parameters and query strings can easily push into version 10+, producing a visually dense code that fails on low-resolution cameras or in poor lighting.

Always shorten URLs before encoding them. Use a short domain redirect or a service like your own redirect infrastructure. A 20-character short URL vs a 150-character original URL is the difference between a version 2 and a version 12 QR code.

Data Formats: URL, vCard, WiFi, and Payment

A QR code encodes a plain text string. The "type" of QR code is just a convention about how that string is formatted. Scanners detect the format and launch the appropriate app.

URL

The simplest and most common format. The string is a full URL including the scheme:

https://codelint.dev/barcode-qr/qr-code-generator

Scanners on iOS and Android will offer to open the URL directly.

vCard 3.0 (contact)

The vCard format encodes a contact card that the phone imports directly into the contacts app:

BEGIN:VCARD
VERSION:3.0
FN:Alice Smith
ORG:Acme Corp
TEL;TYPE=WORK,VOICE:+1-555-123-4567
EMAIL:alice@acme.com
URL:https://acme.com
END:VCARD

Keep vCards concise — each field adds to the encoded length. Include only the fields you actually want shared.

WiFi credentials

The WiFi format lets users join a network by scanning without typing a password:

WIFI:T:WPA;S:MyNetwork;P:MyPassword123;H:false;;

Fields: T: is the security type (WPA, WEP, or nopass), S: is the SSID, P: is the password, H: is whether the network is hidden. Values containing ;, ,, ", or \ must be escaped with a backslash.

Payment (EPC QR / UPI)

In Europe, the EPC QR format (used by banks for instant SEPA transfers) encodes IBAN and BIC. In India, the UPI format encodes the VPA (virtual payment address). These are region-specific conventions — always verify with the relevant payment standard before generating payment QR codes for production use.

Code Examples in Python, Node.js, and Go

Generating QR codes programmatically is straightforward in every major language. Below are working examples for the three most common server-side environments.

Python Python (qrcode library)
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer

# Basic QR code
qr = qrcode.QRCode(
    version=None,          # auto-select version
    error_correction=qrcode.constants.ERROR_CORRECT_M,
    box_size=10,           # pixels per module
    border=4,              # quiet zone in modules (minimum 4)
)
qr.add_data('https://codelint.dev')
qr.make(fit=True)          # auto-selects smallest version that fits

img = qr.make_image(fill_color='black', back_color='white')
img.save('qrcode.png')

# Styled QR code with rounded modules
img_styled = qr.make_image(
    image_factory=StyledPilImage,
    module_drawer=RoundedModuleDrawer()
)
img_styled.save('qrcode_rounded.png')
JavaScript Node.js (qrcode package)
import QRCode from 'qrcode';

// Generate as PNG file
await QRCode.toFile('qrcode.png', 'https://codelint.dev', {
  errorCorrectionLevel: 'M',
  margin: 4,        // quiet zone modules
  width: 400,       // total image width in pixels
  color: {
    dark: '#000000',
    light: '#ffffff',
  },
});

// Generate as data URL (useful for <img> tags in web apps)
const dataUrl = await QRCode.toDataURL('https://codelint.dev', {
  errorCorrectionLevel: 'M',
  width: 400,
});
// dataUrl is "data:image/png;base64,..."

// Generate as SVG string (scalable, no pixel artifacts)
const svg = await QRCode.toString('https://codelint.dev', {
  type: 'svg',
  errorCorrectionLevel: 'M',
  margin: 4,
});
// Write to file or embed directly in HTML
Go Go (skip2/go-qrcode)
package main

import (
	"image/color"
	"log"

	qrcode "github.com/skip2/go-qrcode"
)

func main() {
	// Generate PNG file — positive integer = pixel size
	err := qrcode.WriteFile(
		"https://codelint.dev",
		qrcode.Medium, // error correction level
		256,           // output image size in pixels
		"qrcode.png",
	)
	if err != nil {
		log.Fatal(err)
	}

	// Generate with custom colours
	qr, err := qrcode.New("https://codelint.dev", qrcode.Medium)
	if err != nil {
		log.Fatal(err)
	}
	qr.ForegroundColor = color.RGBA{R: 30, G: 30, B: 30, A: 255}
	qr.BackgroundColor = color.White
	qr.DisableBorderCheck = false // always keep the quiet zone
	err = qr.WriteFile(256, "qrcode_custom.png")
	if err != nil {
		log.Fatal(err)
	}
}

Sizing and Print Specifications

A QR code that looks fine on screen can fail completely in print. The physical size must be large enough for the camera to resolve the individual modules, and the contrast must be sufficient for the scanner's image sensor.

Minimum physical size:

  • ISO 18004 recommends a minimum module size of 0.33 mm. A version 1 code (21 modules wide including the quiet zone) needs at least 21 × 0.33 mm = ~7 mm. A version 10 code (57 modules) needs at least 57 × 0.33 mm = ~19 mm.
  • In practice, use a minimum of 2.5 cm × 2.5 cm (1 inch) for any QR code intended to be scanned by a smartphone at arm's length.
  • For outdoor signage scanned from 2 metres away, scale up proportionally — 10 cm × 10 cm is a safe minimum for 2m scan distance.

Resolution for print:

  • For print, export at 300 DPI minimum. At 300 DPI, a 1-inch QR code is 300×300 pixels. Use SVG whenever possible — it is resolution-independent and will be crisp at any print size.
  • Never upscale a rasterised PNG. A 200×200 pixel PNG scaled to print at 4 inches will be blurry. Generate at the target size from the beginning.

Contrast requirements:

  • The dark modules must have at least a 4:1 contrast ratio against the light background (ISO 15415). Black on white satisfies this easily. Dark navy on white also works. Dark grey on light grey may not.
  • Inverted QR codes (light modules on dark background) are supported by most modern scanners but not all older devices. If broad compatibility matters, stick to dark-on-light.
  • Never print on a patterned or photographic background. The finder patterns must be clearly distinguishable from any background imagery.

Dynamic vs Static QR Codes

A static QR code encodes the destination directly. Once printed, it cannot be changed — if the URL changes, you must reprint the code. The URL is the data itself.

A dynamic QR code encodes a short redirect URL that points to a server. The server redirects to the actual destination, which can be changed at any time through a dashboard without reprinting.

Static Dynamic
Destination changeable?NoYes
Scan analytics?NoYes (via redirect server)
Server dependency?None — works offlineRequires redirect server
Privacy?No trackingRedirect server logs scans
QR code densityDepends on data lengthSmaller (short URL)

When to use static: personal contact cards, WiFi credentials, direct app store links, any scenario where privacy matters or server uptime cannot be guaranteed.

When to use dynamic: marketing campaigns where the landing page may change, printed materials with a long shelf life, anywhere you need scan counts and analytics.

Security note: Always verify the redirect destination of dynamic QR codes you did not generate yourself before scanning — the redirect target can be changed to a phishing page after the code was printed.

The 6 Most Common QR Code Mistakes

  1. Encoding a long URL directly. A 200-character URL with UTM parameters creates a version 10+ code with 57+ modules per side. In poor lighting or at arm's length, this fails. Always shorten URLs first — even a vanity redirect on your own domain (example.com/qr1 → full URL) dramatically improves scan reliability.
  2. No quiet zone. Printing the code edge-to-edge against a border, another image, or text defeats the scanner's ability to locate the finder patterns. Maintain at least 4 module widths of white space on all sides.
  3. Exporting as a small JPEG or GIF. JPEG compression introduces artefacts around high-contrast edges — exactly where module boundaries are. Always export as PNG (for raster) or SVG (for vector/print). GIF is limited to 256 colours and unnecessary. SVG is the correct format for print.
  4. Low contrast colours. Trendy brand-coloured QR codes with dark blue on dark teal, or grey on white, fail on camera sensors in average indoor light. If you use brand colours, test the scan rate with multiple devices before printing at scale.
  5. Overlapping logo area exceeds 30%. At error correction level H, up to ~30% of the code can be occluded. A logo that covers 35% will cause intermittent failures. Test with level H, keep the logo to 20% of the total area, and always test the physical print — not just the on-screen preview.
  6. Linking to a non-mobile-friendly page. The majority of QR code scans happen on mobile. If the destination URL loads a desktop-only website, users will bounce. Always test the full scan-to-action flow on a real smartphone before deploying.

Frequently Asked Questions

What is the maximum amount of data a QR code can hold?
A version 40 QR code at error correction level L can hold up to 7,089 numeric characters, 4,296 alphanumeric characters, or 2,953 bytes of binary data. In practice, most use cases should target far less data than this maximum — codes encoding more than 100 characters become visually dense and are more likely to fail in real-world scanning conditions (poor light, low camera resolution, camera angle). If your data is long, shorten URLs with a redirect or compress the payload before encoding.
What error correction level should I use for a QR code with a logo?
Use level H (High), which allows approximately 30% of the code's data modules to be damaged or obscured and still be decoded. Keep the logo centred and limit it to about 20% of the total code area to leave a safety margin below the 30% limit. Always test by scanning the final physical printout — not just the on-screen preview — with multiple devices and in realistic lighting conditions.
Can QR codes be scanned in the dark?
Not without additional illumination. QR code scanners use the device camera to capture a still image and then decode it in software. In the dark, the camera cannot capture sufficient detail regardless of QR code quality. Some dedicated barcode scanners include a red LED illuminator, which works in the dark. Smartphone cameras in Night Mode can sometimes capture enough ambient light, but this is unreliable for QR scanning. For dark environments, consider a backlit display QR code rather than a printed one.
What is the difference between a QR code and a Data Matrix code?
Both are 2D matrix barcodes, but they use different encoding schemes and are defined by different ISO standards (QR: ISO 18004, Data Matrix: ISO 16022). QR codes are larger and more recognisable (the three corner squares are distinctive) but can hold more data at higher versions. Data Matrix codes are physically smaller for equivalent data capacity and are more common in industrial, pharmaceutical, and electronics manufacturing contexts. QR codes are the right choice for consumer-facing applications; Data Matrix is preferred when space is extremely constrained.
Why does my QR code not scan from a printed flyer?
The most common causes are: (1) the quiet zone is missing — the code needs at least 4 module widths of white space on all sides; (2) the physical size is too small — at arm's length, aim for a minimum of 2.5 cm × 2.5 cm; (3) the image was exported as JPEG which introduces compression artefacts at module edges; (4) the contrast ratio is too low due to off-white paper or coloured modules; (5) the URL encoded is so long it creates a high-version code that fails in poor lighting. Export as SVG or high-resolution PNG, maintain the quiet zone, and test the physical print on multiple smartphones.
Are QR codes safe to scan?
QR codes themselves are just data — scanning one cannot directly infect your device. The risk is the same as clicking a URL: the link may lead to a phishing site, a malware download, or a social engineering page. Before scanning, check the URL preview that most smartphone camera apps show before opening. Never scan QR codes in public places that appear to be stickers placed over an original (a common attack called "QR jacking"). For QR codes you did not generate yourself, treat the destination as untrusted until verified.

Ready to try QR Code Generator?

Free, private, and runs entirely in your browser — no sign-up, no server, no data sent anywhere.

Open QR Code Generator