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 |
|---|---|---|---|
| L | Low | ~7% | Maximum data capacity; clean digital environments |
| M | Medium | ~15% | General use; most online QR generators default to M |
| Q | Quartile | ~25% | Industrial use; codes exposed to dirt or abrasion |
| H | High | ~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.
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')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 HTMLpackage 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? | No | Yes |
| Scan analytics? | No | Yes (via redirect server) |
| Server dependency? | None — works offline | Requires redirect server |
| Privacy? | No tracking | Redirect server logs scans |
| QR code density | Depends on data length | Smaller (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
- 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. - 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.
- 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.
- 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.
- 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.
- 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.