qrcode-rs

Build crates.io docs.rs License Crates.io Security audit

qrcode-rs is a Rust crate for generating QR Code and Micro QR Code symbols, rendering them in multiple output formats, and optionally decoding them back through rqrr.

It is designed to cover the common cases well out of the box while still exposing lower-level building blocks for advanced encoding workflows such as fixed versions, mode forcing, GS1/FNC1 payloads, structured payload helpers, and custom rendering.

Highlights

Installation

Use the default feature set if you want the most common renderers enabled:

[dependencies]
qrcode-rs = "2.0"

If you only need the core encoder and want to avoid the default rendering stack:

[dependencies]
qrcode-rs = { version = "2.0", default-features = false }

Enable only the pieces you need:

[dependencies]
qrcode-rs = { version = "2.1", default-features = false, features = ["std", "svg", "serde"] }

Feature Flags

Feature Purpose
default Enables std, image, svg, pic, eps, html, and pdf.
std Links the standard library. Disable for no_std + alloc.
image Raster image rendering, including PNG workflows.
svg, pic, eps, html, pdf Individual renderer backends.
serde Serialize / Deserialize support for core QR data types.
template-json JSON load/export helpers for QrTemplate and QrTemplatePatch.
log Emits encoder diagnostics through the log crate.
deterministic Exposes explicit deterministic construction helpers for audit-sensitive call sites.
parallel Enables Rayon-backed QrCode::par_batch for ordered parallel batch encoding.
batch-zip-deflate Enables DEFLATE compression for library-level ZIP batch packaging.
async Enables Tokio-backed async rendering helpers.
cli Builds the qrencodes command-line tool.
decode-rqrr Enables decoding through rqrr.
compat-1x Keeps the 1.x facade API available during the 2.0 migration.

Workspace Crates

qrcode-rs remains the recommended facade for applications. The 2.0 release also publishes smaller crates for libraries that need a narrower dependency surface:

Crate Use it when you need
qrcode-core Core encoding types, module views, traits, and plugin contracts.
qrcode-render Shared render traits, text/Unicode/ANSI helpers, and color utilities.
qrcode-image Image-backed pixels, PNG/JPEG encoding, logo overlays, and gradients.
qrcode-parse WiFi, vCard, and GS1 payload parsing without the facade.
qrcode-decode Decoder traits, grayscale views, Structured Append parsing, and the optional rqrr adapter.
qrcode-svg, qrcode-eps, qrcode-pic, qrcode-html, qrcode-pdf Individual renderer backends.
qrcode-cli Split qrencodes workspace package used for release assets and CLI-focused builds.

For example:

[dependencies]
qrcode-core = "2.0"
qrcode-svg = "2.0"

qrcode-compat is a workspace-local migration harness and is not published.

Quick Start

Render a PNG

use image::Luma;
use qrcode_rs::QrCode;

fn main() {
    let code = QrCode::new(b"https://example.com").unwrap();
    let image = code
        .render::<Luma<u8>>()
        .min_dimensions(256, 256)
        .build();

    image.save("/tmp/qrcode.png").unwrap();
}

Generates:

PNG output

Builder API with a fixed error-correction level

use qrcode_rs::{EcLevel, QrCode};
use qrcode_rs::render::unicode;

fn main() {
    let code = QrCode::builder("https://example.com")
        .ec_level(EcLevel::H)
        .build()
        .unwrap();

    let terminal = code
        .render::<unicode::Dense1x2>()
        .quiet_zone(false)
        .build();

    println!("{terminal}");
}

Generate a Micro QR SVG

use qrcode_rs::{EcLevel, QrCode, Version};
use qrcode_rs::render::svg;

fn main() {
    let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
    let image = code
        .render::<svg::Color>()
        .min_dimensions(200, 200)
        .dark_color(svg::Color("#800000"))
        .light_color(svg::Color("#ffff80"))
        .build();

    println!("{image}");
}

Preview:

SVG output

Common Encoding Helpers

qrcode-rs includes convenience constructors for common payload types:

Example:

use qrcode_rs::QrCode;

fn main() {
    let wifi = QrCode::for_wifi("GuestNetwork", "p\\;ss", "WPA").unwrap();
    let contact = QrCode::for_vcard("Ada Lovelace", "+15551234", "ada@example.org").unwrap();

    println!("wifi width = {}", wifi.width());
    println!("contact width = {}", contact.width());
}

Batch Rendering And Packaging

Applications can build named in-memory outputs directly from the library:

use qrcode_rs::QrCode;

let files = QrCode::batch_builder(["alpha", "beta"])
    .file_extension("txt")
    .render_bytes(|code, _| Ok::<_, qrcode_rs::QrError>(
        code.render::<char>().quiet_zone(false).build().into_bytes()
    ))
    .unwrap();

let zip_bytes = files.to_zip().unwrap();
assert!(zip_bytes.starts_with(b"PK\x03\x04"));

With image, BatchOutput<RgbaImage>::to_png_grid(...) builds a PNG contact sheet; with batch-zip-deflate, to_zip_with(ZipCompression::Deflated) writes compressed ZIP entries.

Structured Payload Parsing

The parse module can turn QR payload text back into typed domain objects:

use qrcode_rs::parse::wifi::WifiConfig;

fn main() {
    let payload = r#"WIFI:T:WPA;S:GuestNetwork;P:p\;ss;H:false;;"#;
    let cfg = WifiConfig::parse(payload).unwrap();

    assert_eq!(cfg.ssid(), "GuestNetwork");
    assert_eq!(cfg.security().as_str(), "WPA");
    assert!(!cfg.hidden());
}

There are also parsers and examples for:

Accessible Output

For web and document workflows, the crate includes helpers to describe QR codes accessibly:

use qrcode_rs::QrCode;
use qrcode_rs::render::svg;

fn main() {
    let payload = "https://example.com";
    let code = QrCode::new(payload).unwrap();
    let raw_svg = code.render::<svg::Color>().build();
    let labeled_svg = svg::aria_label(&raw_svg, &QrCode::alt_text(payload));

    println!("{labeled_svg}");
}

Decoding with rqrr

Enable decode-rqrr to bridge generated or external grayscale QR images back into data:

[dependencies]
qrcode-rs = { version = "2.0", features = ["decode-rqrr"] }
use image::Luma;
use qrcode_rs::decode::rqrr::RqrrDecoder;
use qrcode_rs::decode::{GrayPixels, QrDecoder};
use qrcode_rs::QrCode;

fn main() {
    let payload = b"https://example.com/decode-bridge";
    let code = QrCode::new(payload).unwrap();
    let image: image::GrayImage = code.render::<Luma<u8>>().min_dimensions(200, 200).build();

    let decoded = RqrrDecoder::new().decode(GrayPixels::from(&image)).unwrap();
    assert_eq!(decoded[0].data(), payload);
}

Command-Line Tool

Enable the cli feature to build the bundled qrencodes binary:

cargo install qrcode-rs --features cli

Basic usage:

qrencodes "https://example.com"
qrencodes -f svg -o out.svg "https://example.com"
qrencodes -f png -o out.png --size 12 --dark '#1a1a2e' --light '#f5f5dc' "Hello"
printf 'piped input' | qrencodes -f unicode
qrencodes --batch ./payloads.txt -f svg -o ./out
qrencodes --batch ./payloads.csv --batch-format csv --batch-column 2 --parallel -f png -o ./out
qrencodes --batch ./payloads.json --batch-format json --batch-key payload -f svg -o ./out
qrencodes --batch ./payloads.txt --batch-pack zip -f svg -o payloads.zip
qrencodes --batch ./payloads.txt --batch-pack grid --grid-columns 3 -f png -o payloads.png
qrencodes validate out.png --expect "Hello"

Library callers can also render an encoded batch into stable, named in-memory outputs and decide how to package them:

use qrcode_rs::{EcLevel, QrCode};

let codes = QrCode::batch(["alpha", "beta"], EcLevel::M)?;
let rendered = QrCode::batch_render(&codes).extension("txt").build::<char>();
assert_eq!(rendered[0].name(), "qr-0001.txt");
# Ok::<(), qrcode_rs::QrError>(())

Supported output formats:

See the full help with:

cargo run --features cli -- --help

More Examples

The examples/ directory covers the main workflows in this crate:

Migration

For the 1.x to 2.0 upgrade path, see MIGRATION-1.x-to-2.0.md. The compat-1x feature keeps the legacy facade available while call sites move to the builder, module-view, streaming, and split-crate APIs. Plugin authors can follow PLUGIN_GUIDE.md.

Supply-chain verification

The repository checks its locked dependency graph with cargo deny and cargo vet in CI. The minimal feature is an explicit dependency-free profile; SVG can be added without enabling raster/image dependencies:

cargo check --locked --no-default-features --features minimal
cargo check --locked --no-default-features --features minimal,svg
cargo tree --locked --no-default-features --features minimal --edges normal

Tagged releases include CycloneDX SBOM archives and Sigstore keyless signatures. Install cosign and verify an asset with the certificate emitted alongside it:

cosign verify-blob release-artifact \
  --signature release-artifact.sig \
  --certificate release-artifact.pem \
  --certificate-identity-regexp '^https://github.com/houseme/qrcode-rs/.github/workflows/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

GitHub provenance attestations for crate assets can be verified with:

gh attestation verify qrcode-rs-2.1.1.crate --repo houseme/qrcode-rs

Security

See SECURITY.md for supported versions and the private vulnerability-reporting process. The repository runs a scheduled RustSec dependency advisory check; the badge above reflects that workflow, not a security certification. The encoder does not provide encryption or authentication, so applications must validate untrusted payloads and protect generated files and logs. The current verification boundary and deferred security gates are summarized in docs/security-audit.md.

License

Licensed under either of:

at your option.

Contributing

See CONTRIBUTING.md for development and contribution guidelines, including the local property-test, differential-test, and fuzzing commands used by CI.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Acknowledgements

Thanks to Kennytm. This crate is based on qrcode-rust.