nowo-tech/qr-code-bundle
A PHP QR code bundle that makes it easy to generate and embed QR codes in your applications. Configure output size, format, and error correction, then render codes for URLs, text, or other payloads through a simple, framework-friendly API.
First, the mismatch: this is a Symfony bundle, not a Laravel package. It has no ServiceProvider, no config/*.php publish tag, and no facade. If you are working in a Laravel codebase, "day-to-day use" realistically means one of three things:
Confirm which before installing anything.
Minimal Symfony setup
composer require nowo-tech/qr-code-bundle
config/bundles.php (NowoTech\QrCodeBundle\QrCodeBundle::class => ['all' => true] — verify the exact FQCN in the repo).config/packages/ with at least one named profile. Profiles are the core abstraction: each profile carries its own size, margin, error-correction, and validation settings.First use case: a ticket or invoice page. Generate the PNG data URI in the controller, pass it to Twig, and render:
<img src="{{ qrDataUri }}" alt="Ticket QR" width="240" height="240">
No temp files, no storage bucket, no signed-URL plumbing — that is the entire value proposition of the data-URI approach.
Where to look first (in this order):
README.md — profile config shape and the public service ID.DependencyInjection/Configuration.php — the authoritative list of profile options and defaults.Compatibility baseline (from the repo's own contributor rules): PHP >= 8.1, Symfony 6/7/8. That is current and reasonable.
The named-profile design is the intended integration point. Do not scatter size/ECC arguments across call sites; declare profiles once and reference them by name.
ticket — small payload, medium ECC, tight margin (screen display).invoice — larger payload, high ECC, generous quiet zone (print/PDF).marketing_url — URL-only payloads with validation enabled.This keeps call sites to a single string argument and makes tuning a config change rather than a code change.
public function show(Ticket $ticket, QrGeneratorInterface $qr): Response
{
$dataUri = $qr->generate('ticket', $ticket->publicUrl());
return $this->render('ticket/show.html.twig', ['qrDataUri' => $dataUri]);
}
Data URIs are portable by design — the same string works in:
<img src> (web).data: URIs. For email, generate a real PNG and attach or host it instead. Test before shipping.{!! $qr !!} — and note the {!! !!} is safe here only because the value is a generated constant-prefix string, never user input.The bundle's optional URL validation is the pattern to lean on: validate user-supplied links at the boundary, then encode. Treat the validator as an input guard, not a sanitizer. If you accept a URL from a form, validate it and confirm the scheme allowlist matches your product needs before it ever reaches the QR generator.
QR generation is CPU work plus base64 encoding. For codes that rarely change (venue links, product pages, Wi-Fi at a fixed site), cache the data URI keyed by a hash of (profile, payload):
$key = 'qr:'.hash('xxh3', $profile.'|'.$payload);
return Cache::remember($key, now()->addDay(), fn () => $qr->generate($profile, $payload));
Do not cache per-user one-time codes — you will leak them across users.
Looping is fine for tens of codes. For hundreds, watch memory: each data URI is roughly 4/3 × PNG size, and holding a thousand in an array is easy to do accidentally. Stream or write to disk in chunks.
Assert the contract, not the pixels:
$this->assertStringStartsWith('data:image/png;base64,', $uri);
$bytes = base64_decode(substr($uri, strlen('data:image/png;base64,')));
$this->assertStringStartsWith("\x89PNG\r\n\x1a\n", $bytes);
That catches config regressions and format drift without brittle image snapshots.
Wrap the framework-agnostic core in a small class, bind it as a singleton in a service provider, and expose a config file mirroring the profile array. Do not attempt to boot the Symfony bundle inside Laravel — the DI and config-tree coupling will fight you. This is the highest-effort option; price it honestly against just calling the underlying QR library.
The dependency risk is the headline. Zero stars, zero dependents, and a release date of 2026-10-24 — a future date. Either the metadata is wrong or the release tagging is unusual. Verify the tag actually exists on Packagist and in the repo before depending on it. Pin an exact version and commit your lockfile.
"URL validation" is a narrow guarantee. http/https only means it will reject mailto:, tel:, sms:, geo:, WIFI:, and BEGIN:vCard payloads — all of which are legitimate, common QR use cases. If your product needs any of those, validation must be disabled for that profile, and you take on the scheme-checking yourself. Conversely, do not assume the validator blocks every malicious payload; it checks a scheme, not content.
Data URIs have size costs. base64 adds ~33% overhead, and high error-correction on a long payload compounds it. A data URI can easily exceed a VARCHAR(255) column — never store the URI in a column sized for a URL. Store the payload and regenerate, or use TEXT.
PNG-only is a real constraint. No SVG means no infinite scaling. For print, generate at a high pixel size rather than upscaling a small image — upscaled QR codes scan poorly. Also confirm the output respects the quiet zone; a margin of 0 will fail on many scanners regardless of ECC level.
Error correction is a trade-off, not a safety net. Higher ECC tolerates more damage but shrinks usable payload capacity. If you hit capacity, raising ECC is the wrong fix — shorten the payload or reduce ECC.
Debugging a "won't scan" report: dump the decoded PNG to a temp file, open it, and check (a) quiet zone present, (b) dark-on-light contrast, (c) payload actually what you intended, (d) no HTML entity escaping applied to the data URI in the template. That last one is the most common silent failure — Twig autoescaping on a data: URI can corrupt the base64.
Config merge behavior. Symfony config trees merge arrays in ways that surprise people, especially when profiles are defined across config/packages/*.yaml and environment-specific overrides. Read Configuration.php for which keys cascade and which replace wholesale. Verify with debug:config rather than assuming.
Email is the trap. Data URIs render in browsers and in many modern clients, but desktop Outlook historically blocks them. If QR-in-email is a requirement, plan for an attached or hosted PNG from day one — retrofitting is painful.
Extension points are thin by nature. For a bundle this small, customization means config, not interfaces. Before forking, check whether the generator is a final class or an interface — if final, a decorator will not work and you are looking at a wrapper service or a fork.
No community backstop. With zero dependents, you are likely the first person to hit any given bug. Budget for reading the source when something misbehaves, and consider whether a well-maintained standalone QR library (with the same data-URI output) is a lower-risk choice for a production Laravel or Symfony app. The bundle's value is the config-driven profile abstraction and the validator; if you do not need both, the abstraction may not pay for the dependency.
How can I help you explore Laravel packages today?