qrcode review
qrcode 8.2 is a Python encoder for standard QR Codes. Give it text or bytes and it builds the module grid, then hands that grid to a Pillow, PyPNG, SVG, terminal, or custom image factory. It does not scan photos or decode existing symbols. Release 8.2 made color-mask rendering faster and added the correctly spelled `embedded_image` and `embedded_image_path` arguments while retaining the old misspellings for compatibility. Our Python 3.12 install was a 1 MB pure-Python package that imported in 0.09 seconds; static typing remains informal because the distribution has no `py.typed` marker.
qrcode 8.2 installed in 0.2 seconds, took 1 MB, and imported in 0.09 seconds in our sandbox; pip-audit found 0 known vulnerabilities, so standard QR generation adds little installation cost. Install it for controlled PNG, SVG, terminal, or matrix output; choose something else for decoding, Micro QR, strict declared typing, or decorative codes that nobody can scan-test.
We installed it
| Install | ✓ · 0.2s | 1 package on disk · 1 MB |
| Import | ✓ | import qrcode in 0.09s · pure Python · requires Python >=3.9,<4.0 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does qrcode install cleanly?
Yes. In a fresh container with an empty cache, pip install qrcode finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.
What does qrcode need to run?
Python >=3.9,<4.0, and nothing compiled: it is pure Python. In our run import qrcode succeeded in 0.09s.
qrcode or segno: which should you use?
segno: Micro QR support or standards-focused QR serialization is required. qrcode 8.2 installed in 0.2 seconds, took 1 MB, and imported in 0.09 seconds in our sandbox; pip-audit found 0 known vulnerabilities, so standard QR generation adds little installation cost.
When should you not use qrcode?
The input is a camera frame or an uploaded code that must be decoded. qrcode contains an encoder and image factories, with no reader API.
Use it if
- Your Python job turns URLs, ticket references, Wi-Fi credentials, or short byte payloads into standard QR images.
- You need to choose the error-correction level, quiet-zone width, pixel size, or a fixed QR version from 1 through 40.
- The same encoder must feed PNG files, SVG markup, terminal output, or a printer-specific renderer built from the boolean matrix.
- You want rounded modules, color masks, or a centered logo and can scan-test every design on the devices your users carry.
- The input is a camera frame or an uploaded code that must be decoded. qrcode contains an encoder and image factories, with no reader API.
- Your format list includes Micro QR, rMQR, Data Matrix, Aztec, or linear barcodes. The documented size control covers standard QR versions 1 through 40 only.
- Your typing policy requires installed libraries to declare support through `py.typed`; our qrcode 8.2 installation did not contain that marker.
- A bare install must always produce PNG without an environment check. PyPI puts Pillow and PyPNG behind extras, even though the README describes PyPNG as part of the standard install.
- Decorative codes will ship without tests on real scanners. The README warns that styled symbols can fail with some readers and calls for high error correction when an image covers the center.
Setup reality
We installed qrcode 8.2 without a cache in an unprivileged Python 3.12 Bookworm container with 3 CPUs and 8 GB of RAM. Installation finished in 0.2 seconds and left 1 package occupying 1 MB. pip-audit found 0 known vulnerabilities, while import qrcode completed in 0.09 seconds. The distribution is pure Python, has 3 direct dependencies, requires Python >=3.9,<4.0, uses the BSD license, and omits py.typed.
There are no credentials or project config files. Image output is the part to settle before deployment. For qrcode 8.2, PyPI lists Pillow under the pil extra and PyPNG under png; all installs both. This conflicts with the README's claim that a standard install uses PyPNG. Install qrcode[pil] or qrcode[png] deliberately, then exercise the exact save path in CI. A successful import only proves that the encoder module loads.
Automatic sizing starts at QR version 1 and can grow through version 40 when version=None and fit=True. A fixed version with fit=False raises DataOverflowError when its payload will not fit. More error correction reduces available payload space. Keep the border at the documented minimum of 4 modules unless the downstream specification says otherwise. The constructor accepts smaller borders, so it will not enforce that quiet-zone rule for you.
PyPI's 8.2 code has a few fixes waiting on the main branch. clear() empties the data yet keeps the resolved version, so a reused encoder may stay larger than its next payload needs. The unreleased 8.x changelog also lists patches for shared bisect_left thread safety, full-symbol mask evaluation, and a glog(0) error on some zero-heavy bytes. Create a fresh QRCode per job, avoid sharing instances across threads, and regression-test binary payloads that contain long zero runs.
Patterns
Turn a URL into a PNG create-png
import qrcode
code = qrcode.make("https://example.com/check-in/8F3A")
code.save("check-in.png")PNG rendering needs an installed image backend. Add `qrcode[pil]` for this Pillow-backed shortcut and test `save()` in the deployed environment.
Build a PNG response in memory return-png-bytes
from io import BytesIO
import qrcode
buffer = BytesIO()
qrcode.make(receipt_url).save(buffer, format="PNG")
png_bytes = buffer.getvalue()Pillow can write to `BytesIO`, so a web handler does not need a temporary file. Install the `pil` extra before using this path.
Let the payload choose the matrix size fit-qr-version
import qrcode
from qrcode.constants import ERROR_CORRECT_Q
code = qrcode.QRCode(
version=None,
error_correction=ERROR_CORRECT_Q,
box_size=8,
border=4,
)
code.add_data(invitation_url)
code.make(fit=True)
image = code.make_image(fill_color="#12263a", back_color="white")With `version=None`, `fit=True` searches standard QR versions 1 through 40. The 4-module border is the README's stated minimum.
Reject data that exceeds a fixed version detect-overflow
import qrcode
from qrcode.exceptions import DataOverflowError
code = qrcode.QRCode(version=4)
code.add_data(payload)
try:
code.make(fit=False)
except DataOverflowError as error:
raise ValueError("payload does not fit QR version 4") from error`fit=False` preserves version 4 instead of silently selecting a larger matrix. A higher correction level leaves fewer bits for the payload.
Produce path-based SVG markup render-svg
import qrcode
from qrcode.image.svg import SvgPathImage
svg = qrcode.make(
documentation_url,
image_factory=SvgPathImage,
)
markup = svg.to_string(encoding="unicode")`SvgPathImage` draws the dark modules as one path. The version 8.2 README recommends it to avoid white seams when an SVG is enlarged.
Capture a QR Code as terminal text print-terminal-code
from io import StringIO
import qrcode
code = qrcode.QRCode()
code.add_data("otpauth://totp/Example:alex")
text = StringIO()
code.print_ascii(out=text, invert=True)
terminal_code = text.getvalue()`print_ascii()` does not need Pillow or PyPNG. The `invert` setting must match the terminal's foreground and background colors.
Draw rounded modules round-module-shapes
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer
code = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_Q)
code.add_data(campaign_url)
image = code.make_image(
image_factory=StyledPilImage,
module_drawer=RoundedModuleDrawer(),
)Version 8.2 keeps Pillow drawers under `moduledrawers.pil`. The README says styled codes are not guaranteed to work with every reader.
Apply the radial color mask apply-color-mask
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.colormasks import RadialGradiantColorMask
image = code.make_image(
image_factory=StyledPilImage,
color_mask=RadialGradiantColorMask(),
)The public class in 8.2 is spelled `RadialGradiantColorMask`. Release 8.2 speeds up color-mask processing but does not certify the result for scanners.
Place a logo over the center embed-logo
import qrcode
from qrcode.constants import ERROR_CORRECT_H
from qrcode.image.styledpil import StyledPilImage
code = qrcode.QRCode(error_correction=ERROR_CORRECT_H)
code.add_data(landing_page)
code.make(fit=True)
image = code.make_image(
image_factory=StyledPilImage,
embedded_image_path="brand-mark.png",
)qrcode 8 requires `ERROR_CORRECT_H` when an image covers the center. Version 8.2 accepts the correctly spelled `embedded_image_path` keyword.
Keep a byte payload in byte mode encode-raw-bytes
import qrcode
payload = bytes.fromhex("11aa22bb33cc44dd")
code = qrcode.QRCode(version=None)
code.add_data(payload, optimize=0)
code.make(fit=True)
matrix = code.get_matrix()`optimize=0` stops qrcode from splitting the bytes into shorter mode-specific chunks. Version 8.2 can raise `glog(0)` on some zero-heavy binary inputs; the fix is merged after the release.
Pass the module grid to another renderer draw-custom-output
import qrcode
code = qrcode.QRCode(border=4)
code.add_data(printer_payload)
code.make(fit=True)
for row_number, row in enumerate(code.get_matrix()):
printer.draw_row(row_number, row)`get_matrix()` includes the configured border. With `border=4`, preserve all 4 blank modules around the symbol in the final output.
Generate output from the shell use-qr-command
qr --output=badge.png "employee:1042"
qr --factory=svg-path "https://example.com/help" > help.svg
qr --ascii "terminal payload" > code.txtThe `qr` command accepts `--output` for files and factory names for SVG or PNG. On PowerShell, use `--output` for binary PNG instead of redirection.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| segno | PyPI | Pick it when Micro QR support or standards-focused QR serialization is required. |
| PyQRCode | PyPI | Keep it for an older codebase already tied to its SVG, EPS, PNG, and terminal methods. |
| treepoem | PyPI | Use it when one renderer must cover QR, Aztec, PDF417, Code 128, and many other barcode types. |
| pyzbar | PyPI | Choose it when the actual job is reading QR and one-dimensional barcodes from images. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

