davidscotttufts / php-barcode
Generate 59 barcode types (QR, Data Matrix, Aztec, MaxiCode, PDF417, EAN/UPC, GS1-128, GS1 DataBar, Code 128, postal codes and more) as SVG, PNG, PDF, EPS or text. Pure PHP, GS1 validation, styled QR codes.
Requires
- php: >=8.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
Suggests
- ext-gd: Required for PNG, JPEG, GIF and WebP output (SVG and text output need no extensions).
- ext-mbstring: Improves text centering for multi-byte input in the text renderer.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-02 12:57:43 UTC
README
Barcodes for PHP, from the humble Code 39 on a warehouse shelf to the QR code on a restaurant table. 59 barcode types, eight output formats and no required extensions. Every symbol is checked against a real decoder or the official test vectors.
use PhpBarcode\Barcode; echo Barcode::qrCode('https://example.com'); // inline SVG Barcode::ean13('400638133393')->save('product.png'); // check digit added for you Barcode::gs1128('(01)09506000134352(17)251231(10)ABC123')->save('label.pdf');
This started life in 2003 as a 150-line script. Version 2 is a rewrite. The old barcode.php?text=… URLs and the barcode() function still work. See Upgrading from 1.x.
Contents
- Installation
- The basics
- Which barcode should I use?
- Every supported barcode
- Output formats
- Size, text and colour
- Printing at an exact size
- Styled QR codes
- QR code payloads
- GS1: product, logistics and healthcare labels
- Symbology options
- Errors
- Command line
- HTTP endpoint
- Upgrading from 1.x
- Extending
- How it's tested
- Credits and licence
Installation
composer require davidscotttufts/php-barcode
PHP 8.2 or newer. That's the only hard requirement.
- SVG, PDF, EPS and text output work on any PHP install.
- PNG, JPEG, GIF and WebP need the GD extension (
ext-gd), which most hosts have. mbstringandiconvare used when present, for Kanji in QR codes and accented captions.
Not using Composer? Download the repository and require 'php-barcode/src/autoload.php';.
The basics
Pick a barcode, give it data, render it. A Barcode is immutable, so you can build one and render it several ways.
use PhpBarcode\Barcode; $barcode = Barcode::code128('SHIP-2026-0042'); $barcode->toSvg(); // string: an SVG document $barcode->toPng(); // string: PNG bytes $barcode->save('label.pdf'); // format chosen from the extension $barcode->toDataUri(); // "data:image/svg+xml;base64,…" for <img src> $barcode->output('png'); // sends headers + image to the browser echo $barcode; // inline <svg> for templates
There are shortcuts for the common types. Everything else goes through Barcode::of() with a type name, which ignores case, spaces and dashes ('EAN-13', 'ean_13' and 'ean13' all work):
Barcode::of('datamatrix', 'Hello'); Barcode::of('itf14', '15400141288763'); Barcode::of(BarcodeType::IntelligentMail, '01234567094987654321-01234567891');
Settings come from chainable with…() methods. Each one returns a new copy:
Barcode::code128('ABC-123') ->withBarHeight(80) // px ->withModuleWidth(3) // px per narrowest bar ->withColors('#1f2937', '#ffffff') ->save('abc.png');
Which barcode should I use?
If someone else is going to scan it, they usually decide. If it's up to you:
| You need to… | Use |
|---|---|
| Link a phone to a URL, Wi-Fi network, contact or payment | QR Code |
| Sell a product in a shop | EAN-13 (UPC-A in North America), from a GS1-issued GTIN |
| Label a book, magazine or sheet music | ISBN, ISSN, ISMN |
| Label cartons or pallets for a retailer's warehouse | GS1-128 or ITF-14 |
| Mark small parts or medical devices (UDI), or put a lot of data in a small square | Data Matrix (GS1 DataMatrix for healthcare) |
| Print boarding passes, tickets or ID cards | Aztec or PDF417 |
| Ship parcels with UPS | MaxiCode with the UPS structured message (new MaxiCode(postalCode: …, countryCode: …, serviceClass: …)) |
| Put an internal ID on an asset, file or shelf | Code 128: compact, full ASCII, read by every scanner |
| Talk to legacy equipment | Code 39, Interleaved 2 of 5 or Codabar, whichever it expects |
| Send mail in bulk | Your postal service's code: Intelligent Mail, Mailmark, KIX… |
Every supported barcode
Each of these was rendered from the line next to it. The images are in docs/images, and you can regenerate them with php examples/gallery.php.
Two-dimensional codes
Retail
GS1 logistics
General purpose and industrial
Pharmaceutical
| Type | Example | |
|---|---|---|
Pharmacodepharmacode |
Barcode::of('pharmacode', '1234') |
|
Code 32 (Italian pharmacode)code32 |
Barcode::of('code32', '12345678') |
|
PZN (German pharmacy)pzn |
Barcode::of('pzn', '1234562') |
Postal
Output formats
| Format | Method | Needs | Notes |
|---|---|---|---|
| SVG | toSvg(), save('x.svg') |
nothing | Best default: tiny, sharp at any size, supports every style option |
| PNG | toPng(), save('x.png') |
GD | Lossless; bar edges snap to whole pixels so they stay crisp |
| JPEG, GIF, WebP | render('jpeg') etc. |
GD | Only if something insists on these. JPEG blurs bars |
save('x.pdf') |
nothing | One vector page sized exactly to the barcode | |
| EPS | save('x.eps') |
nothing | For InDesign, Illustrator and other print layout tools |
| Text | toText() |
nothing | Unicode blocks for terminals and logs; QR codes stay scannable |
render() takes a format name, an OutputFormat case, or any renderer object, so you can pass a configured renderer:
use PhpBarcode\Renderer\JpegRenderer; use PhpBarcode\Renderer\SvgRenderer; $barcode->render(new JpegRenderer(quality: 95)); $barcode->render(new SvgRenderer(fontFamily: 'Inter, sans-serif', xmlDeclaration: false));
Size, text and colour
Sizes are in pixels unless you're printing. A module is the narrowest bar (or one square of a 2D code), and everything scales from it.
Barcode::code128('ABC-123') ->withModuleWidth(3) // default 2 for 1D codes, 4 for 2D codes ->withBarHeight(100) // default 60; 2D codes ignore it ->withQuietZone(10) // blank margin in modules; default is the spec minimum ->withText(true, fontSize: 16) ->withColors('#0f172a', '#f8fafc') ->vertical() // rotate 90° clockwise ->save('tall.svg');
-
Linear codes print their data underneath by default; 2D codes don't. Use
withoutText()orwithText()to change that. -
To print something else, such as a product name or "Scan to pay", use
withCaption(). It works on every type, 2D codes included:Barcode::qrCode('https://example.com/pay')->withCaption('Scan to pay'); Barcode::ean13('400638133393')->withCaption('Organic Oat Milk 1L');
-
EAN and UPC codes use the classic retail layout, with digits between the guard bars and add-ons labelled above.
-
A background of
'#ffffff00'(fully transparent) gives a transparent PNG or SVG. -
PNG output uses GD's built-in font. For a nicer one, pass a TrueType file through
RenderOptions::withFontFile('/path/to/font.ttf').
For full control, build a RenderOptions and pass it in:
use PhpBarcode\Renderer\RenderOptions; use PhpBarcode\Renderer\Orientation; $options = new RenderOptions( moduleWidth: 2, barHeight: 50, showText: false, orientation: Orientation::Horizontal, ); Barcode::of('code93', 'PART-77', $options)->save('part.svg');
Printing at an exact size
Barcode specs are written in millimetres. For example, an EAN-13 at 100% has 0.33 mm modules. RenderOptions::forPrint() takes millimetres and a DPI:
use PhpBarcode\Renderer\RenderOptions; $print = RenderOptions::forPrint(moduleMm: 0.33, barHeightMm: 22.85, dpi: 600); Barcode::of('ean13', '4006381333931', $print)->save('ean13.pdf'); // a 37.29 mm wide page Barcode::of('ean13', '4006381333931', $print)->save('ean13.svg'); // width="37.29mm" Barcode::of('ean13', '4006381333931', $print)->save('ean13.png'); // 600 DPI stored in the file
For thermal label printers, pick a module width that's a whole number of printer dots. At 203 DPI one dot is 0.125 mm, so 0.25 mm (2 dots) or 0.375 mm (3 dots) prints cleanly.
Styled QR codes
Rounded or dotted modules, custom eyes, gradients and a logo. They apply to any 2D code. SVG and PNG support all of them. PDF and EPS draw the shapes and logo, but use the gradient's first colour as a solid fill. Every combination in the test suite still scans.
use PhpBarcode\Renderer\Style\FinderShape; use PhpBarcode\Renderer\Style\Gradient; use PhpBarcode\Renderer\Style\ModuleShape; Barcode::qrCode('https://example.com', 'H') ->withModuleShape(ModuleShape::Dot) // Square, Rounded, Dot ->withFinderShape(FinderShape::Rounded) // Square, Rounded, Circle ->withGradient(Gradient::linear('#1e3a8a', '#db2777', angle: 45)) ->withLogo('logo.png', size: 0.2) // 20% of the code's width ->save('menu.png');
A logo covers modules, and the code has to rebuild them through error correction. Use error correction level H, keep the logo at or below about 20%, and test on a real phone. The modules behind the logo are cleared, plus a one-module margin; pass new Logo(..., punchout: false) to overlay it instead. SVG logos work in SVG output. PNG, PDF and EPS need a raster logo.
QR code payloads
Phones act on specially formatted QR content: they join Wi-Fi, add contacts, and open banking apps. These classes build that content correctly, including the escaping rules nobody remembers:
use PhpBarcode\Payload\{WiFi, VCard, MeCard, EpcPayment, CalendarEvent, Email, Sms, Phone, Geo, Url}; Barcode::qrCode(new WiFi('Cafe Guest', 'espresso;please')); Barcode::qrCode(new VCard('Ada', 'Lovelace', organization: 'Analytical Engines', phones: ['+44 20 7946 0000'], emails: ['ada@example.com'])); Barcode::qrCode(new EpcPayment('Red Cross Belgium', 'BE72 0000 0000 1616', amountEur: 25.00, remittanceText: 'Donation'), 'M'); Barcode::qrCode(new CalendarEvent('Launch party', new DateTimeImmutable('2026-12-04 19:00'), new DateTimeImmutable('2026-12-04 23:00'), location: 'Rooftop')); Barcode::qrCode(new Geo(48.8584, 2.2945)); Barcode::qrCode(new Email('hello@example.com', subject: 'Hi'));
EpcPayment validates the IBAN checksum and follows EPC069-12, the "GiroCode" that European banking apps scan. MeCard is a smaller alternative to VCard when the code needs to stay small.
Text is encoded efficiently: the encoder splits data into numeric, alphanumeric, byte and Kanji segments to get the smallest possible symbol. Non-ASCII text gets a UTF-8 marker (ECI) so scanners don't garble it.
GS1: product, logistics and healthcare labels
GS1 barcodes carry Application Identifiers: (01) is a GTIN, (17) an expiry date, (10) a batch number, and so on. Write them the way they're printed:
Barcode::gs1128('(01)09506000134352(17)251231(10)ABC123'); // shipping labels Barcode::gs1DataMatrix('(01)09506000134352(17)251231(10)ABC123'); // healthcare, UDI Barcode::of('databarexpanded', '(01)09506000134352(3103)001750'); // fresh food with weight Barcode::digitalLink('(01)09506000134352(10)ABC123'); // QR code with a GS1 Digital Link
Every value is checked against the full GS1 dictionary of 541 AIs, generated from the official GS1 Syntax Dictionary. That covers lengths, character sets, check digits, real calendar dates, country and currency codes, and which AIs may or must appear together. When something's wrong, the error says exactly what:
AI (01): invalid check digit at position 14, expected 2
AI (17): invalid month "13" at position 3
You can also use the parser on its own, for example to read a scanned label:
use PhpBarcode\Gs1\ElementString; use PhpBarcode\Gs1\DigitalLink; $label = ElementString::parse("]C101095060001343521725123110ABC123"); // as a scanner sends it $label->get('17'); // "251231" $label->toHri(); // "(01)09506000134352(17)251231(10)ABC123" (new DigitalLink())->toUri('(01)09506000134352(10)ABC123(17)251231'); // https://id.gs1.org/01/09506000134352/10/ABC123?17=251231
Symbology options
Barcode::of('name', …) uses sensible defaults. To change a symbology's settings, construct its class and hand it to Barcode::create():
use PhpBarcode\Symbology\{QrCode, DataMatrix, Pdf417, AztecCode, Code39, Itf14, Ean13, BearerBars}; use PhpBarcode\Symbology\QrCode\ErrorCorrectionLevel; use PhpBarcode\Symbology\DataMatrix\DataMatrixShape; Barcode::create(new QrCode(ErrorCorrectionLevel::Q, minVersion: 5), $data); Barcode::create(new DataMatrix(DataMatrixShape::Rectangle), 'LOT 42'); Barcode::create(new DataMatrix(size: '16x48'), 'LOT 42'); Barcode::create(new Pdf417(errorCorrectionLevel: 5, columns: 6), $driverLicence); Barcode::create(new AztecCode(errorCorrection: 33), $ticket); Barcode::create(new Code39(withChecksum: true), 'PART-9'); Barcode::create(new Itf14(BearerBars::Horizontal), '15400141288763'); Barcode::create(new Ean13(), '9781234567897+51299'); // EAN-13 with a 5-digit price add-on
Commonly used options:
| Symbology | Options |
|---|---|
QrCode |
error correction L/M/Q/H, fixed or minimum version, mask, ECI, GS1 mode, Kanji, structured append |
MicroQrCode, RectangularMicroQrCode |
error correction, version or size ('R11x43') |
DataMatrix |
square / rectangle / any, fixed size, DMRE sizes, GS1 mode, ECI, structured append |
Pdf417, MicroPdf417 |
error correction level 0–8, columns, rows, aspect ratio, row height, compact mode, Macro PDF417 |
AztecCode |
error correction %, layers, compact or full range, GS1 mode, ECI |
MaxiCode |
mode 2–6, postal code, country, service class, structured append |
Code128 |
force code set A, B or C; FNC1 |
Code39, Interleaved2of5, Industrial2of5 |
optional check digit, 2:1 or 3:1 wide bars |
MsiPlessey |
mod 10, mod 11, mod 1010, mod 1110 |
Itf14 |
bearer bars: box, top/bottom or none |
Ean13, UpcA, UpcE, Ean8 |
add-on gap; append +12 or +12345 to the data for an add-on |
AustraliaPost |
customer, reply paid, routing or redirection |
DataBarOmni, DataBarExpanded |
stacked layouts, segments per row |
Errors
Every exception the library throws implements PhpBarcode\Exception\BarcodeException, so one catch covers everything. The messages are written to be shown to a user who typed in a bad value:
use PhpBarcode\Exception\BarcodeException; try { Barcode::ean13($_POST['gtin'])->output(); } catch (BarcodeException $e) { echo $e->getMessage(); // EAN-13 check digit is invalid: expected "1", got "2". }
Other messages you might see:
Code 39 cannot encode character "*" at position 1.The data (8000 bytes) is too long for a QR Code at error correction level M.
To check input without exceptions, call supports() on any symbology:
(new Ean13())->supports($input); // true or false
Command line
vendor/bin/barcode renders any type from the shell. Without -o it draws the barcode in the terminal, and a QR code drawn this way scans straight off the screen.
vendor/bin/barcode qr "https://example.com" vendor/bin/barcode ean13 400638133393 -o product.svg vendor/bin/barcode code128 "SHIP-42" -o label.png --height 120 --module 3 vendor/bin/barcode gs1128 "(01)09506000134352(10)ABC123" -o carton.pdf vendor/bin/barcode --help
HTTP endpoint
barcode.php in the repository root serves images over HTTP, using the same URLs as version 1:
<img src="/barcode.php?text=HELLO-123" alt="HELLO-123"> <img src="/barcode.php?codetype=qr&text=https%3A%2F%2Fexample.com&sizefactor=6&format=svg" alt="">
| Parameter | Meaning | Default |
|---|---|---|
text |
Data to encode (max 256 characters) | 0 |
codetype |
Any type name from the gallery above | code128 |
size |
Bar height in px | 20 |
sizefactor |
Module width in px | 1 |
orientation |
horizontal or vertical |
horizontal |
print |
true to print the text |
false |
caption |
Custom text to print instead of the data | |
format |
png, svg, jpeg, gif, webp, pdf, eps |
png |
fg, bg |
Hex colours | 000, fff |
Bad input gets a 400 with a plain-text explanation. Sizes are capped so a public endpoint can't be asked for a gigantic image.
Upgrading from 1.x
Most sites need to change nothing.
barcode.php?text=…URLs keep working, with the same parameters and defaults.- The
barcode()function still exists, with the same signature, once Composer's autoloader (orsrc/autoload.php) is loaded. It's marked deprecated in favour of theBarcodeclass. filepathis no longer accepted over HTTP. In 1.x any visitor could make the script write a file anywhere on your server. Call->save()from PHP instead.- Interleaved 2 of 5 (
code25) with an odd number of digits used to drop the last digit silently. It now pads with a leading zero, as the spec requires. - Codabar no longer silently drops characters it can't encode, and no longer doubles up start/stop characters you supply yourself.
- A backtick in Code 128 now encodes. In 1.x it hit a typo in the lookup table.
- Code 39 and Interleaved 2 of 5 default to the more scanner-friendly 3:1 wide-bar ratio. Pass
wideRatio: 2for 1.x-identical widths.
Extending
Symbologies and renderers are small interfaces, and everything else (sizing, rotation, every output format) comes for free.
A new barcode type: extend AbstractSymbology and return a symbol. This one encodes binary digits as wide and narrow bars:
use PhpBarcode\Symbology\AbstractSymbology; use PhpBarcode\Symbol\LinearSymbol; use PhpBarcode\Symbol\Symbol; final class BinaryBars extends AbstractSymbology { public function name(): string { return 'Binary bars'; } protected function validate(string $data): void { $this->assertAlphabet($data, '01'); // throws a helpful InvalidDataException } protected function doEncode(string $data): Symbol { // "0" → narrow bar, "1" → wide bar, each followed by a narrow space; then a final bar. $pattern = strtr($data, ['0' => '11', '1' => '31']) . '1'; return LinearSymbol::fromWidthString($pattern, $data, $this->name()); } } Barcode::create(new BinaryBars(), '1011')->save('binary.pdf');
Return a LinearSymbol for bars, a MatrixSymbol for a grid of modules, a HeightModulatedSymbol for 4-state postal-style bars, or a HexagonSymbol for MaxiCode-style hexagons. Anything else can implement the Symbol interface and describe itself as rectangles, polygons and rings.
A new output format: implement RendererInterface, which has three methods: render(Symbol, RenderOptions): string, mimeType() and extension(). The built-in renderers turn a symbol into a Scene of shapes (SceneBuilder) and draw it on a Canvas. Implement Canvas to get styling, logos, rotation and text layout for free.
How it's tested
A barcode that doesn't scan is worse than none, so correctness is checked against outside references wherever possible:
- Real decoding. The integration suite renders symbols and decodes them with zxing-cpp. That covers every 2D code, every type in the gallery that zxing can read, every output format, and every style option.
- Bit-for-bit comparison. QR, Micro QR and rMQR module grids are identical to segno and Zint across all versions and error correction levels. Linear symbologies that no decoder reads (Code 11, MSI, Plessey, Pharmacode, 2 of 5) are compared against Zint.
- Official test vectors. These include ISO examples for QR, Data Matrix and PDF417, all 10,000 cases in the USPS Intelligent Mail test file, and the published samples from Royal Mail, PostNL, Australia Post and Japan Post.
- PHPStan at max level, and CI on PHP 8.2 to 8.5.
composer test # unit tests composer analyse # PHPStan composer check # style + analysis + tests
To run the decode tests locally, pip install zxing-cpp pillow and set BARCODE_DECODER="python3 tests/Integration/decode.py".
Credits and licence
Created by David S. Tufts. MIT licensed; see LICENSE.
The tables and algorithms follow the ISO/IEC, GS1, USPS and postal-operator specifications. Where an open-source implementation helped clarify a detail, it's credited in the relevant class: Zint (BSD-3-Clause), zxing-cpp (Apache-2.0), BWIPP (MIT) and Nayuki's QR Code generator (MIT). The GS1 Application Identifier data comes from the GS1 Syntax Dictionary (Apache-2.0).