qrcody
Standalone Python port of qrcody's styled QR code SVG rendering engine — square/rounded/dots/blob/bars/squircle pixel styles, styled finder eyes and pupils, gradients, and framed export (perimeter / scan-me / scan bands). No browser, no server — pure functions in, SVG string out.
This is a hand-port of src/lib/buildSVG.js from the qrcody web app. The
rendering logic itself has no DOM dependency in the original (it already runs
inside a Cloudflare Worker), so the two implementations produce visually
equivalent output, though exact numeric string formatting may differ slightly
(e.g. 500 vs 500.0) — SVG renderers treat these identically.
Install
uv add qrcody
# or, for PNG export too:
uv add "qrcody[png]"
# or, for WebP export too (implies png — WebP is encoded from the rasterized PNG):
uv add "qrcody[webp]"
PNG export uses cairosvg, which needs the native
Cairo library installed on the system (brew install cairo on macOS,
apt install libcairo2 on Debian/Ubuntu). WebP export uses the same cairosvg
rasterization, then re-encodes via Pillow.
Library usage
from qrcody import DEFAULT, build_svg, build_framed_svg
settings = {
**DEFAULT,
"text": "https://example.com",
"pixels_style": "dots",
"eyes_style": "circle",
"frame": "scan-me",
"frame_text": "Scan me",
}
qr_svg = build_svg(settings)
framed = build_framed_svg(qr_svg, settings)
svg_string = framed["svg"] # framed["w"] / framed["h"] give the output dimensions
Layout check
validate_qr_layout(settings) inspects the same settings for problems that hurt
scannability — an inadequate quiet zone, text that overflows the error-correction
level, a center logo over a finder pattern or beyond error-correction capacity,
frame text crowding the QR body — before you render:
from qrcody import validate_qr_layout
result = validate_qr_layout(settings)
if result["severity"] == "error":
raise ValueError("; ".join(i["message"] for i in result["issues"]))
result["severity"] is "ok", "warn", or "error"; each issue is a dict with
code, severity, and a human-readable message.
For PNG (requires the png extra):
from qrcody.png import svg_to_png
png_bytes = svg_to_png(svg_string, width=1000)
For WebP (requires the webp extra):
from qrcody.webp import svg_to_webp
webp_bytes = svg_to_webp(svg_string, width=1000, quality=90)
CLI
qrcody --url "https://example.com" --pixels squircle --frame perimeter -o qr.svg
qrcody --url "https://example.com" --pixels dots --eyes circle -o qr.png
qrcody --url "https://example.com" --pixels dots --eyes circle -o qr.webp --quality 90
Run qrcody --help for the full flag list — it mirrors the qrcody /api/qr
HTTP API parameters (see the qrcody repo's functions/api/qr.js and the
parameter table in its CLAUDE.md).
Settings dict
All keys from qrcody.DEFAULT (see defaults.py) are recognized;
unspecified keys fall back to their default. Notable ones:
| Key | Values |
|---|---|
pixels_style |
square / rounded / dots / blob / bars-h / bars-v / squircle |
eyes_style |
square / rounded / circle / leaf / squircle |
pupils_style |
square / rounded / circle / squircle |
corners_style |
none / round / extra-round / squircle |
frame |
none / perimeter / scan-me / scan |
correction_level |
L / M / Q / H |
Development
uv sync --extra webp # implies png too
uv run pytest
uv run ruff check .
Known gaps vs. the JS implementation
- Logo embedding (
logo/logo_scalesettings) is supported inbuild_svgthe same way as the JS version — pass a data URI aslogo. There is no server-side "fetch a logo URL" helper here (that lives in the qrcody Cloudflare Worker's SSRF-hardened fetch, which is deployment-specific and out of scope for this package). - No automated JS/Python golden-file parity test yet — the port was done by
hand against
src/lib/buildSVG.jsand verified with a matching unit test suite, but the two aren't diffed against each other in CI. If the JS renderer changes, this package needs a manual re-sync.
Note on the package name
This package is published as qrcody on PyPI, distinct from the qrcody
npm/web app it's ported from — there's no npm/PyPI namespace collision since
they're different registries.
Release files for qrcody 1.17.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qrcody-1.17.0.tar.gz | 14.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qrcody-1.17.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.4 kB
Release files / qrcody-1.17.0.tar.gz
| Download URL | qrcody-1.17.0.tar.gz |
|---|---|
| Size | 14.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cc30068831bdbb15029fcfa07faaf897bb65130e5c6c471b6d3d444541d38ca
|
|
BLAKE2b-256 checksum How to use checksums |
e8a2146a71a597ed1e0a6eddc39663faa83aebf9b97802e732644329a1beeba5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / qrcody-1.17.0-py3-none-any.whl
| Download URL | qrcody-1.17.0-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2454b1791dbd52a8ec238d69a6d6cc61ed896071a0f57bd25a612e4cb6e1a2f5
|
|
BLAKE2b-256 checksum How to use checksums |
e7fa2c39b6759f3148bf0d6c7374b9bd29f153da5f87f3197001a50ddf9edc84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|