2amigos/qrcode-library
A PHP QR code generation library from 2amigos. Build customizable QR codes with configurable size, colors, margins, and error correction, then output them in common image or vector formats. Its simple object-oriented API works well in Laravel and other PHP apps.
Requirements first. 4.x requires PHP 8.3–8.5 and ext-gd. Confirm both before composer update; on 8.2 or on a Docker image built without GD (very common on Alpine/slim images) the install will fail or fall back to a broken renderer. Verify with php -m | grep -i gd and php -r 'var_dump(function_exists("imagepng"));'.
Upgrade path. Read UPGRADE-4.0.md before touching composer.json. The two things that break a Laravel app are namespace moves and the gradient API — see Gotchas.
Install and locate the integration.
composer require da/qr-code:^4.0
Then look in this order:
src/Bridge/Laravel/ — the service provider, facade, and QrCodeFactory. This is your entry point; the Laravel* class-name prefixes are gone, so re-check any use statements you copied from an old blog post.src/Renderer/ — GdImageBackEnd is now the default PNG/JPG writer; ImagickImageBackEnd is opt-in.UPGRADE-4.0.md and CHANGELOG.md — the only reliable source for the renamed classes in your version.First use case. Generate a PNG and return it from a controller without writing to disk:
use Da\QrCode\QrCode;
return response(
(new QrCode('https://example.com/invite/abc123'))->writeString(),
200,
['Content-Type' => 'image/png']
);
If the framework binding is available, prefer resolving the bridge factory so config and defaults apply. A standalone new QrCodeFactory() now works too (fixed in 4.0.0), which is handy in artisan commands and tests.
Inject the factory, don't new the QrCode inline. Resolve Da\QrCode\Bridge\Laravel\QrCodeFactory from the container (constructor injection or the facade) so a single place controls size, margin, encoding, and error-correction level. Tests can then swap the binding.
Three delivery shapes cover most features:
// Inline response
return response($qr->writeString(), 200)->header('Content-Type', 'image/png');
// Embeddable data URI (emails, PDFs via dompdf/snappy)
$src = $qr->writeDataUri();
// Persist and serve from a CDN
Storage::disk('public')->put("qr/{$user->id}.png", $qr->writeString());
Contact and event QRs. vCard PHOTO (base64, data-URI, or local path) and MeCard ORG make the package viable for badge/contact-card features without hand-rolling the payload format. Build the vCard through the package's builder and attach a resized avatar; keep the photo small (a few KB) or the QR becomes too dense to scan at business-card size.
Styling is where the gradient changes land. Use StyleManager for gradients and compare against GradientType cases, not strings — QrCode::getGradientType() now returns the enum. Radial and inverse-diagonal gradients render correctly in 4.x; if you had a workaround for the solid-black bug, delete it.
Batch generation belongs in a job. Generating dozens of QRs in a request will blow past memory limits on the GD backend at large sizes. Chunk them in a queued job, write to storage, and cache the path. Because output is deterministic for identical input and style, cache aggressively keyed on a hash of (content + style config).
Cross-service reuse. If you share QR generation with a Yii3/Mezzio/Slim service, the PSR-15 Bridge\Psr\QrCodeAction lets you lift the logic out of Laravel controllers. For a Laravel-only app it's noise — ignore it.
Testing. Assert Content-Type and non-empty body, and if you need more, decode the PNG dimensions via GD. Don't assert byte equality: GD 4.x output differs from the old ImageMagick bytes, and PHP patch versions can shift encoder output subtly. Use Storage::fake() for disk writes.
Namespace churn is the main upgrade hazard. Everything under Da\QrCode\Bridge\<Framework>\ was reorganized and the redundant Laravel prefixes were dropped. Grep the codebase for Bridge\Laravel\Laravel and for any facade alias in config/app.php — aliases pointing at old class names produce confusing "class not found" errors at boot, not at the call site.
ImageMagick is no longer the default. PNG/JPG now render through GdImageBackEnd out of the box. Two consequences:
gd-only host can still emit vector formats.ext-gd requirement is now hard. Add it to your Dockerfile (docker-php-ext-install gd with --with-freetype if you use labels) and to composer.json platform checks in CI.Gradient API rename. StyleManager::getGradientTye() → getGradientType(); the old name is a deprecated alias, so it still works but is on borrowed time. The return-type change to GradientType is the sharper edge: === 'radial' comparisons silently return false rather than erroring. Search for string comparisons and switch to enum cases.
Dependency bumps can conflict. bacon/bacon-qr-code ^3 and khanamiryan/qrcode-detector-decoder ^2 may collide with other packages pinning v2 of bacon. Run composer why bacon/bacon-qr-code before upgrading a large app; if a decoder dependency holds you back, upgrade that package first rather than fighting the resolver.
config() unbound. QrCodeFactory now works without Laravel's config repository, so artisan commands, package tests, and queue workers bootstrapped outside the HTTP kernel are fine. Still, don't rely on config being present in your own wrappers — read defaults through the factory, not config('qr-code.*'), in reusable code.
marc-mabe/php-enum is gone. Enum classes under Da\QrCode\Enums keep their constants, so GradientType::RADIAL-style usage is unchanged — but anything doing instanceof \MarcMabe\Enum\... or calling its helper methods will break. Same for serialization that relied on that library's __toString/jsonSerialize.
Debugging unreadable QRs. Almost always content density: long URLs, embedded photos, or excessive error-correction level. Drop EC level to M or L, shorten the payload with a redirect/short ID, and only then increase size. The 4.x troubleshooting guide addresses this directly; check it before filing an issue.
Extension point. Implement a custom renderer backend and inject it when you need SVG or a house style GD can't express — the writer constructor is the supported seam, and it's now the documented way to opt back into Imagick.
How can I help you explore Laravel packages today?