What Is a QR Code and How Does It Work?
QR codes are everywhere — on restaurant menus, product packaging, event tickets, and payment terminals. A QR code (Quick Response code) is a two-dimensional barcode that stores data as a grid of black and white squares. Unlike a traditional 1D barcode that holds ~20 digits, a QR code can store thousands of characters in a compact, machine-readable image.
This guide explains how QR codes are structured, how they encode data, and how to generate them programmatically in four languages.
Anatomy of a QR Code
A QR code is a square grid of black squares (modules) on a white background. The grid has several fixed regions with specific roles:
| Region | Purpose |
|---|---|
| Finder patterns | Three identical 7×7 squares in three corners — the scanner uses them to detect position and orientation |
| Timing patterns | Alternating black/white stripes between the finder patterns — establish the module grid |
| Alignment patterns | Smaller squares inside larger QR versions — correct perspective distortion from camera angles |
| Format information | Stores error correction level and mask pattern — duplicated to survive damage |
| Data modules | The remaining squares — encode the actual payload using Reed-Solomon codes |
| Quiet zone | A 4-module white border around the entire code — essential for scanner detection |
The quiet zone (white border) is the most commonly violated rule when embedding QR codes in designs. If it is missing or too narrow, many scanners fail.
How QR Codes Encode Data
QR codes support four encoding modes, each optimised for a different type of content:
| Mode | Characters supported | Bits per character | Example |
|---|---|---|---|
| Numeric | 0–9 | 3.33 | 12345 |
| Alphanumeric | 0–9, A–Z, space, $%*+-./: |
5.5 | HELLO WORLD |
| Byte | ISO-8859-1 or UTF-8 | 8 | https://example.com |
| Kanji | Shift-JIS characters | 13 | Japanese text |
A single QR code can mix modes — a URL uses Byte mode; a phone number uses Numeric mode. The encoder picks the most compact mode automatically.
Versions and Capacity
QR codes come in 40 versions. Version 1 is 21×21 modules; each subsequent version adds 4 modules per side, so Version 40 is 177×177 modules.
Capacity depends on version and error correction level:
| Version | Size | Alphanumeric capacity | Byte capacity |
|---|---|---|---|
| 1 | 21×21 | 25 chars | 17 chars |
| 5 | 37×37 | 106 chars | 72 chars |
| 10 | 57×57 | 395 chars | 271 chars |
| 20 | 97×97 | 1,167 chars | 858 chars |
| 40 | 177×177 | 4,296 chars | 2,953 chars |
These figures are for error correction level L (lowest). Higher error correction reduces capacity because more space is reserved for redundancy.
Error Correction Levels
QR codes use Reed-Solomon error correction — the same algorithm used on CDs and DVDs. Even if part of the code is damaged, obscured, or covered by a logo, scanners can reconstruct the missing data.
| Level | Data recovery | Module overhead | Use case |
|---|---|---|---|
| L | 7% | Low | Clean environments, maximum capacity |
| M | 15% | Medium | General use (most common default) |
| Q | 25% | High | Industrial, dirty environments |
| H | 30% | Highest | Logos embedded in the code, heavy wear |
Tip: If you are placing a logo in the centre of a QR code, use level H. The logo effectively damages the central modules; level H provides enough redundancy to recover that data.
Static vs Dynamic QR Codes
| Static | Dynamic | |
|---|---|---|
| Data location | Encoded directly in the image | Encoded as a short URL that redirects |
| Editable | No — changing content requires a new image | Yes — change the destination without changing the image |
| Tracking | No | Yes — scans, location, device type |
| Size | Larger for long URLs | Small (short URL is always ≤30 chars) |
| Works offline | Yes | No — requires internet for the redirect |
| Cost | Free | Usually a paid service |
For most developer use cases (linking to a fixed URL, embedding contact info, generating Wi-Fi credentials), static QR codes are sufficient and free.
QR Code for Common Payloads
Different payload formats trigger specific device behaviour:
# Plain URL
https://example.com
# Wi-Fi credentials (opens system Wi-Fi join dialog)
WIFI:T:WPA;S:MyNetwork;P:MyPassword;;
# Contact card (vCard)
BEGIN:VCARD
VERSION:3.0
FN:Jane Smith
TEL:+1-555-0100
EMAIL:jane@example.com
END:VCARD
# Email with subject and body
mailto:hello@example.com?subject=Hello&body=Hi%20there
# SMS
sms:+15550100?body=Hello
# Phone number (opens dialler)
tel:+15550100
# Geo coordinates (opens maps)
geo:37.7749,-122.4194
Code Examples
JavaScript (Node.js)
// npm install qrcode
import QRCode from "qrcode";
// Generate as data URL (embed in <img src="...">)
const dataUrl = await QRCode.toDataURL("https://example.com", {
errorCorrectionLevel: "M",
width: 300,
margin: 4, // quiet zone in modules
});
// Generate as PNG file
await QRCode.toFile("qr.png", "https://example.com", {
errorCorrectionLevel: "H",
width: 400,
color: {
dark: "#000000",
light: "#ffffff",
},
});
// Generate as SVG string (scalable, no pixelation)
const svg = await QRCode.toString("https://example.com", {
type: "svg",
errorCorrectionLevel: "M",
});
Python
# pip install qrcode[pil]
import qrcode
from qrcode.constants import ERROR_CORRECT_H
qr = qrcode.QRCode(
version=None, # auto-select smallest version that fits
error_correction=ERROR_CORRECT_H,
box_size=10, # pixels per module
border=4, # quiet zone in modules (minimum 4)
)
qr.add_data("https://example.com")
qr.make(fit=True) # auto-select version
img = qr.make_image(fill_color="black", back_color="white")
img.save("qr.png")
# Generate as bytes (for HTTP response or embedding)
from io import BytesIO
buffer = BytesIO()
img.save(buffer, format="PNG")
png_bytes = buffer.getvalue()
Go
// go get github.com/skip2/go-qrcode
package main
import (
"os"
qrcode "github.com/skip2/go-qrcode"
)
func main() {
// Write PNG to file
err := qrcode.WriteFile(
"https://example.com",
qrcode.Medium, // error correction: Low/Medium/High/Highest
256, // image size in pixels
"qr.png",
)
if err != nil {
panic(err)
}
// Encode to PNG bytes (for HTTP handler)
png, err := qrcode.Encode("https://example.com", qrcode.Medium, 256)
if err != nil {
panic(err)
}
os.WriteFile("qr2.png", png, 0644)
}
PHP
<?php
// composer require endroid/qr-code
use Endroid\QrCode\QrCode;
use Endroid\QrCode\Writer\PngWriter;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\Color\Color;
$qrCode = new QrCode(
data: 'https://example.com',
errorCorrectionLevel: ErrorCorrectionLevel::Medium,
size: 300,
margin: 10, // quiet zone in pixels
foregroundColor: new Color(0, 0, 0),
backgroundColor: new Color(255, 255, 255),
);
$writer = new PngWriter();
$result = $writer->write($qrCode);
// Save to file
$result->saveToFile('qr.png');
// Output as HTTP response
header('Content-Type: ' . $result->getMimeType());
echo $result->getString();
?>
Quick Reference
| Task | JavaScript | Python | Go | PHP |
|---|---|---|---|---|
| Install | npm i qrcode |
pip install qrcode[pil] |
go get github.com/skip2/go-qrcode |
composer require endroid/qr-code |
| Save PNG | QRCode.toFile() |
img.save("qr.png") |
qrcode.WriteFile() |
$result->saveToFile() |
| Get bytes | QRCode.toBuffer() |
BytesIO() |
qrcode.Encode() |
$result->getString() |
| Data URL | QRCode.toDataURL() |
base64.b64encode(bytes) |
base64 encode bytes | base64_encode($bytes) |
| SVG output | type: "svg" |
qrcode.image_factory=SvgImage |
— | SvgWriter |
| Error level | errorCorrectionLevel: "H" |
ERROR_CORRECT_H |
qrcode.Highest |
ErrorCorrectionLevel::High |
Common Pitfalls
No quiet zone. The white border around the code must be at least 4 modules wide. Without it, most phone cameras fail to detect the code. Always set margin: 4 (or equivalent) in your generator.
Low contrast colours. QR scanners expect high contrast between dark and light modules. Dark-on-dark or light-on-light colour schemes fail. Stick to near-black on near-white, or verify with multiple devices before deploying.
Logo too large with wrong error level. A logo covering more than 30% of the code cannot be recovered even with level H (which covers 30%). Keep logos to under 25% of the total area and always test with a real scanner.
URL too long for small print. A 500-character URL at Version 10 produces a dense 57×57 grid that scans poorly when printed under ~2 cm. Use a URL shortener, or switch to a dynamic QR code pointing to a short redirect URL.
Missing https:// prefix. Plain domain names like example.com are not automatically treated as URLs by all operating systems. Always include the protocol: https://example.com.
Raster scaling. Scaling a PNG QR code up causes pixelation and can introduce interpolated grey pixels that confuse scanners. Generate at final display size, or use SVG output for scalable print media.
Frequently Asked Questions
What is the minimum printable size for a QR code?
For a Version 5 code (37×37 modules), each module must be at least 0.25 mm × 0.25 mm for reliable scanning — roughly 9 mm × 9 mm total. Version 1 codes can go smaller. In practice, 2 cm × 2 cm works reliably for standard use; below that, test with multiple devices.
Can QR codes expire?
Static QR codes never expire — the data is encoded in the image itself and does not depend on any server. Dynamic QR codes (which encode a short URL) expire only if the redirect service deactivates the link.
How do I put a logo inside a QR code?
Use error correction level H (30% recovery), place the logo in the centre, keep it under 25% of the total area, and always verify the code scans correctly. Some generators (endroid/qr-code, qrcode-rust) support this natively.
Why does my QR code look different from another generator's output for the same URL?
QR encoders have freedom in choosing: version, mask pattern (8 possibilities), module placement order, and encoding mode mix. Two encoders can produce visually different but equally valid codes for the same data.
Are QR codes secure?
QR codes themselves have no security — they are data containers. Anyone can create a QR code that points to a malicious URL. Educate users to preview the URL before opening, especially for unknown QR codes in public spaces.
What's the difference between QR Code and Data Matrix?
Both are 2D barcodes, but Data Matrix is used mainly in industrial/medical contexts (smaller at tiny sizes, mandatory for GS1-128 pharmaceutical labels). QR Code dominates consumer applications due to broader device support and higher capacity.