linkxtr/laravel-qrcode
linkxtr/laravel-qrcode is a Laravel package for generating QR codes with minimal setup. Build QR codes from text or URLs using a simple API, then output them for display or download in your Laravel applications. It offers a convenient way to add scannable codes to links, tickets, menus, and more.
Verify before you commit. With ~41 stars and zero dependents, treat this as a thin wrapper you are evaluating, not a de-facto standard. Budget 15 minutes to read the whole src/ tree — a package this size is usually a few hundred lines around an underlying QR library (endroid/qr-code or bacon/bacon-qr-code).
composer require linkxtr/laravel-qrcode
composer show linkxtr/laravel-qrcode # note the QR backend + version constraints
composer show --tree linkxtr/laravel-qrcode
The transitive dependency matters more than the wrapper: endroid/qr-code has had breaking majors, so check the constraint against your PHP version.
vendor/linkxtr/laravel-qrcode/composer.json for the extra.laravel.providers block. Then publish without guessing tags:php artisan vendor:publish # pick the linkxtr entry from the interactive list
Confirm the public API surface. Look for a facade alias in the provider's register()/aliases, the config file (config/qrcode.php or similar), and any Blade directives or routes registered in boot(). That's your entire contract.
First use case — a signed URL QR on a route:
use Illuminate\Support\Facades\Route;
Route::get('/qr/{token}', function (string $token) {
$payload = URL::signedRoute('checkin', ['token' => $token]);
return response(
QrCodeFacade::format('svg')->size(400)->generate($payload),
200,
['Content-Type' => 'image/svg+xml']
);
});
Start with SVG. It needs no gd/imagick extension, scales losslessly for print, and renders identically in email and PDF pipelines.
Wrap it, don't sprinkle it. Because you may need to swap this package out later, put every call behind one small class:
final class QrRenderer
{
public function svg(string $payload, int $size = 400): string
{
return QrCodeFacade::format('svg')->size($size)->generate($payload);
}
public function dataUri(string $payload, int $size = 400): string
{
return 'data:image/png;base64,' . base64_encode($this->png($payload, $size));
}
}
If the package dies, you rewrite one file. This is the single highest-value decision you make with a 41-star dependency.
Inline vs. download vs. data URI. Three distinct consumers, three distinct handlers:
response($bytes)->header('Content-Type', ...) plus Content-Disposition when you actually want a file.public_path(); containerized and read-only filesystems will bite you. If you must persist, use Storage::disk().Cache aggressively, keyed by content. QR generation is cheap but not free, and the same payload+options recurs constantly (every event badge, every invoice). Key on a hash of the full option set:
$key = 'qr:' . hash('xxh128', $payload . '|svg|400|M');
$svg = Cache::remember($key, now()->addDay(), fn () => $this->renderer->svg($payload));
Queue bulk generation. Never loop over 500 attendees inside a request — memory climbs and the user stares at a spinner. Dispatch a job that writes SVGs to storage and emails a zip or links.
Route-based generation for dynamic codes. A GET route with a signed/tenant-scoped parameter is the cleanest pattern for codes printed on physical media, because the payload stays stable while the route does the auth. Prefer redirecting scanners to a signed route over embedding raw PII.
Blade component for consistency. Wrap sizing, margin, and alt text once so marketing pages don't each invent their own <img>:
<img src="{{ $qr }}" alt="Scan to check in" width="{{ $size }}" height="{{ $size }}">
Testing. Assert on content type, non-emptiness, and (for SVG) that the string starts with <svg. Do not snapshot binary PNGs — they differ across GD/Imagick versions and platforms and will fail CI forever.
Bus factor is the real risk. Zero dependents means no community pressure to patch it. Pin the exact version in composer.json, watch composer outdated in CI, and keep the adapter class above so replacement is a one-file change.
Verify the release metadata yourself. The listed last release date looks anomalous — check the actual tag history on the repo and the composer.json constraints rather than trusting a single number. If the last real commit predates Laravel 11/12, test on your target version explicitly.
Extension dependencies. PNG requires gd or imagick; SVG does not. If you can't guarantee the extension in CI or on your host, standardize on SVG and make it a composer platform requirement note in your README.
Config caching. After publishing, any config edits are invisible until php artisan config:cache is re-run. If defaults look ignored in production but work locally, that's why. Also check that the package uses mergeConfigFrom — some small packages read env() directly in the provider, which breaks under config:cache.
Output inside an already-started response. Emitting QR bytes with echo inside a controller that also returns JSON corrupts both. Always return a Response. If you see leading garbage characters or a truncated image, look for a stray echo/print or a BOM in a published config file.
Size and payload density. Big size values allocate big buffers; cap user-supplied sizes. More importantly, long payloads push the QR to a higher version and denser modules, which makes scanning unreliable at small print sizes. Keep payloads to a URL or compact token — never raw JSON blobs.
Error correction level. Raise it (H) only when you're overlaying a logo; otherwise M or Q keeps the module count down and scans faster. Check whether the package's config exposes this or whether you're stuck with the backend's default.
Non-ASCII payloads. UTF-8 content requires ECI/encoding support in the underlying library. Test with a real accented or CJK string; some wrappers silently produce unscannable output or throw deep inside the backend. Prefer percent-encoded URLs anyway.
Caching headers vs. freshness. If you serve QRs from a public route, add Cache-Control/ETag — but remember a cached QR pointing at a rotated token is a support ticket. Version the URL when the underlying payload changes.
Extension points to look for. A config file for defaults (format, size, margin, ECC), a bound interface you can rebind in a service provider, and Macroable on the facade. Those three are what separate a wrapper you can live with from one you have to fork.
How can I help you explore Laravel packages today?