DEV Community

Snappy Tools
Snappy Tools

Posted on

QR Code Generation and Scanning in Web Apps: A Developer's Guide

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;;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)