picqer/php-barcode-generator
A lightweight PHP barcode generator supporting EAN, UPC, Code 128, Code 39, ITF and more. Output barcodes as SVG, PNG, JPG or HTML via a simple API. No external dependencies; works standalone or with Laravel.
composer require picqer/php-barcode-generator
There is nothing else to install: no service provider, no config file, no facade, no published assets. It is plain PHP, so the "integration" is whatever you write around it. The whole surface area is two decisions: which type constant you pass and which renderer you instantiate.
use Picqer\Barcode\BarcodeGeneratorSVG;
$generator = new BarcodeGeneratorSVG();
$svg = $generator->getBarcode('SKU-00123', $generator::TYPE_CODE_128, 2, 60);
The fastest first use case is an order/packing-slip label. A throwaway route gets you there in five minutes:
// routes/web.php
Route::get('/barcodes/{code}.svg', function (string $code) {
$generator = new BarcodeGeneratorSVG();
$svg = $generator->getBarcode($code, $generator::TYPE_CODE_128, 2, 60);
return response($svg, 200, [
'Content-Type' => 'image/svg+xml',
'Cache-Control' => 'public, max-age=86400',
]);
})->where('code', '[A-Za-z0-9\-]+');
Where to look first, in this order:
TYPE_EAN_13, TYPE_CODE_128, TYPE_ITF_14, TYPE_CODABAR, TYPE_MSI, TYPE_PHARMA_CODE, …).Exceptions/ directory — four or five classes that tell you exactly what can go wrong.BarcodeGeneratorSVG / BarcodeGeneratorPNG / BarcodeGeneratorHTML / BarcodeGeneratorJPG classes (2.x) or the Picqer\Barcode\Renderers\* namespace (3.x). Check the tag you actually pulled before copying a snippet from a blog post.If you want this testable and swappable, bind a tiny wrapper in a service provider rather than new-ing renderers in controllers:
// app/Services/BarcodeService.php
class BarcodeService
{
public function svg(string $code, string $type = BarcodeGeneratorSVG::TYPE_CODE_128, int $width = 2, int $height = 60): string
{
return (new BarcodeGeneratorSVG())->getBarcode($code, $type, $width, $height);
}
public function png(string $code, string $type = BarcodeGeneratorPNG::TYPE_CODE_128, int $width = 3, int $height = 80): string
{
return (new BarcodeGeneratorPNG())->getBarcode($code, $type, $width, $height);
}
}
Renderer → output → transport. Pick the renderer by destination, not by taste:
| Destination | Renderer | Why |
|---|---|---|
| Blade / HTML page / email body | BarcodeGeneratorSVG |
Crisp, small, no GD, styleable |
| PDF via dompdf | BarcodeGeneratorPNG |
dompdf's SVG support is unreliable; rasterize at high width factor |
| PDF via Browsershot / wkhtmltopdf | BarcodeGeneratorSVG |
Chromium renders it perfectly and stays sharp |
Attachment / Storage::put |
BarcodeGeneratorPNG |
Bytes on disk, no markup |
| Interactive JS widget | BarcodeGeneratorDynamicHTML |
Draws client-side; useless server-side |
Single parameterised endpoint with a whitelist. One route serving PNG or SVG keeps caching and auth logic in one place, but never let the type come from raw user input — map it through a whitelist:
Route::get('/barcodes/{type}/{code}.{format}', BarcodeController::class)
->where('format', 'svg|png')
->where('code', '[A-Za-z0-9\-]+');
// BarcodeController
$types = [
'ean13' => BarcodeGeneratorPNG::TYPE_EAN_13,
'code128' => BarcodeGeneratorPNG::TYPE_CODE_128,
'itf14' => BarcodeGeneratorPNG::TYPE_ITF_14,
];
abort_unless(array_key_exists($type, $types), 404);
Emails and PDFs. In a Mailable, inline it so clients do not block a remote image:
$png = app(BarcodeService::class)->png($order->reference);
$message->embedData($png, 'barcode.png', 'image/png');
// or, for a real attachment:
$this->attachData($png, "label-{$order->id}.png", ['mime' => 'image/png']);
Bulk labels belong in a job. Generating 500 PNGs inside a request will time out. Fan out a queued job per order, write to a disk, and only then build the merged PDF:
Storage::disk('labels')->put("{$order->id}.png", $png);
Cache aggressively — the output is deterministic. The bytes depend only on (type, code, widthFactor, height, colour), so key on exactly that:
$key = 'barcode:'.md5(serialize([$type, $code, $width, $height]));
$png = Cache::remember($key, now()->addWeek(), fn () => $service->png($code, $type, $width, $height));
For product catalogues this turns thousands of regenerations into a few dozen.
Type choice is a domain decision, not a style one. EAN-13/UPC-A for retail GTINs, Code128 for internal SKUs and serials (compact, full ASCII), Code39 only where a legacy scanner demands it (wide, uppercase-only unless you use the extended variant), ITF-14 for cartons, Codabar for library/medical workflows. Encode the choice in your model so nobody has to remember.
Testing. Assert on signatures and dimensions rather than pixel-diffs: SVG output starts with <svg, PNG starts with \x89PNG, and a known EAN-13 produces the expected width for a given width factor. Add a feature test that invalid input returns 422 rather than 500.
Catch the exceptions or your invoices will 500. InvalidCharacterException, InvalidLengthException, and UnknownTypeException all extend BarcodeException. Any code path that renders a barcode from database content can be handed a bad SKU, so wrap generation and convert to a validation error or a placeholder image:
try {
$png = $service->png($product->sku);
} catch (BarcodeException $e) {
report($e);
abort(422, 'Invalid barcode value.');
}
Leading zeros and integer columns. An EAN-13 starting with 0 stored in an INT column silently becomes a 12-digit barcode. Store barcode values in CHAR/VARCHAR and cast to string before passing them in.
Length rules differ per symbology. EAN-13 accepts 12 digits (checksum appended for you) or 13 (must be valid); UPC-A accepts 11 or 12; EAN-8 accepts 7 or 8. If you generate the check digit yourself, verify it matches, or you will ship labels that scan to a different number than your database.
widthFactor is not pixels. It is the multiplier for the narrow bar module. Do not generate at default size and then stretch the <img> with CSS — resampling destroys the bar-width ratios and scanners reject it. Generate at the physical size you need. For 300 dpi print, width factor 3–4 and height 80–120 is a sane starting point.
The library does not add a quiet zone. White space either side of the symbol is mandatory for scanning and is absent from the SVG/PNG output. Add it yourself with padding in the container, or your warehouse scanners will be blamed for a bug in your CSS.
The human-readable text is on you. Most real labels want the number printed underneath. Render it in Blade/PDF below the barcode; the package will not do it.
PNG requires ext-gd. Add ext-gd to composer.json or your Dockerfile. SVG and HTML renderers need nothing.
The HTML renderer is heavy. It emits one <div> per module. A page of 100 product barcodes becomes tens of thousands of DOM nodes and a sluggish page. Use SVG for anything list-shaped; the HTML renderer is only reasonable for one-off inline snippets.
PNG background is transparent. Usually fine, but on dark email clients or when composited into a PDF it can look like a black box. If it matters, set a white background in the container or in your PDF template.
1D only. There is no QR or DataMatrix here. If you need a URL or GS1 Digital Link payload, pair it with a QR package (bacon/bacon-qr-code or simplesoftwareio/simple-qrcode) rather than forcing a 40-character string into Code128 on a 50 mm label — the width scales linearly with data length and it will not fit.
DynamicHtmlRenderer is a trap in emails and PDFs. It emits JavaScript; CSP headers will block it and dompdf will ignore it.
LGPL-3.0 in a commercial app. Using it as an unmodified Composer dependency from proprietary Laravel code is the intended use case. Do not copy its classes into app/ to "customise" them — that turns your fork into a derivative work you must publish. If you need different output, implement a renderer (3.x exposes a renderer interface) instead of editing one.
Version drift. If a tutorial's BarcodeGeneratorPNG::getBarcode(...) signature does not match your installed version, you are on 3.x and looking at 2.x docs. Pin the major version in composer.json and read the tags, not the blog posts.
How can I help you explore Laravel packages today?