QR codes seem simple — a black-and-white pattern that links to a URL. But there's more to them than that, especially if you're generating them programmatically or integrating scanning into a web app.
What's inside a QR code
A QR code can encode several data types:
- URL: Most common. Opens in the device's browser or relevant app.
- Plain text: Displayed as text when scanned.
-
Email:
mailto:format — opens a new email. -
Phone:
tel:format — initiates a call. -
SMS:
smsto:format — opens SMS with a pre-filled message. -
Wi-Fi credentials:
WIFI:T:WPA;S:NetworkName;P:Password;;— joins the network automatically on iOS and Android. - vCard: Contact information in a structured format.
-
Location:
geo:lat,lng— opens maps.
The most useful Wi-Fi format (for printing in an office or home):
WIFI:T:WPA;S:MyNetworkName;P:MyPassword;H:false;;
When scanned, iOS 11+ and Android 9+ offer to join the network directly. No typing required.
Generating QR codes: quick options
No-code: For single QR codes (business cards, posters, links), a free online QR code generator is the fastest approach — no signup, generates PNG instantly.
JavaScript library (qrcode.js):
<div id="qr"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
new QRCode(document.getElementById('qr'), {
text: 'https://example.com',
width: 256,
height: 256,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
</script>
JavaScript library (qr-code-styling — for custom designs):
import QRCodeStyling from 'qr-code-styling';
const qrCode = new QRCodeStyling({
width: 300,
height: 300,
type: 'canvas',
data: 'https://example.com',
dotsOptions: { color: '#2f855a', type: 'rounded' },
backgroundOptions: { color: '#ffffff' },
imageOptions: { crossOrigin: 'anonymous', margin: 20 }
});
qrCode.append(document.getElementById('canvas'));
qrCode.download({ name: 'qr', extension: 'png' });
Python (qrcode library):
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer
# Basic
qr = qrcode.make('https://example.com')
qr.save('qr.png')
# Custom
qr = qrcode.QRCode(
version=None, # auto-calculate size
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4,
)
qr.add_data('https://example.com')
qr.make(fit=True)
img = qr.make_image(fill_color='black', back_color='white')
img.save('qr.png')
# SVG output
import qrcode.image.svg
qr = qrcode.make('https://example.com', image_factory=qrcode.image.svg.SvgImage)
qr.save('qr.svg')
Error correction levels
QR codes have four error correction levels:
| Level | Recovery capability | Use case |
|---|---|---|
| L (Low) | ~7% | Clean digital contexts |
| M (Medium) | ~15% | General use |
| Q (Quartile) | ~25% | Slightly dirty print |
| H (High) | ~30% | Embedded logos, outdoor print |
Higher error correction makes the QR code larger (more modules) but more resilient to damage, smearing, or having a logo overlaid. For codes that will be printed on packaging or worn materials, use H.
Adding a logo to a QR code
Logos can be overlaid on QR codes if the error correction level is high enough (H). A logo can obscure up to ~30% of the pattern without breaking scanning.
from PIL import Image
import qrcode
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
qr.add_data('https://example.com')
qr.make(fit=True)
img = qr.make_image(fill_color='black', back_color='white').convert('RGBA')
logo = Image.open('logo.png').convert('RGBA')
# Resize logo to ~30% of QR code
qr_size = img.size[0]
logo_size = int(qr_size * 0.28)
logo = logo.resize((logo_size, logo_size), Image.LANCZOS)
# Center the logo
pos = ((qr_size - logo_size) // 2, (qr_size - logo_size) // 2)
img.paste(logo, pos, mask=logo)
img.save('qr_with_logo.png')
Scanning QR codes in a web app
The EyeDropper API and BarcodeDetector API are the modern browser APIs:
if ('BarcodeDetector' in window) {
const detector = new BarcodeDetector({ formats: ['qr_code'] });
// Detect from an image
const barcodes = await detector.detect(imageElement);
for (const barcode of barcodes) {
console.log(barcode.rawValue); // The decoded content
}
// Detect from a video stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const video = document.createElement('video');
video.srcObject = stream;
video.play();
const scan = setInterval(async () => {
const barcodes = await detector.detect(video);
if (barcodes.length > 0) {
console.log('Scanned:', barcodes[0].rawValue);
clearInterval(scan);
}
}, 200);
}
BarcodeDetector support: Chrome 83+, Edge 83+, Android. Not yet in Firefox or Safari.
Fallback: jsQR library
import jsQR from 'jsqr';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) console.log('Scanned:', code.data);
Print guidelines
- Minimum size: 2 × 2 cm (0.8 × 0.8 in) for scanning at normal distances (15–30 cm)
- Quiet zone: 4-module white border around the code — never crop it
- Color: High contrast (dark pattern on light background). White on dark works if contrast is sufficient but scans less reliably on some devices.
- Testing: Always test the printed version, not just the screen version. Print a small test copy before a large run.
Dynamic vs static QR codes
Static QR codes (like those from free generators) encode the URL directly in the pattern. They never expire and require no account or service.
Dynamic QR codes (from commercial services like QR Tiger, Bitly) contain a short redirect URL. The destination can be changed without reprinting. They track scans and expire if you stop paying.
For most use cases — business cards, product packaging, presentations — static QR codes are sufficient and free. Use dynamic QR codes when you need scan analytics or the ability to update the destination post-print.
QR codes remain one of the most pragmatic bridges between physical and digital spaces. Generating them takes seconds; scanning them is built into every modern smartphone. Understanding the options (error correction, logo embedding, Wi-Fi credentials, dynamic vs static) lets you pick the right type for each use case.
Top comments (0)