Learn How to Build a QR Code Generator in Python: Absolute Beginners Tutorial

QR codes are everywhere — on product packaging, restaurant menus, boarding passes, and business cards. Python lets you generate them in under ten lines of code. This tutorial walks through building a QR code generator from scratch, with no prior experience assumed.

A QR code (Quick Response code) is a two-dimensional matrix barcode that encodes data as a pattern of black and white squares. Smartphone cameras and dedicated scanner apps can read that pattern and decode it back into a URL, plain text, contact information, or any other string. Unlike a traditional barcode, a QR code holds significantly more data and can be read from any direction. When you generate one in Python, you are producing that exact square grid as an image file that can be printed, embedded in a web page, or distributed digitally.

What Is a QR Code and How Does It Store Data?

At a technical level, a QR code divides its area into small square modules. Each module is either dark or light, and the combination of all modules encodes a sequence of characters. The specification defines several data modes — numeric, alphanumeric, byte, and kanji — and Python's qrcode library handles mode selection automatically. The version of a QR code (an integer from 1 to 40) controls the grid size: version 1 produces a 21×21 module grid, and each additional version adds four modules per side, up to 177×177 at version 40.

One of the more interesting properties of a QR code is its built-in error correction. A portion of the encoded data is redundant, which allows a scanner to reconstruct the original content even if part of the code is damaged, dirty, or obscured. This redundancy is why you can place a logo on top of a QR code and it still scans correctly, as long as the obscured area does not exceed the recovery capacity of the chosen error correction level.

Note

The qrcode library targets Python 3 and is available on PyPI. It wraps the QR code encoding logic and relies on Pillow (the Python Imaging Library fork) to render the output as an image file. You do not need any graphics software installed separately.

The four error correction levels in the QR code standard correspond to the constants qrcode.constants.ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, and ERROR_CORRECT_H. The letters stand for Low, Medium, Quartile, and High, with recovery capacities of roughly 7%, 15%, 25%, and 30% respectively. Choosing a higher level produces a denser, more complex image because more of the grid is devoted to redundancy data.

Installing qrcode and Pillow

Both packages are installable from PyPI through pip. The qrcode[pil] notation installs the core library plus its Pillow dependency in a single command, which is the recommended approach. If your environment already has Pillow installed, you can install qrcode alone, but using the combined form avoids potential version mismatch issues.

bash
pip install qrcode[pil]

After installation, verify it worked by opening a Python interpreter and importing the library:

python
import qrcode
print(qrcode.version_info)
# Expected output: something like (8, 0, 0)
Pro Tip

If you are working inside a virtual environment, activate it before running pip install. This keeps your project dependencies isolated and avoids polluting the global Python installation.

Understanding QRCode Parameters

The qrcode.QRCode class accepts four main parameters that shape the output. Understanding each one before writing code prevents common issues such as a QR code that is too small to scan reliably or one that stores more data than intended.

Default
1 (smallest grid, holds the least data)
Recommended for beginners
Pass None and set fit=True in qr.make(). The library will automatically pick the smallest version that fits your data.
Default
ERROR_CORRECT_M (15% recovery)
When to raise it
Use ERROR_CORRECT_H (30%) when the QR code will be printed on a surface likely to get scratched or partially obscured, such as product packaging or outdoor signage.
What it controls
The pixel width and height of each module (the individual dark or light squares). A value of 10 means each module is 10×10 pixels.
Recommended starting value
10 for a medium-sized image suitable for most uses. Set it higher if the QR code will be displayed at a large size.
What it controls
The width of the quiet zone (the blank margin around the QR code) measured in modules. The QR code specification requires a minimum of four modules.
Default
4. Do not set this below 4 — scanners may fail to read a QR code that lacks adequate quiet zone space.
code builder click a token to place it

Build the correct line that instantiates a QRCode object with medium error correction:

your code will appear here...
error_correction=qrcode.constants.ERROR_CORRECT_M qrcode.QRCode( qrcode.make( = ) qr error_correction=ERROR_CORRECT_H
Why: The variable name qr is assigned with =. The constructor is qrcode.QRCode(, not qrcode.make(, which is a shortcut function that returns an image directly rather than a configurable object. The full constant path qrcode.constants.ERROR_CORRECT_M is required — the shorthand ERROR_CORRECT_H without the module path would raise a NameError.
spot the bug click the line that contains the bug

The function below is meant to generate and save a QR code image. One line contains a bug. Click the line you think is wrong, then hit check.

1 import qrcode
2 qr = qrcode.QRCode(version=1, box_size=10, border=4)
3 qr.add_data("https://pythoncodecrack.com")
4 qr.make(fit=False)
5 img = qr.make_image(fill_color="black", back_color="white")
6 img.save("my_qr.png")
The fix: Change fit=False to fit=True. When fit is False and version=1, the QR code will attempt to fit all data into a version 1 grid (21×21 modules), which can only hold about 17 alphanumeric characters. If the data exceeds that capacity, the library raises a DataOverflowError. Setting fit=True tells the library to automatically upgrade to a higher version if needed.

How to Build a QR Code Generator in Python

The steps below build from a bare-minimum example up to a reusable function with color customization. Each step builds directly on the previous one. All code assumes Python 3.8 or later and a working installation of qrcode[pil].

  1. Install qrcode and Pillow

    Open your terminal and run pip install qrcode[pil]. The bracket notation is shorthand that installs Pillow alongside the core qrcode package. Pillow is required for saving the output as a PNG or JPEG file.

  2. Create and configure a QRCode object

    Import qrcode and instantiate a QRCode object. Pass in version=1, your chosen error_correction constant, box_size=10, and border=4. These are reasonable defaults for a standard-size QR code. Then call qr.add_data() with the URL or text to encode, followed by qr.make(fit=True) to allow automatic version selection.

  3. Generate and save the image

    Call qr.make_image(fill_color="black", back_color="white") to create a Pillow Image object. Then call .save("filename.png") on the returned image. Pillow infers the file format from the extension, so using .png produces a lossless image, which is the recommended format for QR codes.

  4. Customize colors and wrap in a reusable function

    Pass any Pillow-recognized color name to fill_color and back_color — for example, "navy" and "lightyellow". Wrap the entire process in a function that accepts data, filename, fill, and back as parameters. This makes the generator reusable throughout your project without duplicating code.

Here is the minimal working example that corresponds to steps 1 through 3:

python
import qrcode

qr = qrcode.QRCode(
    version=1,
    error_correction=qrcode.constants.ERROR_CORRECT_M,
    box_size=10,
    border=4
)

qr.add_data("https://pythoncodecrack.com")
qr.make(fit=True)

img = qr.make_image(fill_color="black", back_color="white")
img.save("pythoncodecrack_qr.png")

Running this script saves a file named pythoncodecrack_qr.png in the same directory as the script. The image will be a standard black-on-white QR code. You can open it in any image viewer and scan it with a phone camera to confirm it decodes to the correct URL.

Watch Out

If you call qr.make(fit=False) with version=1 and your data exceeds the version 1 capacity, the library will raise a qrcode.exceptions.DataOverflowError. Always use fit=True unless you have a specific reason to lock the version.

The reusable function version

Wrapping the generator in a function allows you to call it multiple times with different data and filenames without repeating the configuration block each time.

python
import qrcode


def generate_qr(
    data: str,
    filename: str,
    fill: str = "black",
    back: str = "white",
    error_level=qrcode.constants.ERROR_CORRECT_M
) -> None:
    """Generate a QR code image and save it to disk.

    Args:
        data:        The text or URL to encode.
        filename:    Output file path, e.g. 'output/my_qr.png'.
        fill:        Module (foreground) color. Default: 'black'.
        back:        Background color. Default: 'white'.
        error_level: Error correction constant. Default: ERROR_CORRECT_M.
    """
    qr = qrcode.QRCode(
        version=None,
        error_correction=error_level,
        box_size=10,
        border=4
    )
    qr.add_data(data)
    qr.make(fit=True)

    img = qr.make_image(fill_color=fill, back_color=back)
    img.save(filename)
    print(f"QR code saved to: {filename}")


# Example calls
generate_qr("https://pythoncodecrack.com", "pcc_qr.png")
generate_qr("https://pythoncodecrack.com", "pcc_qr_navy.png", fill="navy", back="lightyellow")
generate_qr(
    "Contact: Kandi Brian — pythoncodecrack.com",
    "contact_qr.png",
    error_level=qrcode.constants.ERROR_CORRECT_H
)

Notice that version=None is used in the function version. Combining version=None with fit=True is the cleanest approach because it lets the library choose the smallest version that accommodates the data, rather than starting at version 1 and potentially needing to expand. This removes the DataOverflowError risk entirely.

"Explicit is better than implicit." — The Zen of Python (PEP 20)
Pro Tip

For high-contrast and accessibility reasons, stick to dark fill colors on light backgrounds. Low-contrast combinations such as light gray on white scan poorly on many phone cameras, especially in poor lighting.

QR code generation pipeline — the four method calls from data input to saved image file.

Python Learning Summary Points

  1. The qrcode library abstracts the entire QR code encoding process. You pass it a string and it handles mode selection, error correction encoding, and matrix construction internally.
  2. Setting version=None combined with fit=True is the safest configuration — it prevents DataOverflowError and ensures the smallest valid output regardless of data length.
  3. Pillow color names follow CSS conventions and include hundreds of named colors. Any name that Pillow recognizes can be passed to fill_color and back_color, giving you precise control over the visual output without needing to work with hex values.
  4. Wrapping your generator in a function with default parameter values is standard Python practice. It keeps your code DRY (Do Not Repeat Yourself) and makes it easy to generate multiple QR codes with different configurations from a single call site.

From here you can extend the generator in several directions: batch-generating QR codes from a CSV of URLs, embedding a logo image on top of the QR code using Pillow's compositing tools, or building a small web interface around the generator using Flask or FastAPI. Each of those projects builds directly on the same four-method pipeline you used here.

check your understanding question 1 of 4

questions answered correctly

Frequently Asked Questions

The most commonly used library is qrcode, which you install with pip install qrcode[pil]. It depends on Pillow for image rendering. Both libraries are available on PyPI and support Python 3.

Yes, when using the qrcode library to save a QR code as a PNG or JPEG file, Pillow is required as the image backend. Installing qrcode[pil] with pip installs both packages together.

Error correction allows a QR code to remain scannable even when part of it is damaged or obscured. The qrcode library offers four levels: L (7% recovery), M (15%), Q (25%), and H (30%). Higher levels produce denser QR codes.

Yes. The qrcode library's make_image method accepts fill_color and back_color parameters. You can pass any color name recognized by Pillow, such as "black", "white", "navy", or "darkgreen", to customize both the module color and the background.

Pillow supports saving images as PNG, JPEG, BMP, TIFF, and more. PNG is the recommended format for QR codes because it is lossless and preserves sharp edges needed for reliable scanning.