payload-barcodes
v1.0.0
Published
Barcodes and QR codes for Payload products: pick the field, get a printable code on every item, with no runtime dependencies.
Maintainers
Readme
payload-barcodes
Gives every Payload product a barcode and a QR code, drawn from a field you choose, on a printable page that is black on white and nothing else. The encoders are part of this package: no runtime dependencies, no image service, no external call.
- Code 128, EAN-13 and QR written from the specification, not wrapped from a library
- The shop chooses which field holds the code, in the admin panel
- Filled in automatically on every save, and searchable
- One page for a single label or for a whole selection, ready for the printer
- The main entry imports no React at all
Compatibility
| Requirement | Version | Declared as |
| --- | --- | --- |
| payload | >=3.88 <4 | peer dependency |
| @payloadcms/plugin-ecommerce | >=3.88 <4 | peer dependency |
| react | >=19 <20 | optional peer dependency, only for the print buttons |
| Node | >=20 | engines |
The floor is the version this package was actually tested against. Verified end to end on Payload 3.88.0 with the official ecommerce plugin on PostgreSQL.
Install
Requires Payload 3.88 or newer and @payloadcms/plugin-ecommerce 3.88 or newer.
pnpm add payload-barcodesimport { barcodesPlugin } from 'payload-barcodes'
export default buildConfig({
plugins: [
barcodesPlugin({
collections: ['products'],
}),
],
})Then open Barcodes in the admin panel and name the field that holds the code, for example sku. Nothing is generated until you do.
What was measured
Measured on 21 August 2026 against @payloadcms/plugin-ecommerce 3.88.0 and the npm registry.
| Question | Answer | | --- | --- | | Does the ecommerce plugin produce a barcode or a QR code? | No. Neither term appears in it | | Is there a barcode or QR plugin for Payload? | None on the registry | | Do products carry a code field? | No |
The encoders are verified, not assumed
Every QR code this package draws is read back by an independent decoder in the test suite. The sweep covers versions 1 to 10 at all four error correction levels:
723 codes encoded, 723 decoded correctly, 0 failuresThat check runs in CI on every push, so a regression in the encoder cannot pass unnoticed.
The barcodes are checked against published article numbers. 590123412345 gives check digit 7, 400638133393 gives 1, 978020137962 gives 4, 501234567890 gives 0. An EAN-13 pattern is 95 modules wide with guards at 101, 01010 and 101; a Code 128 pattern is eleven modules per symbol plus a thirteen module stop.
Verified in a live install
| Test | Result |
| --- | --- |
| Admin names slug as the source field | Code filled in on save, indexed and searchable |
| A source value the CMS transforms | Recorded value matches the stored value, not the submitted one |
| QR set to a URL template | Page scanned back as https://shop.example/p/walnut-table-99 |
| Print page | HTML, #000 and #fff only, prints on load |
| Several ids at once | One page, one label per product, no label split across pages |
| Value that is not a valid EAN-13 | Recorded with a plain explanation instead of a broken drawing |
| Print endpoint without a token | 403 |
How it works
The plugin adds three things: a code group on each named collection, a settings global, and one endpoint.
Recording. An afterChange hook reads the source field and stores the value, the symbology, and an explanation when the value cannot be drawn. It runs after the write on purpose: a field hook may still transform the value, a slug being the usual case, and a code that does not match the field it came from is worse than none.
The hook writes only when something changed, and marks the request so its own save cannot re-enter it.
Drawing. The images are never stored. Both codes are drawn when the page is requested, so changing a setting changes every label immediately and nothing in your database goes stale.
Printing. The endpoint returns one complete HTML page holding every label, with page-break-inside: avoid so a label is never split. It opens the print dialog on load. There are no colours, no images and no scripts beyond that one call.
Options
| Option | Default | Meaning |
| --- | --- | --- |
| collections | required | Collections that get a code, by slug |
| disabled | false | Stops recording, leaves fields and settings in place |
| fieldName | 'barcode' | Name of the field group holding the code |
| settingsSlug | 'barcodes' | Slug of the settings global |
Settings
| Setting | Meaning |
| --- | --- |
| Source field | Field read for the code, for example sku. Nothing happens until this is set |
| Format | Code 128 for any printable ASCII, or EAN-13 for thirteen digit article numbers |
| QR code carries | The same value, or a URL built from it |
| URL template | Where {value} is replaced by the code, URL encoded |
| Print the value under the code | Adds the value in monospace under the bars |
What it adds to your database
| Where | Field | Notes |
| --- | --- | --- |
| each named collection | barcode group | value (indexed), format, and error when the value cannot be drawn |
| new global barcodes | source field, format, QR source, URL template, caption | Appears in the admin menu under Ecommerce |
The settings are an ordinary Payload global, which is why they reach the menu without this package shipping an admin component.
Printing
GET /api/barcodes/print?collection=products&id=42&type=barcode
GET /api/barcodes/print?collection=products&id=1,2,3&type=qrRequires an authenticated user and reads each document with the caller's own access rights. type is barcode or qr. Several ids give one page with one label each.
The two buttons
The endpoint is enough on its own. If you want the buttons inside a document, import them from the separate client entry:
import { PrintButtons } from 'payload-barcodes/client'
<PrintButtons collection="products" id={id} />They render exactly Print Barcode and Print QR Code, with no styling of their own beyond spacing, so they inherit your admin theme.
This is a deliberate split. The main entry contains no React, which is what keeps the package on the part of Payload's surface that survives minor releases. The buttons are opt in, and if they ever break, the printing itself does not.
Honest limits
QR codes go up to version 10. That is 271 bytes at level L, 213 at M. It covers an article number, a SKU or a product URL comfortably. A longer value is refused with a clear message rather than silently truncated.
Code 128 uses set B only. Printable ASCII from space to tilde. Set A control characters and set C numeric compression are not implemented, so a long numeric code is wider than it strictly needs to be. Anything outside set B, Greek included, is refused with an explanation.
EAN-13 is validated, not corrected. A value that is not thirteen digits with a matching check digit is recorded with an error and no barcode is drawn. completeEan13 is exported if you want to add the check digit yourself. The QR code is still drawn in that case.
Recording costs a second write on the first save. The hook runs after the document is written, so a new document is saved twice: once by you, once to record the code. Later saves write only when the value actually changed.
The print page carries no fonts of its own. It uses the system stack, so a label printed on one machine may be a hair wider than on another. The codes themselves are vector and exact.
Codes are not checked for uniqueness. Two products may carry the same value if the source field allows it. Put a unique index on your source field if that matters.
License
MIT. Copyright George Vasiliades, https://github.com/Poseidonas
