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.
In this guide
- 1. What is an EAN barcode?
- 2. EAN-13 vs EAN-8 — when each is used
- 3. GS1 checksum — alternating weights 1 and 3
- 4. The GS1 prefix — what it actually means
- 5. Special prefixes — ISBN, ISSN, and coupons
- 6. The production-ready solution
- 7. Node.js code example
- 8. cURL example
- 9. Understanding the response
- 10. Edge cases
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
EAN-13 (13 digits)
The standard for retail products worldwide. Encodes GS1 prefix (2–3 digits), company code, product code, and check digit.
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.
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
| Digit | 5 | 9 | 0 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 5 | 7 |
| Weight | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | — |
| Product | 5 | 27 | 0 | 3 | 2 | 9 | 4 | 3 | 2 | 9 | 4 | 15 | ? |
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.
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.| Prefix | GS1 Member Organisation |
|---|---|
| 00–09 | United States & Canada |
| 30–37 | France |
| 40–44 | Germany |
| 45, 49 | Japan |
| 50 | United Kingdom |
| 57 | Denmark |
| 590 | Poland |
| 690–695 | China |
| 73 | Sweden |
| 76 | Switzerland |
| 80–83 | Italy |
| 84 | Spain |
| 87 | Netherlands |
| 978–979 | Bookland (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.
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; }
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 }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Correct length (8 or 13), all digits, GS1 checksum passes |
| format | string | EAN-8 or EAN-13 |
| prefix | string | 2 or 3-digit GS1 prefix (EAN-13 only) |
| prefixCountry | string | GS1 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
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.