Guide · Node.js · 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

// eanValidator.js
const API_KEY = process.env.ISVALID_API_KEY;
const BASE_URL = 'https://api.isvalid.dev';

/**
 * Validate an EAN barcode using the IsValid API.
 *
 * @param {string} ean - EAN-8 or EAN-13 barcode (spaces and hyphens are stripped)
 * @returns {Promise<object>} Validation result with format and prefix info
 */
async function validateEan(ean) {
  const params = new URLSearchParams({ value: ean });

  const response = await fetch(`${BASE_URL}/v0/ean?${params}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(`EAN API error ${response.status}: ${error.message ?? 'unknown'}`);
  }

  return response.json();
}

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

const result = await validateEan('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

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.