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

Quar Laravel Package

tuncaybahadir/quar

Qr Code Generator for Laravel (PHP 8.2+, Laravel 10–13) built on BaconQrCode and simple-qrcode. Use the Quar facade to generate QR codes, set size, and force versions 1–40; output renders directly in Blade templates.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm discovery. The package has no documented publish step, config file, or manual provider registration — composer require tuncaybahadir/quar is the whole install, and the facade resolves via Laravel package auto-discovery.

composer require tuncaybahadir/quar

Where to look first. The README is effectively the API reference: every example maps to one chainable method (size(), eye(), style(), color(), backgroundColor(), gradient(), margin(), version(), format(), merge(), withText(), configureText()). Read it in the order it is written — simple usage → size → styling → saving → logo — because each section builds on the previous chain. The docs/images/example-*.png screenshots are the fastest way to decide on visual options without generating anything.

First use case: an inline SVG QR in a Blade view. SVG is the right default for anything web-facing — no disk I/O, no storage link, infinitely crisp, and cheap to cache as a string.

use tbQuar\Facades\Quar;

// Controller
public function show(Invoice $invoice)
{
    return view('invoice.show', [
        'qr' => Quar::size(200)
            ->margin(2)
            ->eye('rounded')
            ->style('round', 0.9)
            ->generate($invoice->publicUrl()),
    ]);
}
<div class="qr">
    {!! $qr !!}   {{-- see Gotchas: {{ }} may escape the SVG --}}
</div>

Sanity-check the loop in three steps: (1) render the raw string in routes/web.php to confirm a QR appears, (2) move it into a Blade view, (3) scan it with two different phone scanner apps before you ship. Scannability is the only thing that matters; styling can look perfect and still fail.

Implementation Patterns

Pick output format deliberately. generate() with no path returns a string you render or base64-encode; generate($data, $path) writes a file. Choose per surface:

Surface Pattern
Web page, email HTML default SVG, render inline
Download / API format('png') → binary string → response()
Email attachment, PDF, invoice archive format('png') → write to disk, reference by URL
Logo overlay format('png') only — SVG merge is fragile

Streaming a PNG download (no temp file, no storage link):

$png = Quar::format('png')->size(600)->margin(2)->generate($url);

return response($png)
    ->header('Content-Type', 'image/png')
    ->header('Content-Disposition', 'attachment; filename="qr.png"');

Persisted PNGs for tickets and invoices. The README's md5(random_int(...).date(...)) naming is demo code — it guarantees a cache miss and orphans files. Derive the filename from the payload instead, so repeat requests reuse the same asset:

$relative = 'qr-code-images/'.hash('xxh128', $payload).'.png';
$absolute = Storage::disk('public')->path($relative);

if (! is_file($absolute)) {
    Quar::format('png')
        ->size(400)
        ->margin(2)
        ->setPngCompression(50)
        ->generate($payload, $absolute);
}

$url = Storage::disk('public')->url($relative); // needs `php artisan storage:link`

Wrap the house style once. Controllers should not repeat seven-method chains; the chain is your design system, so put it in a small service or a config/quar.php preset of your own and call one method. This also gives you a single swap point if you ever replace the package.

class QrCode
{
    public static function svg(string $payload, int $size = 240): string
    {
        return (string) Quar::size($size)
            ->margin(2)
            ->errorCorrectionSafe() // your own guard, see Gotchas
            ->eye(config('quar.eye', 'rounded'))
            ->style(config('quar.style', 'round'), 0.9)
            ->color(...config('quar.fg'))
            ->backgroundColor(...config('quar.bg'))
            ->generate($payload);
    }
}

Logo overlay, corrected. The README's conditional example is subtly broken (see Gotchas). Pass a callback to when() and always set margin(1):

$logo = config('app.company.logo');

$png = Quar::format('png')
    ->margin(1)                                   // required for merge
    ->size(400)
    ->when($logo && file_exists($logo),
        fn ($q) => $q->merge($logo, 0.25, absolute: true),
        fn ($q) => $q)
    ->generate($url);

Captions. withText() draws around the code — it is not encoded into it. Keep it short (a label or entity name) and configure legibility explicitly:

Quar::size(300)
    ->withText('Invoice #1042')
    ->configureText(fn ($t) => $t
        ->setFontSize(12)
        ->setPosition('bottom')
        ->setTextColor('#111827')
        ->setPadding(20))
    ->generate($url);

Cache aggressively on hot paths. QR generation is cheap but not free, and ticket/checkout endpoints tend to be spiky. Cache::remember('qr:'.hash('xxh128', $payload.$styleVariant), now()->addDay(), fn () => QrCode::svg($payload)) removes the package from the request path entirely.

Testing. Assert on shape, not bytes: $this->assertStringContainsString('<svg', QrCode::svg('x')); and, for files, Storage::disk('public')->assertExists($relative). Avoid snapshotting rendered pixels — they will churn on every dependency bump and give you nothing.

Gotchas and Tips

when() with no callback does not gate anything. The README's logo example uses ->when($flag)->merge(...) — Laravel's Conditionable::when() returns $this when no callback is given, so merge() runs regardless, and a missing file throws. Always pass the callback as shown above.

merge() requires margin(1) or more. Documented explicitly as a BaconQrCode bug; at margin(0) the overlay corrupts the code. Also verify the merged logo still scans — error-correction level is not exposed anywhere in the README, so you cannot raise ECC to buy back tolerance. Keep logos ≤25% of area and test with real scanners.

State may leak between calls in one request. The facade resolves a single instance, so options accumulate. Build the full chain every time rather than relying on a partially-configured builder, and be suspicious of a second QR in the same request picking up the first one's colors. This matters in queue workers and Blade loops especially.

Blade escaping. {{ $qr }} will render &lt;svg&gt; unless the returned object is Htmlable/HtmlString. If you see literal markup, switch to {!! $qr !!} — and only do that because QR output is not user-authored.

Sanitize the value you pass to withText(). The caption is embedded into the SVG document as text, so unescaped user input is an SVG-injection vector. e() it or constrain it to a whitelist.

Fonts for non-Latin text and emoji. The README's 🇹🇷 example works only because a suitable font was available; without setFont('/path/to/font.ttf'), flags, CJK, and emoji render as tofu boxes. Bundle the font in your repo rather than referencing a system path.

Version is not an output size. version() sets the module grid (1–40, up to 177×177); size() sets pixels. Forcing version(40) at size(500) yields ~2.8px modules — unreadable in print. Pair high versions with proportionally larger size() values, and keep modules ≥4px. Remember a forced version is a minimum-capacity grid: forcing 13 on short content just gives you a bigger, chunkier code.

Catch WriterException. A version too small for the payload throws. If you let users choose a version, wrap the call and fall back to auto-selection (just omit version()):

try {
    $qr = Quar::version($v)->generate($payload);
} catch (\BaconQrCode\Exception\WriterException) {
    $qr = Quar::generate($payload);   // smallest fitting version
}

Argument signatures are inconsistent. eyeColor(0, r, g, b) takes an int index and accepts 3 or 6 components; eyeColorFromHex('0', ...) takes a string index. gradient() types are documented lowercase but one README example passes 'VERTICAL' uppercase — normalize to lowercase in your own wrapper and don't assume case-insensitivity.

Smoothness ceilings differ. 0.9 max for square/dot/round; 0.5 max for star/vertigo. Exceeding it is silently clamped or visually wrong, and the difference is only visible at large sizes.

Contrast beats style. Dark modules on a light background, generous quiet zone (margin(2)), and no gradient between two light colors. Gradients and light-on-dark inversions are the most common cause of "it scans on my phone but not at the till." Test printed output at the real physical size, not just on screen.

generate($data, $path) return value is ambiguous in the README — one logo example base64-encodes the result after also passing a path. Don't rely on it; either use the path variant and file_get_contents(), or skip the path and use the returned bytes directly.

Maturity risk. Zero dependents and a very young release history (last release dated 2026-10-24) mean you are the integration tester. Pin it in composer.json ("tuncaybahadir/quar": "^1.8"), keep the facade behind your own QrCode helper class, and run your test suite after any Laravel or PHP minor upgrade — the package advertises Laravel 10–13 and PHP 8.2–8.5, which is a wide matrix to have actually exercised.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console