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

Gs1 Barcode Parser Laravel Package

lamoda/gs1-barcode-parser

Parse GS1 barcodes in PHP with lamoda/gs1-barcode-parser. Decode application identifiers like GTIN, batch/lot, expiry date, and serial number from GS1-128 or DataMatrix labels into structured data for inventory, logistics, and Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a pure PHP library (no Laravel service provider, no facade, no config file), so installation is a single Composer command and then you instantiate it yourself:

composer require lamoda/gs1-barcode-parser

Where to look first

  1. vendor/lamoda/gs1-barcode-parser/README.md — contains the canonical parse example; copy it verbatim into php artisan tinker before writing any application code.
  2. vendor/lamoda/gs1-barcode-parser/src/ — the top-level Gs1BarcodeParser class and the ApplicationIdentifier* classes are the only public surface you should rely on.
  3. composer.json of the package — check the php constraint (the project has not shipped since Feb 2022, so it is pinned to PHP 7.x/8.0-era constraints; verify it installs on your current PHP before committing).

First use case — the most common one in Laravel is parsing a scanned GS1 DataMatrix (from a handheld scanner or a camera library) at the edge of the app, e.g. in a form request or an API controller, and extracting the GTIN plus lot/expiry:

use Lamoda\Gs1BarcodeParser\Gs1BarcodeParser;

// Typically created once and reused (it is stateless per parse call).
$parser = new Gs1BarcodeParser();

$raw = $request->input('barcode'); // e.g. "(01)09501101530003(17)270531(10)LOT123"
$result = $parser->parse($raw);

$gtin     = $result->find('01')?->getValue(); // "09501101530003"
$expiry   = $result->find('17')?->getValue(); // "270531" (YYMMDD)
$lot      = $result->find('10')?->getValue(); // "LOT123"

Wrap that call in try/catch (\Throwable $e) from day one — malformed input is the normal case when a physical scanner misfires, and the library will throw rather than return a partial result.

Register it as a singleton so you don't re-build the AI table on every request:

// AppServiceProvider::register()
$this->app->singleton(Gs1BarcodeParser::class, fn () => new Gs1BarcodeParser());

Implementation Patterns

Thin domain wrapper. Don't let Gs1BarcodeParser leak into controllers. Write a small value object + mapper so the rest of the app speaks in your language (gtin, expiry_date, batch, serial) rather than GS1 AI codes:

final class ParsedGs1Barcode
{
    public function __construct(
        public readonly ?string $gtin,
        public readonly ?CarbonImmutable $expiry,
        public readonly ?string $batch,
        public readonly ?string $serial,
    ) {}
}

final class Gs1BarcodeParserService
{
    public function __construct(private Gs1BarcodeParser $parser) {}

    public function parse(string $raw): ParsedGs1Barcode
    {
        $result = $this->parser->parse($raw);
        $expiry = $result->find('17')?->getValue();

        return new ParsedGs1Barcode(
            gtin:   $result->find('01')?->getValue(),
            expiry: $expiry ? CarbonImmutable::createFromFormat('ymd', $expiry)->endOfDay() : null,
            batch:  $result->find('10')?->getValue(),
            serial: $result->find('21')?->getValue(),
        );
    }
}

Form request validation. Because parsing throws, a Rule object is a clean fit:

class ValidGs1Barcode implements ValidationRule
{
    public function __construct(private Gs1BarcodeParserService $service) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        try {
            $this->service->parse((string) $value);
        } catch (\Throwable $e) {
            $fail("The {$attribute} is not a valid GS1 barcode.");
        }
    }
}

Ingest pattern (queues). When scanners push batches of codes, parse in a job and store both the raw string and the structured fields. Keep the raw value — GS1 codes are dense and you will want to re-parse when you add a new AI, without asking the warehouse to rescan.

Persisting. Store the GS1 values in dedicated columns (gtin CHAR(14), expiry_date DATE, batch VARCHAR(20)). The parser does no normalisation: 01 values are 14 digits with leading zeros, and 17 values are six-digit YYMMDD — you convert, not the library.

Integration with a barcode scanner pipeline. Most scanners (Zebra, Honeywell) can emit either the human-readable (01)…(10)… form or the raw 01…\x1D10… form. The library handles the parenthesised form directly; for the raw form, check the README — the standard FNC1 separator is ASCII 29 (\x1D), and you may need to inject it before parsing. Normalise at the edge and store only one canonical format.

Testing. Ship fixture strings as raw byte literals so \x1D is preserved in the test:

$raw = "01" . "09501101530003" . "17" . "270531" . "\x1D" . "10" . "LOT123";

Gotchas and Tips

  • Not a Laravel package. There is no config, no publishing, no auto-discovery. If a Laravel-native wrapper appears in your team, this is the one place to add it; otherwise keep the singleton registration explicit.
  • Maintenance risk. Last release is Feb 2022. Pin it exactly ("lamoda/gs1-barcode-parser": "1.*"), and add a contract test around the AI codes you actually use. If you need AI support beyond the shipped table, you will be subclassing or patching — the ApplicationIdentifier classes are the extension point, and you can register custom AI definitions without forking if you construct the parser with your own AI collection.
  • Variable-length AIs. 10 (batch), 21 (serial) and 240/30 are variable length and must be terminated by FNC1 (\x1D) or be last in the code. Truncated or missing separators are the single most common parse failure in the wild; surface the raw string in your error log so you can tell a scanner problem from a real validation failure.
  • Fixed-length AIs have no separator. 01, 17, 11, 15 are fixed. Do not insert \x1D after them — some scanners do, and the extra byte can break naive splits.
  • Parentheses are human-readable only. (01)123… is the display form; the encoded form is 01123…. The parser accepts the parenthesised form, but if you forward barcode to another system, forward the raw scanned bytes, not the pretty form.
  • GTIN length. AI 01 is always 14 digits (leading zeros matter). AI 02 (contained GTIN) is also 14. Don't ltrim('0') — it changes the key you join on.
  • Expiry dates. AI 17 is YYMMDD; AI 7003 is YYMMDDHHMM. Handle the "day 00 means end of month" GS1 convention yourself — the library returns the literal string.
  • Exceptions vs. null returns. Assume parse() throws on invalid input and find() returns null for absent AIs. Do not wrap the whole pipeline in a bare catch that swallows parse errors silently — log at warning with the raw value.
  • Encoding. If the barcode is stored in MySQL, the \x1D separator and any UTF-8 in variable values survive only in a utf8mb4/VARBINARY column. Storing in latin1 will silently corrupt the separator and every later re-parse.
  • Performance. Constructing the parser builds the full AI table each time — bind it as a singleton in the container, not new per call, especially in queue workers that process thousands of codes.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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
cgv/localized-resource