npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gs1-core

v0.1.0

Published

Zero-dependency library to parse, validate and generate GS1 element strings, raw barcode data and GS1 Digital Link URIs

Readme

gs1-core

Parse, validate and generate GS1 strings in JavaScript/TypeScript. Zero dependencies.

Supports the three ways GS1 data travels:

| Format | Example | | --- | --- | | Human-readable element string | (01)09506000134352(17)261231(10)ABC123 | | Raw barcode data (GS1-128, DataMatrix, QR) | ]d201095060001343521726123110ABC123 | | GS1 Digital Link URI | https://id.gs1.org/01/09506000134352/10/ABC123?17=261231 |

Features

  • Full AI dictionary: ~180 Application Identifiers from the GS1 General Specifications, including the decimal families (310n-369n, 390n-395n)
  • Validation of format specs, CSET 82 / CSET 39 character sets, dates, and check digits (GTIN, SSCC, GLN, GSIN, GSRN, GDTI, GCN, GRAI, ITIP)
  • One parse() call handles element strings and raw scanner data, including symbology identifiers (]C1, ]d2, ]Q3, ...)
  • Dates, weights and measures with implied decimal point, currency amounts and counts are returned as typed values, not strings
  • GS1 Digital Link URIs with correct primary key and qualifier ordering
  • ESM, fully typed, works in Node.js >= 18 and modern bundlers

Installation

npm install gs1-core

Quick start

import { parse } from 'gs1-core';

const elements = parse('(01)09506000134352(17)261231(3103)000189(10)ABC123');

for (const el of elements) {
  console.log(el.ai, el.definition.title, el.value, el.interpreted);
}
// 01    GTIN              09506000134352
// 17    USE BY OR EXPIRY  261231   { year: 2026, month: 12, day: 31, iso: '2026-12-31' }
// 3103  NET WEIGHT (kg)   000189   0.189
// 10    BATCH/LOT         ABC123

Usage

Parsing scanner output

Raw data uses ASCII GS (\x1D) as the FNC1 separator by default, and a leading symbology identifier is skipped automatically:

import { parseRawString, toRecord } from 'gs1-core';

const elements = parseRawString(']C101095060001343521726123110ABC123\x1D21SER99');

toRecord(elements);
// { '01': '09506000134352', '17': '261231', '10': 'ABC123', '21': 'SER99' }

// Some scanners are configured to substitute FNC1 with another character:
parseRawString('10LOT1|21SER1', { fnc1: '|' });

Parsing validates by default. Unknown AIs, wrong lengths, illegal characters, bad check digits and impossible dates all throw a GS1Error that tells you which AI failed. Pass { validate: false } to parse structure only.

import { GS1Error, parse } from 'gs1-core';

try {
  parse('(01)09506000134353'); // wrong check digit
} catch (err) {
  if (err instanceof GS1Error) console.error(err.ai, err.message);
  // '01' 'AI (01): invalid check digit in "09506000134353" (position 14)'
}

Generating strings

import { generateElementString, generateRawString } from 'gs1-core';

const items = [
  { ai: '01', value: '09506000134352' },
  { ai: '10', value: 'ABC123' },
  { ai: '17', value: '261231' },
];

generateElementString(items);
// '(01)09506000134352(10)ABC123(17)261231'

generateRawString(items);
// '010950600013435217261231' + '10ABC123'

generateRawString produces the content to encode after the leading FNC1 of a GS1-128 / GS1 DataMatrix / GS1 QR symbol. Predefined-length AIs are moved to the front so fewer separators are needed (disable with { optimize: false }), and variable-length values are GS-terminated only when another element follows.

Values are validated before generation, so you cannot emit an invalid GS1 string. Check digit helpers are available if you need to compute one first:

import { withCheckDigit, computeCheckDigit, verifyCheckDigit } from 'gs1-core';

withCheckDigit('0950600013435');    // '09506000134352'
computeCheckDigit('0950600013435'); // 2
verifyCheckDigit('09506000134352'); // true

GS1 Digital Link

import { generateDigitalLink, parseDigitalLink } from 'gs1-core';

generateDigitalLink(
  [
    { ai: '01', value: '09506000134352' },
    { ai: '10', value: 'ABC123' },
    { ai: '17', value: '261231' },
  ],
  { domain: 'https://id.example.com' }, // default: https://id.gs1.org
);
// 'https://id.example.com/01/09506000134352/10/ABC123?17=261231'

parseDigitalLink('https://id.gs1.org/01/4006381333931?17=261231');
// GTINs are normalised to 14 digits, resolver path prefixes are handled,
// and non-AI query parameters (linkType, ...) are ignored.

AI dictionary

import { AI_DEFINITIONS, getAIDefinition } from 'gs1-core';

getAIDefinition('3103');
// {
//   ai: '310n', title: 'NET WEIGHT (kg)', spec: 'N6',
//   predefinedLength: true, fixedLength: 6, kind: 'decimal', ...
// }

for (const def of AI_DEFINITIONS.values()) {
  console.log(def.ai, def.spec, def.title);
}

Interpreted values

element.interpreted carries a typed value when the AI has a known semantic:

| Kind | AIs (examples) | Type | | --- | --- | --- | | date | 11, 13, 15, 17, 7006, ... | { year, month, day, iso }. DD = 00 resolves to the last day of the month; 2-digit years follow the GS1 -49/+50 sliding window | | datetime | 7003, 7011, 8008, 4324, 4325 | { year, ..., hour, minute?, second?, iso } | | dateRange | 7007 | { start, end? } | | decimal | 310n-369n, 390n, 392n, 394n, 395n | number (implied decimal point from the 4th AI digit) | | currencyDecimal | 391n, 393n | { currency, amount } (ISO 4217 numeric code) | | count | 30, 37, 8111, ... | number |

API

| Function | Description | | --- | --- | | parse(input, options?) | Parse any GS1 string, auto-detecting the format | | parseElementString(input, options?) | Parse (01)...(10)... element strings | | parseRawString(input, options?) | Parse raw barcode data (fnc1 option for custom separators) | | parseDigitalLink(uri, options?) | Parse a GS1 Digital Link URI | | generateElementString(input, options?) | Build a human-readable element string | | generateRawString(input, options?) | Build raw barcode content (fnc1, optimize options) | | generateDigitalLink(input, options?) | Build a Digital Link URI (domain option) | | computeCheckDigit(digits) / verifyCheckDigit(digits) / withCheckDigit(digits) | GS1 mod-10 check digit helpers | | getAIDefinition(ai) / AI_DEFINITIONS | AI dictionary lookup / full table | | toRecord(elements) | Convert parsed elements to { ai: value } | | parseDate6, parseDateTime, resolveYear, interpretValue | Low-level interpretation helpers |

Generator functions accept either an array of { ai, value } pairs (order preserved) or a plain record { '01': '...', '10': '...' }. Note that JavaScript iterates integer-like keys such as '10' before keys like '01', so use the array form when order matters.

All errors are instances of GS1Error, which exposes the offending ai when known.

Known limitations

  • In human-readable element strings, a value that itself contains a "(AI)"-shaped sequence (parentheses are legal CSET 82 characters) is ambiguous and will be split. Use the raw format for such data; this ambiguity is inherent to the human-readable notation.
  • Trailing components documented as mandatory by GS1 for a few AIs (e.g. the postal code of AI 421) are treated as optional.

Contributing

Issues and pull requests are welcome.

git clone https://github.com/rmingon/gs1-core.git
cd gs1-core
npm install
npm test        # run the vitest suite
npm run build   # type-check and emit dist/

If you spot a missing or outdated Application Identifier, please open an issue with a reference to the relevant GS1 General Specifications section. The whole dictionary lives in src/ai.ts.

License

MIT