wolfcode/bacon-qr-code
Laravel wrapper for BaconQrCode that makes generating QR codes simple. Create customizable QR codes as SVG or PNG, save them to disk, or return them directly from controllers with a clean, expressive API. Supports size, margin, error correction, and colors.
composer require wolfcode/bacon-qr-code
This is a fork of the BaconQrCode library, so the namespace is still BaconQrCode\. There is no service provider — it is a plain PHP library you instantiate yourself. Requirements:
ext-imagick only if you want PNG output. SVG and EPS backends are pure PHP.Where to look first:
src/Renderer/ImageRenderer.php — the main renderersrc/Renderer/Image/* — available backends (SvgImageBackEnd, ImagickImageBackEnd, EpsImageBackEnd)src/Renderer/RendererStyle/RendererStyle.php — sizing/margin/ECC controlssrc/Writer.php — the entry point (writeString, writeFile)First use case — return a QR as SVG from a route:
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
Route::get('/qr', function () {
$renderer = new ImageRenderer(
new RendererStyle(300),
new SvgImageBackEnd()
);
$svg = (new Writer($renderer))->writeString('https://example.com');
return response($svg, 200, ['Content-Type' => 'image/svg+xml']);
});
If that route renders, you have everything you need to move on to the patterns below.
Wrap it in a small service so you never repeat the boilerplate. Bind it in a provider:
// app/Services/QrCodeService.php
namespace App\Services;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
class QrCodeService
{
public function svg(string $data, int $size = 300, int $margin = 0): string
{
$renderer = new ImageRenderer(
new RendererStyle($size, $margin),
new SvgImageBackEnd()
);
return (new Writer($renderer))->writeString($data);
}
public function png(string $data, int $size = 300, int $margin = 0): string
{
$renderer = new ImageRenderer(
new RendererStyle($size, $margin),
new ImagickImageBackEnd('png')
);
return (new Writer($renderer))->writeString($data);
}
}
// app/Providers/AppServiceProvider.php
$this->app->singleton(\App\Services\QrCodeService::class);
Inline image in Blade (SVG as data URI):
<img src="data:image/svg+xml;base64,{{ base64_encode(app(QrCodeService::class)->svg($url)) }}" alt="QR">
Base64 encoding avoids the XML declaration and any < characters leaking into your HTML.
Downloadable PNG:
public function download(Request $request, QrCodeService $qr)
{
$png = $qr->png($request->input('url'), 600, 0);
return response($png, 200, [
'Content-Type' => 'image/png',
'Content-Disposition' => 'attachment; filename="qr.png"',
]);
}
Write straight to storage (skip the string round-trip for large sizes):
$writer = new Writer(
new ImageRenderer(new RendererStyle(1024), new ImagickImageBackEnd('png'))
);
$writer->writeFile($url, storage_path('app/public/qr.png'));
Higher error correction for printed/sticker QR codes — pass an ErrorCorrectionLevel to RendererStyle:
use BaconQrCode\Common\ErrorCorrectionLevel;
new RendererStyle($size, $margin, null, null, ErrorCorrectionLevel::H())
Use L for dense URLs you want to keep small, H for logos/labels where part of the code will be obscured.
Cache the rendered bytes — generation is cheap but not free, and SVG output is highly compressible:
return Cache::remember("qr:{$hash}", now()->addDay(), fn () => $qr->svg($url));
Route model integration — treat a QR as a resource endpoint so you can scope authorization and logging:
Route::get('/events/{event}/qr.svg', [QrController::class, 'show'])->name('events.qr');
This lets you use Gate/Policy on the underlying model before generating the code, which is the correct pattern for anything short-lived or tokenized.
PNG requires Imagick, not GD. ImagickImageBackEnd will throw at runtime if ext-imagick is missing. Check extension_loaded('imagick') in a deployment health check or fall back to SVG. Many shared hosts disable Imagick; if so, ship SVG and let CSS scale it.
The package name and namespace differ. You install wolfcode/bacon-qr-code, but use BaconQrCode\.... If anything else in the tree pulls in bacon/bacon-qr-code (e.g. simplesoftwareio/simple-qrcode), composer may install both and you'll get confusing "class not found" or duplicate-class behaviour. Run composer why bacon/bacon-qr-code if you see oddities, and prefer one dependency chain.
It's a fork with 0 stars and no dependents. Pin the version in composer.json ("wolfcode/bacon-qr-code": "2.*") and treat upgrades as a manual review step — there's no ecosystem signal that will warn you about regressions. If you only need SVG, consider evaluating upstream bacon/bacon-qr-code directly, since it has the maintenance and security history.
RendererStyle size is a request, not a guarantee. The renderer adjusts to a whole number of modules. Asking for size 100 with a long URL and ECC level H produces a code with tiny, unreadable modules. Validate: keep size ≥ 200 for content longer than ~50 chars, and don't let users pick arbitrary sizes without a floor.
Guard against unbounded input. A user-supplied URL plus a user-supplied size=10000 is a trivial memory DoS with Imagick. Clamp both:
$size = min(max((int) $request->integer('size', 300), 100), 1024);
Long data throws. Oversized payloads exceed QR capacity and throw BaconQrCode\Exception\RuntimeException. Catch it and return a 422 rather than surfacing a 500:
try {
$svg = $qr->svg($data);
} catch (\BaconQrCode\Exception\RuntimeException $e) {
abort(422, 'Content too long for a QR code.');
}
Use RendererStyle($size, $margin) deliberately. The default margin is 4 modules (the QR spec's "quiet zone"). Setting it to 0 makes codes look tight in UIs but can hurt scan reliability on printed media — keep at least 1–2 for anything physical.
SVG output starts with <?xml ... ?>. If you inject it directly into HTML (not via <img>), browsers may or may not honor it depending on the parser path. Always serve it with Content-Type: image/svg+xml or as a data URI, never as a fragment inside a text/html document.
Encoding/ECI. writeString($data) assumes a default encoding. For non-ASCII payloads (emoji, CJK), pass the encoding argument ($writer->writeString($data, 'UTF-8')) so scanners decode consistently, and verify with a physical scanner — automated tests won't catch a bad ECI flag.
No Blade component ships with the package. If you want <x-qr-code :data="$url" />, build it yourself around the service above; don't expect the fork to ship one.
Testing tip. Don't assert on rendered pixels. Assert on the string prefix (<svg for SVG, \x89PNG for PNG) and on throw behaviour for oversized input. If you need to verify scannability, decode with a separate library in an integration test rather than eyeballing snapshots.
How can I help you explore Laravel packages today?