Skip to main content
๐Ÿ›ก๏ธ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

QR Codes in Python: The Complete Developer Guide to Generation and Decoding

ยท 9 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Quick Response (QR) codes are two-dimensional matrix barcodes widely used for encoding URLs, text, contact information, and authentication tokens. Integrating QR code generation and decoding into Python applications is straightforward using robust libraries like qrcode, pyzbar, and Pillow.

This guide details how to generate basic QR codes, apply custom styles and colors, embed brand logos safely, output print-ready vector formats (SVG), host large files like PDFs via encoded links, and decode QR code images (including NumPy video streams).


QR Code Generation Foundationsโ€‹

The standard pure-Python library for generating QR matrices is qrcode, which relies on Pillow (PIL) for image rendering.

Installationโ€‹

pip install qrcode[pil]

Basic QR Generationโ€‹

The simplest way to create a QR code is to use the make() function, which automatically maps data into a black-and-white image matrix:

import qrcode

data = "https://hrekov.com"
img = qrcode.make(data)
img.save("basic_qrcode.png")

Customizing Matrix Propertiesโ€‹

For design control, instantiate the QRCode class to adjust parameters:

  • version: Controls the matrix size (1 to 40). Version 1 is a 21x21 grid; each subsequent version adds 4 modules per side. Setting this to None lets the library select the smallest size based on the data length.
  • error_correction: Dictates how much data can be recovered if the QR code is damaged or obscured:
    • constants.ERROR_CORRECT_L (7% recovery)
    • constants.ERROR_CORRECT_M (15% recovery - default)
    • constants.ERROR_CORRECT_Q (25% recovery)
    • constants.ERROR_CORRECT_H (30% recovery - highest correction)
  • box_size: Sets the width/height in pixels for each module square.
  • border: Sets the margin thickness (quiet zone) around the QR code (the standard recommends at least 4 modules).
import qrcode
from qrcode import constants

qr = qrcode.QRCode(
version=1,
error_correction=constants.ERROR_CORRECT_M,
box_size=10,
border=4
)
qr.add_data("https://hrekov.com")
qr.make(fit=True)

img = qr.make_image(fill_color="#1a73e8", back_color="#ffecb3")
img.save("custom_colors.png")

Embedding Branded Logosโ€‹

Adding a brand logo to the center of a QR code is a common branding technique. Because the logo covers part of the grid, you must use the highest error correction level (ERROR_CORRECT_H) to ensure the QR code remains scannable.

1. Modern Method (StyledPilImage)โ€‹

The qrcode library includes a StyledPilImage factory that automatically handles logo positioning and provides module styling (like rounded corners):

import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer

qr = qrcode.QRCode(
error_correction=qrcode.constants.ERROR_CORRECT_H
)
qr.add_data("https://hrekov.com")
qr.make(fit=True)

# Rounded modules with a centered logo
qr_img = qr.make_image(
image_factory=StyledPilImage,
embedded_image_path="logo.png",
module_drawer=RoundedModuleDrawer(),
fill_color="darkblue",
back_color="white"
)
qr_img.save("branded_qr_styled.png")

2. Manual Method (Direct Pillow Manipulation)โ€‹

For fine control over placement or borders, generate the base QR image and paste the logo manually using Pillow:

import qrcode
from PIL import Image

# 1. Generate base QR image
qr = qrcode.QRCode(
error_correction=qrcode.constants.ERROR_CORRECT_H
)
qr.add_data("https://hrekov.com")
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGB")

# 2. Process Logo
logo = Image.open("logo.png")
logo_max_width = int(qr_img.size[0] * 0.2) # Limit logo width to 20% of QR size

# Calculate dimensions maintaining aspect ratio
w_percent = (logo_max_width / float(logo.size[0]))
h_size = int((float(logo.size[1]) * float(w_percent)))
logo = logo.resize((logo_max_width, h_size), Image.Resampling.LANCZOS)

# Centering calculations
x_pos = (qr_img.size[0] - logo.size[0]) // 2
y_pos = (qr_img.size[1] - logo.size[1]) // 2

# Paste logo
qr_img.paste(logo, (x_pos, y_pos))
qr_img.save("branded_qr_manual.png")

Logo Design Best Practicesโ€‹

  • Keep it small: The logo should not cover more than 20-25% of the QR code area, even when using H-level error correction.
  • Transparency: Use a PNG with a transparent background to prevent white boxes from clipping the QR code modules.

Outputting Vector Graphics (SVG)โ€‹

For high-resolution print jobs, output QR codes in vector format (SVG) instead of rasterized PNGs to prevent pixelation:

pip install qrcode[svg]

Specify an SVG factory when rendering the image:

import qrcode
import qrcode.image.svg

qr = qrcode.QRCode(
error_correction=qrcode.constants.ERROR_CORRECT_L
)
qr.add_data("https://hrekov.com")

# Use SvgPathImage to draw vector paths
img_svg = qr.make_image(
image_factory=qrcode.image.svg.SvgPathImage,
fill_color="black",
back_color="transparent"
)
img_svg.save("vector_qr.svg")

Generating QR Codes for Hosted URLs (e.g. PDFs)โ€‹

QR codes have strict data capacity limits. A standard QR code can store up to 2,953 bytes of binary data or 4,296 alphanumeric characters. Trying to embed an entire PDF or file binary directly into a QR code is not possible.

Instead, host the file online and encode the resulting URL:

# The hosted file URL is the data payload
PDF_URL = "https://hrekov.com/documents/whitepaper.pdf"

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_Q)
qr.add_data(PDF_URL)
# Generate and save...

URL Linking Best Practicesโ€‹

  • Use Static Links: Ensure the link is permanent. If the URL changes, any printed QR codes will stop working.
  • Keep URLs Short: Denser data requires a higher QR version, making the code harder to scan when printed small. Use short URLs or redirection paths.
  • Enforce HTTPS: Secure links are required for modern mobile device scanners to open target content without warnings.

Decoding QR Codes (Reader)โ€‹

To decode QR codes, use the pyzbar library along with Pillow.

Installationโ€‹

pip install pyzbar pillow

Note: On Linux or macOS, you may need to install the system-level zbar C-library dependency first (e.g., brew install zbar or sudo apt-get install libzbar-dev).

1. Decoding from an Image Fileโ€‹

from PIL import Image
from pyzbar.pyzbar import decode

def decode_qr(image_path):
img = Image.open(image_path)
decoded_objects = decode(img)

results = []
for obj in decoded_objects:
# Data is returned as bytes, decode to UTF-8 string
payload = obj.data.decode("utf-8")
results.append({
"type": obj.type,
"data": payload,
"rect": obj.rect # Bounding box coordinates
})
return results

print(decode_qr("branded_qr_styled.png"))

2. Decoding from a NumPy Array (OpenCV Video Streams)โ€‹

If processing live video frames using OpenCV, the frames are loaded as NumPy arrays. pyzbar can read these arrays directly:

import cv2
from pyzbar.pyzbar import decode

# Cap active webcam video stream
cap = cv2.VideoCapture(0)

while True:
ret, frame = cap.read()
if not ret:
break

# Read QR code from raw frame numpy array
for obj in decode(frame):
print(f"Decoded: {obj.data.decode('utf-8')}")

# Standard OpenCV loop break
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()

Sourcesโ€‹

More on python