Help us shape the future of Go! Take the 2026 Go Developer Survey

gs1

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 4 Imported by: 0

README

gs1-go

CI Go Reference Go Report Card

A dependency-free Go library for parsing and validating GS1 Application Identifier element strings: the data carried by GS1-128, GS1 DataMatrix, GS1 QR Code and GS1 DataBar symbols.

It is built for the data-capture edge, where scanner output is noisy and throughput matters: hospital dispensing, pharmacy point of sale, warehouse receiving, and pharmaceutical serialization systems.

import "github.com/galenzo17/gs1-go"

b, err := gs1.Parse("]d20104150000021126172506302112345ABC\x1D10LOT42X")
if err != nil {
    return err
}
b.GTIN()          // "04150000021126"
b.Lot()           // "LOT42X"
b.SerialNumber()  // "12345ABC"
b.ExpirationDate() // 2025-06-30 00:00:00 +0000 UTC

Features

  • Element string parsing for fixed- and variable-length AIs with FNC1 (ASCII 29) separators, bracket notation (01)…(17)…, AIM symbology identifiers (]C1, ]d2, ]Q3, ]e0, ]J1) and bare EAN-13 / UPC-A / GTIN-14.
  • Scanner resilience by default. UTF-8 BOM, CR/LF, NUL bytes and duplicated FNC1 from keyboard-wedge and USB HID scanners are normalized before parsing. See ADR 0003.
  • Missing-FNC1 recovery. When a scanner drops the separator between two variable-length fields, a balanced-split heuristic restores the boundary instead of failing.
  • Zero-allocation hot path. ParseInto reuses a caller-owned Barcode; values are substrings of the input. See ADR 0004.
  • GTIN check digits (GTIN-8/12/13/14), UPC-E expansion, YYMMDD date parsing with a configurable day-zero policy.
  • Regulatory profiles for LATAM pharmaceutical traceability: ANVISA, ANMAT, SNFA, COFEPRIS.
  • WebAssembly build with a JavaScript loader and TypeScript declarations.
  • CLI for scripts and scanner loops, also dependency-free.
  • Fuzzed continuously in CI and nightly.

Installation

go get github.com/galenzo17/gs1-go@latest

Requires Go 1.23 or later. The module has no dependencies outside the standard library.

CLI:

go install github.com/galenzo17/gs1-go/cmd/gs1@latest

Usage

Parse

Parse accepts whatever the scanner sends. The returned Barcode keeps the elements in scan order and offers typed accessors for the common healthcare AIs.

b, err := gs1.Parse("(01)04150000021126(17)250630(10)ABC123")
if err != nil {
    log.Fatal(err)
}

for _, e := range b.Elements {
    ai, _ := gs1.LookupAI(e.AI)
    fmt.Printf("(%s) %-16s %s\n", e.AI, ai.Name, e.Value)
}
// (01) GTIN             04150000021126
// (17) Expiration Date  250630
// (10) Batch/Lot        ABC123

v, ok := b.Get("17") // generic lookup, first occurrence

Errors wrap sentinel values so callers can branch with errors.Is:

Sentinel Meaning
ErrEmptyInput Input is empty or whitespace
ErrUnknownAI Unrecognized Application Identifier at a position
ErrTruncatedData Fixed-length field cut short
ErrInvalidData Data violates the AI's type or length
ErrInvalidCheckDigit GTIN modulo-10 check failed
ErrInvalidDate YYMMDD field is not a calendar date
ErrMissingRequiredAI A regulator-mandated AI is absent
Zero-allocation loop

For continuous scanning or batch pipelines, reuse a Barcode. Each goroutine must own its instance; there is no shared state.

var b gs1.Barcode
for sc.Scan() {
    b.Reset()
    if err := gs1.ParseInto(sc.Text(), &b); err != nil {
        log.Println(err)
        continue
    }
    process(b.GTIN(), b.Lot(), b.SerialNumber())
}
API Allocs/op Throughput, single core
Parse 1 ~3.9M/s
ParseInto 0 ~5.9M/s

Run make bench to reproduce on your hardware.

GTIN validation

Parsing checks structure only. Validate check digits explicitly:

err := gs1.ValidateGTIN("04150000021126")            // nil
check, _ := gs1.ComputeGTINCheckDigit("0415000002112") // '6'
upca, _ := gs1.ExpandUPCE("012345")                    // 12-digit UPC-A
Dates

GS1 dates are YYMMDD with years mapped to 2000–2099. A day of 00 denotes the end of the month in the GS1 General Specifications; some national systems interpret it as the first day instead.

t, _ := gs1.ParseDate("250200")                                   // 2025-02-28
t, _ = gs1.ParseDateWithOptions("250200",
        gs1.DateOptions{DayZero: gs1.DayZeroFirstDay})            // 2025-02-01
raw, _ := gs1.ParseDateRaw("250630")                              // "250630", validated
Regulatory profiles

Check that a barcode carries the AIs a national regulator mandates for pharmaceutical traceability.

b, _ := gs1.Parse("0104150000021126172506302112345\x1D10LOT1")

err := gs1.ANVISA.Validate(b)   // or b.ValidateANVISA()
Regulator Country Required AIs
ANVISA Brazil 01, 17, 10, 21
ANMAT Argentina 01, 17, 10, 21
SNFA Chile 01, 17, 10
COFEPRIS Mexico 01, 17, 10

Custom profiles are plain values: gs1.Regulator{Name: "…", RequiredAIs: []string{"01", "10"}}.

Supported Application Identifiers

Formats use GS1 Syntax Dictionary notation: N14 is exactly 14 digits, X..20 is up to 20 characters from the GS1 AI encodable character set 82.

AI Data title Format
00 SSCC N18
01 GTIN N14
02 Content GTIN N14
10 Batch/Lot X..20
11 Production Date N6
13 Packaging Date N6
15 Best Before Date N6
17 Expiration Date N6
21 Serial Number X..20
30 Count N..8
37 Count of Trade Items N..8
240 Additional Product ID X..30
241 Customer Part Number X..30
310n Net Weight, kg N6
320n Net Weight, lb N6
330n Gross Weight, kg N6
340n Gross Weight, lb N6
402 GSIN N17
414 GLN N13
710–714 NHRN (DE, FR, ES, BR, PT) X..20
90 Internal X..30
91–99 Internal X..90

gs1 ai <code> on the command line, or gs1.LookupAI in code, returns the same information. Requests for additional AIs are welcome; see CONTRIBUTING.md.

Input formats

Source Example
GS1-128 keyboard wedge 0104150000021126172506301012345
FNC1 as ASCII 29 2112345\x1D10LOT1
Human-readable brackets (01)04150000021126(17)250630(10)12345
AIM prefix, DataMatrix ]d2… (also ]d1)
AIM prefix, GS1-128 ]C1…
AIM prefix, QR / DotCode / composite ]Q3…, ]J1…, ]e0…
Bare GTIN (EAN-13, UPC-A, GTIN-14) 7800038041425 → AI 01, zero-padded to 14

GS1 DataBar needs no special handling: scanners decode it to the same element string as GS1-128.

Command line

gs1 parse "0104150000021126172506302112345ABC"        # text table
gs1 parse -json -iso "(01)04150000021126(17)250200"   # JSON, ISO dates
gs1 parse -validate anvisa "$SCAN"                     # exit 1 if non-compliant
cat scans.txt | gs1 parse -json                        # one JSON object per line
gs1 gtin 04150000021126                                # check digit
gs1 ai 3102                                            # AI (3102)  Net Weight kg  N6

Exit codes: 0 success, 1 invalid input or failed validation, 2 usage error.

WebAssembly

The same parser runs in browsers, Electron and edge runtimes.

make wasm   # produces wasm/gs1.wasm and a servable example under wasm/example/
<script src="wasm_exec.js"></script>
<script type="module">
  import { loadGS1 } from './gs1-loader.js';
  const gs1 = await loadGS1('gs1.wasm');

  const r = gs1.parse("0104150000021126172502001012345", { dateFormat: "iso", dayZero: "first" });
  r.gtin;            // "04150000021126"
  r.expirationDate;  // "2025-02-01"

  gs1.validateGTIN("04150000021126");            // true
  gs1.validateRegulatory(scan, "anvisa");       // null when compliant, else message
</script>

TypeScript declarations live in wasm/gs1.d.ts. Design notes are in ADR 0005.

Scope

This library is a parsing and validation layer. It does not generate barcodes, verify print quality, resolve GS1 Digital Link URIs, or implement business documents such as dispatch advices. See ADR 0002 for the reasoning and the GS1 resources that cover those areas.

Contributing

Issues and pull requests are welcome. Read CONTRIBUTING.md for the workflow and docs/adr for design history. Security reports go through SECURITY.md.

License

MIT. Copyright (c) 2026 Agustín Bereciartúa Castillo. See LICENSE.

GS1 is a registered trademark of GS1 AISBL. This project is an independent implementation of publicly available specifications and is not affiliated with, sponsored by, or endorsed by GS1.

Documentation

Overview

Package gs1 parses and validates GS1 Application Identifier element strings as produced by barcode scanners reading GS1-128, GS1 DataMatrix, GS1 QR Code and GS1 DataBar symbols.

The package is dependency-free, allocation-conscious, and designed for the data-capture edge: point-of-care dispensing, warehouse receiving, and pharmaceutical traceability systems where scanner output is noisy and throughput matters.

Terminology

An Application Identifier (AI) is a two- to four-digit prefix that declares the meaning and format of the data that follows it, as defined in the GS1 General Specifications. A sequence of AI/value pairs is an element string. Variable-length fields are terminated by the FNC1 character, which scanners transmit as ASCII 29 (Group Separator).

Parsing

Parse accepts raw scanner output, bracketed human-readable form, and AIM symbology identifiers:

b, err := gs1.Parse("]d20104150000021126172506302112345ABC\x1D10LOT42X")
if err != nil {
	return err
}
fmt.Println(b.GTIN(), b.Lot(), b.SerialNumber())

ParseInto reuses a caller-owned Barcode for zero-allocation parsing in hot loops. Each goroutine must own its Barcode.

Validation

Parsing checks structure only. ValidateGTIN verifies the modulo-10 check digit, ParseDate validates YYMMDD dates, and Regulator profiles check that the AIs mandated by a national pharmaceutical regulator are present.

Trademark

GS1 is a registered trademark of GS1 AISBL. This project is an independent implementation of publicly available specifications and is not affiliated with or endorsed by GS1.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ANVISA — Brazil (RDC 157/2017, SNCM). Requires GTIN + expiry + lot + serial.
	ANVISA = Regulator{Name: "ANVISA", Country: "BR", RequiredAIs: []string{"01", "17", "10", "21"}}

	// ANMAT — Argentina (Disposición 3683/2011, SNT). Requires GTIN + expiry + lot + serial.
	ANMAT = Regulator{Name: "ANMAT", Country: "AR", RequiredAIs: []string{"01", "17", "10", "21"}}

	// SNFA — Chile (ISP/MINSAL). Requires GTIN + expiry + lot.
	SNFA = Regulator{Name: "SNFA", Country: "CL", RequiredAIs: []string{"01", "17", "10"}}

	// COFEPRIS — Mexico. Requires GTIN + expiry + lot.
	COFEPRIS = Regulator{Name: "COFEPRIS", Country: "MX", RequiredAIs: []string{"01", "17", "10"}}
)

LATAM pharmaceutical regulators and their required AIs.

View Source
var ErrEmptyInput = errors.New("gs1: empty input")

ErrEmptyInput indicates that the input string is empty or contains only whitespace.

View Source
var ErrInvalidCheckDigit = errors.New("gs1: invalid check digit")

ErrInvalidCheckDigit indicates that a GTIN's modulo-10 check digit does not match.

View Source
var ErrInvalidData = errors.New("gs1: invalid data")

ErrInvalidData indicates that field data violates the AI's type or length constraints.

View Source
var ErrInvalidDate = errors.New("gs1: invalid date")

ErrInvalidDate indicates that a YYMMDD date field contains an impossible date.

View Source
var ErrMissingRequiredAI = errors.New("gs1: missing required AI")

ErrMissingRequiredAI indicates that a barcode is missing an AI required by a regulator.

View Source
var ErrTruncatedData = errors.New("gs1: truncated data")

ErrTruncatedData indicates that the input ended before a fixed-length field was fully consumed.

View Source
var ErrUnknownAI = errors.New("gs1: unknown application identifier")

ErrUnknownAI indicates that the parser encountered an unrecognized Application Identifier.

Functions

func ComputeGTINCheckDigit

func ComputeGTINCheckDigit(partial string) (byte, error)

ComputeGTINCheckDigit computes the GS1 modulo-10 check digit for a partial GTIN (all digits except the check digit). Returns the check digit as a byte ('0'-'9').

func ExpandUPCE

func ExpandUPCE(upce string) (string, error)

ExpandUPCE expands a 6-digit UPC-E code (without check digit) or an 8-digit UPC-E (with number system and check digit) to a 12-digit UPC-A. The input must be 6 or 8 digits.

func ParseDate

func ParseDate(yymmdd string) (time.Time, error)

ParseDate parses a 6-digit YYMMDD date string as used in GS1 barcodes. Years map to 2000-2099. Day 00 means the last day of the specified month (deprecated since 2025 but supported for backward compatibility).

func ParseDateRaw

func ParseDateRaw(yymmdd string) (string, error)

ParseDateRaw validates a YYMMDD string and returns it as-is without converting to time.Time. Useful when the caller wants the raw GS1 value.

func ParseDateWithOptions

func ParseDateWithOptions(yymmdd string, opts DateOptions) (time.Time, error)

ParseDateWithOptions parses a YYMMDD date with configurable day-zero policy. When opts.Format is DateFormatRaw, the returned time is zero-valued; use ParseDateRaw instead for a string result.

Example
package main

import (
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	last, _ := gs1.ParseDate("250200")
	first, _ := gs1.ParseDateWithOptions("250200", gs1.DateOptions{DayZero: gs1.DayZeroFirstDay})
	fmt.Println(last.Format("2006-01-02"), first.Format("2006-01-02"))
}
Output:
2025-02-28 2025-02-01

func ParseInto

func ParseInto(input string, b *Barcode) error

ParseInto parses a GS1 barcode string into an existing Barcode, reusing its allocated memory. Call b.Reset() before reuse to clear previous data. Each goroutine must use its own Barcode.

Example
package main

import (
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	scans := []string{
		"0104150000021126172506301012345",
		"010415000002112617251231\x1D10LOT2",
	}

	var b gs1.Barcode
	for _, s := range scans {
		b.Reset()
		if err := gs1.ParseInto(s, &b); err != nil {
			fmt.Println("error:", err)
			continue
		}
		fmt.Println(b.GTIN(), b.Lot())
	}
}
Output:
04150000021126 12345
04150000021126 LOT2

func ValidateGTIN

func ValidateGTIN(gtin string) error

ValidateGTIN validates the modulo-10 check digit of a GTIN string. It accepts GTIN-8, GTIN-12, GTIN-13, and GTIN-14.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	fmt.Println(gs1.ValidateGTIN("04150000021126"))

	err := gs1.ValidateGTIN("04150000021127")
	fmt.Println(errors.Is(err, gs1.ErrInvalidCheckDigit))
}
Output:
<nil>
true

Types

type AI

type AI struct {
	Code   string // AI code, e.g. "01", "10", "3102"
	Name   string // data title, e.g. "GTIN", "Batch/Lot"
	Format string // format specifier, e.g. "N14", "X..20"
}

AI describes a GS1 Application Identifier as listed in the GS1 General Specifications. Format uses GS1 Syntax Dictionary notation: "N14" is exactly 14 digits, "X..20" is up to 20 characters of any type.

func LookupAI

func LookupAI(code string) (AI, bool)

LookupAI returns the specification for an Application Identifier code such as "01" or "3102". The boolean is false for codes this package does not recognize.

Example
package main

import (
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	ai, ok := gs1.LookupAI("3102")
	fmt.Println(ok, ai.Name, ai.Format)
}
Output:
true Net Weight kg N6

type Barcode

type Barcode struct {
	Raw      string    // original input string
	Elements []Element // parsed AI-value pairs in scan order
}

Barcode represents a fully parsed GS1 barcode (GS1-128 or DataMatrix).

func Parse

func Parse(input string) (Barcode, error)

Parse parses a GS1 barcode string (GS1-128 or DataMatrix scanner output) into a Barcode with typed elements. It handles AIM symbology identifiers (e.g., ]C1, ]d2), FNC1 separators (ASCII 29), and bracket notation (e.g., "(01)04150000021126(17)250630").

Example
package main

import (
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	b, err := gs1.Parse("0104150000021126172506302112345ABC\x1D10LOT42X")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("GTIN:  ", b.GTIN())
	fmt.Println("Lot:   ", b.Lot())
	fmt.Println("Serial:", b.SerialNumber())

	exp, _ := b.ExpirationDate()
	fmt.Println("Expiry:", exp.Format("2006-01-02"))
}
Output:
GTIN:   04150000021126
Lot:    LOT42X
Serial: 12345ABC
Expiry: 2025-06-30
Example (BracketNotation)
package main

import (
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	b, _ := gs1.Parse("(01)04150000021126(17)250630(10)ABC123")
	for _, e := range b.Elements {
		ai, _ := gs1.LookupAI(e.AI)
		fmt.Printf("(%s) %-16s %s\n", e.AI, ai.Name, e.Value)
	}
}
Output:
(01) GTIN             04150000021126
(17) Expiration Date  250630
(10) Batch/Lot        ABC123

func (Barcode) BestBeforeDate

func (b Barcode) BestBeforeDate() (time.Time, error)

BestBeforeDate returns the parsed best-before date (AI 15).

func (Barcode) Count

func (b Barcode) Count() string

Count returns the item count (AI 30), or "" if not present.

func (Barcode) ExpirationDate

func (b Barcode) ExpirationDate() (time.Time, error)

ExpirationDate returns the parsed expiration date (AI 17).

func (Barcode) GTIN

func (b Barcode) GTIN() string

GTIN returns the GTIN value (AI 01), or "" if not present.

func (Barcode) Get

func (b Barcode) Get(ai string) (string, bool)

Get returns the value for the given AI code and whether it was found. If the AI appears multiple times, the first occurrence is returned.

func (Barcode) Lot

func (b Barcode) Lot() string

Lot returns the batch/lot number (AI 10), or "" if not present.

func (Barcode) ProductionDate

func (b Barcode) ProductionDate() (time.Time, error)

ProductionDate returns the parsed production date (AI 11).

func (*Barcode) Reset

func (b *Barcode) Reset()

Reset clears a Barcode for reuse, retaining allocated memory.

func (Barcode) SSCC

func (b Barcode) SSCC() string

SSCC returns the SSCC value (AI 00), or "" if not present.

func (Barcode) SerialNumber

func (b Barcode) SerialNumber() string

SerialNumber returns the serial number (AI 21), or "" if not present.

func (Barcode) ValidateANMAT

func (b Barcode) ValidateANMAT() error

ValidateANMAT checks compliance with Argentina's ANMAT traceability requirements.

func (Barcode) ValidateANVISA

func (b Barcode) ValidateANVISA() error

ValidateANVISA checks compliance with Brazil's ANVISA traceability requirements.

func (Barcode) ValidateCOFEPRIS

func (b Barcode) ValidateCOFEPRIS() error

ValidateCOFEPRIS checks compliance with Mexico's COFEPRIS traceability requirements.

func (Barcode) ValidateSNFA

func (b Barcode) ValidateSNFA() error

ValidateSNFA checks compliance with Chile's SNFA traceability requirements.

type DateFormat

type DateFormat int

DateFormat controls the output style for date fields.

const (
	// DateFormatTime returns parsed time.Time values (default).
	DateFormatTime DateFormat = iota
	// DateFormatRaw returns the raw YYMMDD string without parsing to time.Time.
	DateFormatRaw
)

type DateOptions

type DateOptions struct {
	DayZero DayZeroPolicy
	Format  DateFormat
}

DateOptions configures date parsing behavior.

type DayZeroPolicy

type DayZeroPolicy int

DayZeroPolicy controls how day=00 in YYMMDD dates is resolved.

const (
	// DayZeroLastDay resolves day 00 to the last day of the month (GS1 default).
	DayZeroLastDay DayZeroPolicy = iota
	// DayZeroFirstDay resolves day 00 to the first day of the month.
	DayZeroFirstDay
)

type Element

type Element struct {
	AI    string // application identifier code, e.g., "01"
	Value string // raw data value
}

Element represents a single AI-value pair extracted from a GS1 barcode.

type Regulator

type Regulator struct {
	Name        string   // regulatory body name, e.g., "ANVISA"
	Country     string   // ISO 3166-1 alpha-2 country code
	RequiredAIs []string // AI codes that must be present in compliant barcodes
}

Regulator represents a pharmaceutical regulatory body that mandates specific GS1 Application Identifiers for drug traceability.

func (Regulator) Validate

func (r Regulator) Validate(b Barcode) error

Validate checks that the barcode contains all Application Identifiers required by this regulator. Returns nil if compliant, or an error listing the first missing AI.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/galenzo17/gs1-go"
)

func main() {
	b, _ := gs1.Parse("01041500000211261725063010LOT42X")

	fmt.Println(gs1.COFEPRIS.Validate(b))

	err := gs1.ANVISA.Validate(b)
	fmt.Println(errors.Is(err, gs1.ErrMissingRequiredAI), err)
}
Output:
<nil>
true gs1: missing required AI: ANVISA requires AI (21)

Directories

Path Synopsis
cmd
gs1 command
Command gs1 parses GS1 Application Identifier element strings from the command line or from a stream of scanner output on stdin.
Command gs1 parses GS1 Application Identifier element strings from the command line or from a stream of scanner output on stdin.
gs1-wasm command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL