Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Qrcode Laravel Package

chillerlan/php-qrcode

chillerlan/php-qrcode is a PHP QR Code library for generating and reading codes, based on Kazuhiko Arase's generator and a ZXing port. It supports Model 2 versions 1-40, L/M/Q/H error correction, mixed encoding modes, and configurable output.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and version pin

composer require chillerlan/php-qrcode

Before you write a single line, check the PHP constraint against your app:

Package line Min PHP Notes
^5.0 7.4 / 8.0 $outputType constants still exist
^6.0 8.2 outputInterface replaces outputType
dev-main (v7 dev) 8.4 Asymmetric visibility, property hooks

Pin an explicit constraint (^6.0), never dev-main without a commit hash — the main branch is active development for the next major and the examples in it will not compile against your installed release. The README is explicit about matching the examples folder to your version branch (v5.0.x, v6.0.x, main). This is the number one source of "why doesn't this work" issues.

First use case: render, don't save

The minimum viable call is a one-liner that returns a complete data URI:

echo '<img src="'.(new QRCode)->render($data).'" alt="QR Code" />';

QRCode::render() returns a string — for markup/image outputs this is a data: URI, for QRMarkupHTML it's an HTML fragment, for QRStringJSON it's JSON. The default output interface is SVG, which means you get a vector, resolution-independent, no GD dependency, and you can drop it straight into an <img src>, a PDF, or a Blade template.

Where to look first

  1. The user manual — https://php-qrcode.readthedocs.io (usage + installation guides), not the README.
  2. The examples/ folder on the matching branch — this is effectively the real API documentation.
  3. QROptions — the single most important class. It is a chillerlan/php-settings-container based options object; everything you configure lives here. Its documentation lives in the php-settings-container repo.
  4. QROutputInterface — when you need to know which class to put in $outputInterface.

A realistic first integration

use chillerlan\QRCode\{QRCode, QROptions};

$options = new QROptions([
    'eccLevel'        => QRCode::ECC_H,       // L / M / Q / H
    'outputInterface' => QRMarkupSVG::class,  // v6: class name, not a constant
    'addQuietzone'    => true,
    'quietzoneSize'   => 4,
    'outputBase64'    => false,               // raw markup, not a data URI
]);

echo (new QRCode($options))->render('otpauth://totp/...');

Since v6 the QRCode constructor also accepts an iterable and wraps it in a QROptions internally, so new QRCode(['eccLevel' => QRCode::ECC_H]) works too.

Implementation Patterns

Pattern 1 — one options object, many renders

QROptions is cheap to clone and expensive to misconfigure per-call. Build it once (per request or per config), reuse it:

class QrService
{
    private QROptions $options;

    public function __construct()
    {
        $this->options = new QROptions([
            'eccLevel'       => QRCode::ECC_M,
            'scale'          => 8,
            'addQuietzone'   => true,
            'outputBase64'   => false,
        ]);
    }

    public function svg(string $data): string
    {
        $o = clone $this->options;
        $o->outputInterface = QRMarkupSVG::class;
        return (new QRCode($o))->render($data);
    }

    public function png(string $data): string   // needs ext-gd
    {
        $o = clone $this->options;
        $o->outputInterface = QRGdImagePNG::class;
        $o->outputBase64    = true;
        return (new QRCode($o))->render($data);
    }
}

Bind this as a singleton in the container; do not new QROptions inside a loop.

Pattern 2 — route + cache, not route + generate

QR encoding is pure CPU. For anything user-facing, generate once and serve statically:

Route::get('/qr/{slug}.svg', function (string $slug) {
    $path = storage_path("qr/{$slug}.svg");

    if (!file_exists($path)) {
        $svg = app(QrService::class)->svg(Url::to("/p/{$slug}"));
        File::put($path, $svg);
    }

    return response()->file($path, ['Content-Type' => 'image/svg+xml']);
});

QRCode::render($data, $file) also accepts a second argument that writes to disk directly, which is handy for one-off CLI/batch generation:

(new QRCode($options))->render($payload, storage_path("qr/{$id}.png"));

Pattern 3 — write-once, multi-format output

If you need PNG, SVG and PDF for the same payload, keep the payload and options separate and re-render per format. Don't try to convert between outputs — the library generates from data, it does not transcode.

$formats = [
    'svg'  => QRMarkupSVG::class,
    'png'  => QRGdImagePNG::class,
    'webp' => QRGdImageWEBP::class,
    'pdf'  => QRPdf::class,        // requires setasign/fpdf
    'eps'  => QREps::class,
];

Pattern 4 — round-trip testing with the reader

The reader is the best test harness for the generator. Assert that what you produce is actually scannable instead of asserting on byte-for-byte output:

use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\QRCodeReader;

public function test_qr_is_readable(): void
{
    $payload = 'https://example.com/p/abc?ref=qr';

    $options = new QROptions([
        'outputInterface' => QRGdImagePNG::class,
        'outputBase64'    => true,
        'scale'           => 10,
    ]);

    $dataUri = (new QRCode($options))->render($payload);
    $blob    = base64_decode(substr($dataUri, strlen('data:image/png;base64,')));

    $result = (new QRCodeReader())->readFromBlob($blob);

    $this->assertSame($payload, $result->data);
}

This catches the real regressions: quiet zone removed, scale too small, ECC level silently changed, payload truncated.

Pattern 5 — reading uploaded QR codes

use chillerlan\QRCode\QRCodeReader;

$result = (new QRCodeReader())->readFromFile($request->file('qr')->getRealPath());
$decoded = $result->data;

Guard this like any image upload: validate MIME/size first, wrap in try/catch (the detector throws on undecodable input), and note the reader requires ext-gd or ext-imagick — a separate requirement from the generator, which only needs ext-gd for raster output.

Pattern 6 — branded / styled output

Tuning is done through QROptions, not by post-processing pixels:

$options = new QROptions([
    'outputInterface'     => QRGdImagePNG::class,
    'drawCircularModules' => true,
    'circleRadius'        => 0.45,
    'drawLightModules'    => false,
    'keepAsSquare'        => [...],     // finder patterns etc. by module value
    'moduleValues'        => [
        // map module bitmask -> [R,G,B,A]
    ],
    'imageTransparent'    => false,
]);

For logos/watermarks, overlay after rendering (GD/Imagick or intervention/image) and raise ECC to H first — you're destroying modules, so you need the redundancy.

Pattern 7 — custom output modules

When you need HTML-with-classes, inline JSON for a frontend renderer, or a bespoke template:

class QrTailwindHtml extends QROutputAbstract
{
    public static function moduleValueIsValid(mixed $value): bool { /* ... */ }

    public function dump(mixed $matrix): string { /* ... */ }
}

Implement/extend QROutputAbstract, then set $options->outputInterface = QrTailwindHtml::class. This is the sanctioned extension point and is far more maintainable than string-replacing generated SVG.

Gotchas and Tips

$outputType no longer exists in v6. The constants on QROutputInterface were removed; you now pass a class-string via $options->outputInterface (default QRMarkupSVG::class). Any tutorial using QROutputInterface::MARKUP_SVG is pre-v6.

dev-main is not a release. The main branch targets v7 and requires PHP 8.4. If a snippet from the repo's main examples fails, you almost certainly installed ^6.0. Always match the examples branch to the installed version.

The getters are being retired. Since 6.0.1, QRMatrix::getMatrix(), getSize(), getVersion(), getEccLevel(), and ~18 others are deprecated in favour of public properties via PHP 8.4 asymmetric visibility ($matrix->matrix, $matrix->moduleCount, etc.). QRMatrix gets a magic getter as an upgrade path; the rest do not. Stop calling getMatrix() in new code.

getMatrix() vs getBooleanMatrix(). In v6 the $boolean parameter was removed from getMatrix() and a separate getBooleanMatrix() was extracted for type clarity. Code passing true here will break.

Base64 doubles your payload. outputBase64 => true turns a ~2KB SVG into a ~3KB data URI. For QRGdImage*, data URIs are convenient for <img>; for SVG served over a route, set it to false and emit image/svg+xml.

Never echo raw binary in HTML. PNG/WebP output is binary. Either set outputBase64 or write to a file and serve it.

Quiet zone and scale cause 90% of "it won't scan." Keep addQuietzone => true with quietzoneSize >= 4. For raster output, scale is pixels-per-module — 8–10 is a safe floor; a 500-character payload pushes you to a high version, more modules, and a physically denser code, so scale up proportionally. A code that renders fine at 200px on desktop can be unscannable in print.

ECC H costs capacity. Higher error correction means fewer data modules. If you're near the version ceiling with ECC_H, either shorten the payload (use a short URL redirect, not the full URL with tracking params) or drop to M/Q. Do the ECC choice before the logo overlay decision — those two constraints fight each other.

QROptions rejects unknown keys. It's a settings container with validation; typos in option names throw rather than being silently ignored. That's good, but it means copy-pasted option names from v5 docs ($outputType, $imageBase64, etc.) fail loudly at runtime.

Charset and encoding modes. Default is 8-bit binary/UTF-8, which handles most URLs and vCards. Kanji (Shift-JIS) and hanzi (GB2312/GB18030) use dedicated 13-bit double-byte modes, and ECI is supported for binary mode. Don't hand-roll Japanese text into a UTF-8 payload and expect mode optimisation — mixed-mode selection happens based on what you feed it, so feed it correctly encoded input.

The QROutputAbstract speed-cache properties are going away. $excludeFromConnect, $keepAsSquare, $connectPaths, $eol, $drawCircularModules, etc. will be removed from that class in v7 (replaced by property hooks / local re-addition). If you've subclassed QROutputAbstract and depend on them, read those from $this->options instead.

Optional dependencies are truly optional — until they aren't. QRPdf needs setasign/fpdf; QRImagick needs ext-imagick and ext-fileinfo; QRGdImage* needs ext-gd. Feature-detect and fall back to SVG rather than letting a production request fatal:

$interface = extension_loaded('gd') ? QRGdImagePNG::class : QRMarkupSVG::class;

Reader robustness. The ZXing port struggles with low-contrast, inverted, or transparent images. The reader has its own options trait (QRCodeReaderOptionsTrait) separate from QROptions — use it to force Imagick, grayscale, invert, or increase contrast before giving up on an upload. Also cap image dimensions before decoding; a 6000×6000 upload will happily eat your memory.

Debugging output shape. When an output looks wrong, dump $result[0..200] and check the prefix: data:image/svg+xml;base64, vs <?xml vs <!DOCTYPE html> vs { tells you instantly whether outputInterface took effect. Most "wrong format" bugs are an options object that got overwritten somewhere upstream.

Licensing. Apache-2.0, with the decoder being a PHP port of ZXing — keep the NOTICE file if you redistribute. Also note "QR Code" is a Denso Wave trademark; don't brand your feature as if you own the term.

Generate in a queue for bulk. Rendering tens of thousands of codes (event badges, ticket batches) belongs in a job with a chunked loop, not a web request. The library is fast, but you're still doing Reed–Solomon math in userland PHP.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json