Guide · Node.js · SDK · REST API

EAN Barcode Validation in Node.js — GS1 Checksum and Prefix Lookup

EAN barcodes are the backbone of global retail — every product on every shelf has one. Here's how the checksum works, what the prefix encodes, and how to validate barcodes reliably in your Node.js application.

1. What is an EAN barcode?

EAN stands for European Article Number, now officially called GTIN (Global Trade Item Number) by GS1 — the standards body that manages product identification globally. Despite the rebrand, "EAN" and "barcode" remain the common terms in practice.

Every EAN encodes a product identifier as a sequence of digits with a trailing check digit. When a barcode scanner reads the stripes, it decodes the digits and validates the checksum — ensuring the scan was not corrupted by a dirty or damaged label.

In e-commerce and inventory systems, EAN codes are used to identify products across suppliers and marketplaces, match catalog entries, and feed product databases.


2. EAN-13 vs EAN-8 — when each is used

5901234123457

EAN-13 (13 digits)

The standard for retail products worldwide. Encodes GS1 prefix (2–3 digits), company code, product code, and check digit.

96385074

EAN-8 (8 digits)

Used for small items where a full 13-digit barcode would not fit — cigarette packs, pencils, chewing gum. Assigned only by GS1 on request.

ℹ️UPC-A (the 12-digit barcode used in the US and Canada) is a subset of EAN-13: prepend a zero to any UPC-A to get a valid EAN-13. UPC-A barcodes scan correctly on any EAN-13 reader.

3. GS1 checksum — alternating weights 1 and 3

The checksum algorithm is simpler than Luhn — instead of doubling, it alternates fixed weights of 1 and 3, then computes the complement to 10.

Let's walk through 5901234123457:

Step 1 — Apply alternating weights (1 and 3) to the first 12 digits

Digit5901234123457
Weight131313131313
Product52703294329415?

Step 2 — Sum the products

5 + 27 + 0 + 3 + 2 + 9 + 4 + 3 + 2 + 9 + 4 + 15 = 83

Step 3 — Check digit = (10 − sum mod 10) mod 10

(10 − (83 % 10)) % 10 = (10 − 3) % 10 = 7

Last digit of barcode is 7 ✓ — valid EAN-13

// eanChecksum.js — validate EAN-8 or EAN-13
function validateEan(raw) {
  const digits = raw.replace(/[\s-]/g, '');
  if (!/^\d+$/.test(digits)) return false;
  if (digits.length !== 8 && digits.length !== 13) return false;

  const len = digits.length;
  let sum = 0;
  for (let i = 0; i < len - 1; i++) {
    // EAN-13: weight 1 for even positions (0,2,4...), 3 for odd (1,3,5...)
    // EAN-8:  weight 3 for even positions, 1 for odd (reversed)
    const weight = len === 13
      ? (i % 2 === 0 ? 1 : 3)
      : (i % 2 === 0 ? 3 : 1);
    sum += parseInt(digits[i], 10) * weight;
  }

  const checkDigit = (10 - (sum % 10)) % 10;
  return checkDigit === parseInt(digits[len - 1], 10);
}

console.log(validateEan('5901234123457')); // true  ✓  EAN-13
console.log(validateEan('96385074'));       // true  ✓  EAN-8
console.log(validateEan('5901234123458')); // false ✗  bad check digit

4. The GS1 prefix — what it actually means

The first 2–3 digits of an EAN-13 are the GS1 prefix, assigned to a GS1 Member Organisation by country. A common misconception is that it indicates the country of origin of the product — it does not. It indicates which GS1 national organisation issued the company number.

⚠️A product with prefix 590 (Poland) can be manufactured anywhere in the world — it only means the company registered its barcodes through GS1 Poland. Prefix is not country of origin.
PrefixGS1 Member Organisation
00–09United States & Canada
30–37France
40–44Germany
45, 49Japan
50United Kingdom
57Denmark
590Poland
690–695China
73Sweden
76Switzerland
80–83Italy
84Spain
87Netherlands
978–979Bookland (ISBN)

5. Special prefixes — ISBN, ISSN, and coupons

978–979 — Bookland (ISBN)

EAN-13 barcodes on books use prefix 978 or 979, encoding the ISBN-13. The digits after the prefix correspond to the book's ISBN (minus the check digit, which is recalculated for EAN). An ISBN-13 and its EAN-13 barcode are the same number.

// Check if an EAN-13 encodes a book (ISBN)
function isBookEan(ean13) {
  return ean13.startsWith('978') || ean13.startsWith('979');
}

977 — ISSN (Serials / Magazines)

Periodicals (magazines, newspapers, journals) use prefix 977, encoding the ISSN. The 8-digit ISSN is embedded in digits 4–10 of the EAN-13.

20–29 — In-store restricted codes

Prefix 20–29 is reserved for in-store use — price-embedded barcodes on weighed goods (deli, fresh produce), store-specific loyalty items, or internal inventory labels. These codes are not globally unique and should not be used in external product databases.


6. The production-ready solution

The IsValid EAN API validates the format (8 or 13 digits), computes the GS1 checksum, and for EAN-13 returns the GS1 prefix and the associated member organisation country.

EAN-8/13
Formats
both variants supported
<15ms
Response time
pure algorithmic check
100/day
Free tier
no credit card

Full parameter reference and response schema: EAN Validation API docs →


7. Node.js code example

import { createClient } from '@isvalid-dev/sdk';

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY });

// ── Example usage ────────────────────────────────────────────────────────────

const result = await iv.ean('5901234123457');

if (!result.valid) {
  console.log('Invalid EAN barcode');
} else {
  console.log('Format:', result.format);           // → 'EAN-13'
  console.log('Prefix:', result.prefix);           // → '590'
  console.log('Issued by:', result.prefixCountry); // → 'Poland'

  // Detect books
  if (result.prefix === '978' || result.prefix === '979') {
    console.log('This EAN encodes an ISBN (book)');
  }
}

In a product import pipeline:

// Validate barcodes before inserting into a product catalog
async function importProducts(rows) {
  const results = [];

  for (const row of rows) {
    if (!row.ean) {
      results.push({ ...row, eanStatus: 'missing' });
      continue;
    }

    const check = await validateEan(row.ean);

    if (!check.valid) {
      results.push({ ...row, eanStatus: 'invalid' });
      continue;
    }

    results.push({
      ...row,
      ean: row.ean.replace(/[\s-]/g, ''), // store normalised
      eanFormat: check.format,
      eanPrefix: check.prefix,
      eanCountry: check.prefixCountry,
      eanStatus: 'valid',
    });
  }

  return results;
}
Always store the normalised barcode (digits only, no spaces) in your database. Use the format field to distinguish EAN-8 from EAN-13 — they can look similar in some fonts and your UI should render them differently.

8. cURL example

Validate an EAN-13:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=5901234123457"

Validate an EAN-8:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=96385074"

Book barcode (ISBN-encoded EAN-13):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=9780141036144"

9. Understanding the response

Valid EAN-13:

{
  "valid": true,
  "format": "EAN-13",
  "prefix": "590",
  "prefixCountry": "Poland"
}

Valid EAN-8 (no prefix lookup for 8-digit codes):

{
  "valid": true,
  "format": "EAN-8"
}

Invalid barcode:

{
  "valid": false
}
FieldTypeDescription
validbooleanCorrect length (8 or 13), all digits, GS1 checksum passes
formatstringEAN-8 or EAN-13
prefixstring2 or 3-digit GS1 prefix (EAN-13 only)
prefixCountrystringGS1 member organisation associated with this prefix (EAN-13 only)

10. Edge cases

UPC-A → EAN-13 conversion

If a user scans a US product with a UPC-A scanner, they may get 12 digits instead of 13. Convert UPC-A to EAN-13 by prepending a zero and re-validating.

function upcAtoEan13(upc) {
  if (upc.length === 12) return '0' + upc;
  return upc; // already 13 digits or EAN-8
}

const ean = upcAtoEan13('012345678905');
console.log(ean); // → '0012345678905'

Distinguishing EAN-13 from ISBN-13

ISBN-13 and EAN-13 are the same format — ISBN-13 is just an EAN-13 with a 978 or 979 prefix. If your application handles both products and books, check the prefix to route the barcode to the correct lookup system (product catalogue vs book database).

Barcode scanner output

Barcode scanners typically emit the digits followed by Enter or Tab. Strip the trailing newline/tab before sending to the API. Some scanners add a prefix/suffix character — configure them to emit raw digits only for cleanest integration.

Leading zeros

EAN codes can start with zeros — 0012345678905 is a valid EAN-13. Do not parse barcodes as integers — store and process them as strings to preserve leading zeros.


Summary

Do not parse EAN codes as integers — leading zeros will be lost
Do not use GS1 prefix as country of origin — it is the issuing GS1 member organisation
Support both EAN-8 and EAN-13 — they use different weight sequences
Convert UPC-A (12 digits) to EAN-13 by prepending a zero
Use prefix 978/979 to detect ISBN-encoded barcodes
Store normalised (digits only) and format for display separately

Node.js integration notes

In a TypeScript project, represent a validated EAN barcode as a branded type so the compiler enforces that only checked values flow through your pipeline: type EanBarcode = string & { readonly __brand: 'EanBarcode' }. The IsValid SDK ships with complete TypeScript definitions for all response fields, which means your editor provides autocomplete on the parsed result — country codes, category names, registry data — without writing manual type declarations.

EAN barcode validation often appears inside data ingestion pipelines: EDI feeds, supply-chain APIs, catalog imports, or financial data streams. In these contexts, validation happens at the boundary where external data enters your system. Model this as a transformation step: raw string in, validated branded type out. Use Promise.allSettled() to process batches and collect both valid and invalid results — invalid items can be quarantined for manual review without blocking the rest of the batch.

Express.js and Fastify middleware

For APIs that accept EAN barcode as a path or query parameter, create a route middleware that validates the value before it reaches the handler. On success, attach the full parsed API response to req.validatedData so handlers can access enrichment fields (description, category, country) without making a second API call. Cache the parsed result in Redis keyed by the normalised identifier to avoid repeat API calls for the same value within a TTL window.

When EAN barcode values arrive from user input or partner systems, normalise them before validation: trim surrounding whitespace, remove optional hyphens or spaces that some sources include for readability, and convert to the canonical case used by the relevant standard. Apply the same normalisation logic consistently across your codebase to prevent cache misses caused by formatting differences rather than value differences.

  • Read process.env.ISVALID_API_KEY once at startup and store it in a module-level constant
  • Use jest.mock() to stub the IsValid client in unit tests; CI pipelines should never make real API calls
  • Log the full validation response at debug level — it often contains fields useful for debugging data quality issues
  • For very high throughput, consider a local pre-filter that checks format with a regex before calling the API, reducing calls for obviously malformed inputs

When making HTTP calls to the IsValid API directly (without the SDK), the choice between fetch and axios is largely a matter of preference. The native fetch API is available in Node.js 18+ without any additional dependency and is sufficient for simple request/response flows. axios adds automatic JSON parsing, request/response interceptors, and a cleaner timeout API (axios.create({ timeout: 5000 })), which makes it easier to centralise the Authorization header and retry logic in one place. For high-throughput services that make many concurrent API calls, consider undici — the HTTP client underlying Node.js fetch — used directly for its connection pooling and lower overhead.

See also

Validate EAN barcodes instantly

Free tier includes 100 API calls per day. No credit card required. Supports EAN-8 and EAN-13 with GS1 prefix lookup.