17

How can I check if a string is a valid EAN / GTIN barcode in JavaScript?

I need checks for EAN8, EAN12, EAN13, EAN14, EAN18 and also GTIN12, GTIN13, GTIN14.

dom
  • 11,894
  • 10
  • 51
  • 74

8 Answers8

33

EDIT I also created a npm module, which can be found on github.

I created a small library, which supports EAN8, EAN12, EAN13, EAN14, EAN18, GTIN12, GTIN13 and GTIN14.

It works inside node.js and all modern browsers.

barcoder.js:

/*!
 * Barcoder
 * Copyright (c) 2013 mifitto GmbH <dominik@mifitto.com>
 * MIT Licensed
 */

(function() {

  'use strict';

  /**
   * Library version.
   */

  var version = '1.1.0';

  /**
   * Supported formats
   */

  var minValidLength = 6;
  var maxValidLength = 18;
  var usualValidChars = /^\d+$/;

  var formats = {
    'ean8'   : { validChars : /^\d+$/, validLength : 8 },
    'ean12'  : { validChars : /^\d+$/, validLength : 12 },
    'ean13'  : { validChars : /^\d+$/, validLength : 13 },
    'ean14'  : { validChars : /^\d+$/, validLength : 14 },
    'ean18'  : { validChars : /^\d+$/, validLength : 18 },
    'gtin12' : { validChars : /^\d+$/, validLength : 12 },
    'gtin13' : { validChars : /^\d+$/, validLength : 13 },
    'gtin14' : { validChars : /^\d+$/, validLength : 14 }
  };

  /**
   * Validates the checksum (Modulo 10)
   * GTIN implementation factor 3
   *
   * @param  {String} value The barcode to validate
   * @return {Boolean}
   * @api private
   */

  var validateGtin = function( value ) {

    var barcode = value.substring( 0, value.length - 1 );
    var checksum = parseInt( value.substring( value.length - 1 ), 10 );
    var calcSum = 0;
    var calcChecksum = 0;

    barcode.split('').map(function( number, index ) {
      number = parseInt( number, 10 );
      if ( value.length % 2 === 0 ) {
        index += 1;
      }
      if ( index % 2 === 0 ) {
        calcSum += number;
      }
      else {
        calcSum += number * 3;
      }
    });

    calcSum %= 10;
    calcChecksum = (calcSum === 0) ? 0 : (10 - calcSum);

    if ( calcChecksum !== checksum ) {
      return false;
    }

    return true;

  };

  /**
   * Barcoder class
   *
   * @param {string}  format    See formats
   * @param {Object}  options   Valid option `enableZeroPadding`, defaults to `true`
   * @api public
   */

  var Barcoder = function ( format, options ) {

    if ( format && !formats[format] ) throw new Error( '"format" invalid' );

    this.format = (format) ? formats[format] : 'autoSelect';
    this.options = (options) ? options : { enableZeroPadding : true };

    if ( !this.options.enableZeroPadding ) {
      this.options.enableZeroPadding = true;
    }

  };

  /**
   * Validates a barcode
   *
   * @param  {string}  barcode   EAN/GTIN barcode
   * @return {Boolean}
   * @api public
   */

  Barcoder.prototype.validate = function( barcode ) {

    var self = this;

    if ( self.format === 'autoSelect' ) {

      if ( barcode.length < minValidLength || barcode.length > maxValidLength ) {
        return false;
      }

      var isValidGtin = validateGtin( barcode );
      var paddedBarcode = barcode;
      var successfullyPadded = false;

      if ( !isValidGtin ) {
        var possiblyMissingZeros = maxValidLength - barcode.length;
        while( possiblyMissingZeros-- ) {
          paddedBarcode = '0' + paddedBarcode;
          if ( validateGtin( paddedBarcode ) ) {
            isValidGtin = true;
            successfullyPadded = true;
            break;
          }
        }
      }

      return {
        possibleType: (barcode.length > 8) ? 'GTIN' + barcode.length : 'EAN8 / padded GTIN',
        isValid: isValidGtin
      };

    }

    var validChars = self.format.validChars;
    var validLength = self.format.validLength;
    var enableZeroPadding = self.options.enableZeroPadding;

    if ( validChars.exec( barcode ) === null ) {
      return false;
    }

    if ( enableZeroPadding && barcode.length < validLength ) {
      var missingZeros = validLength - barcode.length;
      while( missingZeros-- ) {
        barcode = '0' + barcode;
      }
    }
    else if ( !enableZeroPadding && barcode.length != validLength ) {
      return false;
    }
    else if ( barcode.length > validLength ) {
      return false;
    }

    return validateGtin( barcode );

  };

  /**
   * Export
   */

  if ( 'undefined' !== typeof module && module.exports ) {
    module.exports = Barcoder;
    exports.version = version;
  }

  if ( 'undefined' === typeof ender ) {
    this['Barcoder'] = Barcoder;
  }

  if ( 'function' === typeof define && define.amd ) {
    define('Barcoder', [], function () {
      return Barcoder;
    });
  }

}).call( this );

Installation:

$ npm install barcoder

Usage:

var Barcoder = require('barcoder');

var ean1 = '0016T20054453';
var ean2 = '9330071314999';

var validator = new Barcoder('ean13');

console.log( '%s ean1 is valid: %s', ean1, validator.validate( ean1 ) );
console.log( '%s ean2 is valid: %s', ean1, validator.validate( ean2 ) );

// or /w automatic type selection

validator = new Barcoder();

var validation1 = validator.validate( ean1 );
var validation2 = validator.validate( ean2 );

console.log( '%s is valid: %s and has guessed type: %s', ean1, validation1.isValid, validation1.possibleType );
console.log( '%s is valid: %s and has guessed type: %s', ean2, validation2.isValid, validation2.possibleType );
dom
  • 11,894
  • 10
  • 51
  • 74
18

I am not sure why, but @doms solution did not work correctly for me. Also I would like to both calculate new codes as well as verify old ones. I ended up with this, that I have verified to be working in my browsers atleast:

function eanCheckDigit(s){
    var result = 0;
    for (let counter = s.length-1; counter >=0; counter--){
        result = result + parseInt(s.charAt(counter)) * (1+(2*(counter % 2)));
    }
    return (10 - (result % 10)) % 10;
}

2020 Update - Had to add let in front of counter otherwise it was saying counter was not defined.

2020 2nd Update - After lots of fighting with this, I realized this formula only works for UPC's passed in that are 10(or even digits in length). If you pass one in that is 11 digits, this doesn't work. So I've modified it to work with any length UPC. Let me know if this can be written cleaner.

function eanCheckDigit(s){
        let result = 0;
        let i = 1;
        for (let counter = s.length-1; counter >=0; counter--){
            result = result + parseInt(s.charAt(counter)) * (1+(2*(i % 2)));
            i++;
        }
        return (10 - (result % 10)) % 10;
    }
dmikester1
  • 1,374
  • 11
  • 55
  • 113
Mr. Developerdude
  • 9,118
  • 10
  • 57
  • 95
  • I'm not sure why this code works for you. For me, the for loop had an error until I put a `let` in front of `counter`. It said `counter` was not defined. – dmikester1 Apr 22 '20 at 17:00
  • I am guessing that is beacuse the answer is rather old (2013) and js has progressed since then. Feel free to edit the answer to fix bugs! – Mr. Developerdude Apr 22 '20 at 18:21
  • I tried this code : 4026639011190 which is valid and it does not validate but if I use @Kjeld function it does – Patrice Flao Nov 23 '20 at 19:46
8

Here is a short version that can check if the EAN13 check digit is valid:

  var checkSum = ean.split('').reduce(function(p,v,i) {
    return i % 2 == 0 ? p + 1 * v : p + 3 * v;
    }, 0);
  if (checkSum % 10 != 0) {
    alert('error');
  }
Kjeld
  • 131
  • 1
  • 3
  • 1
    cool, thats exactly what I needed. Just add ean = ""+ean; to cast it as string to use the .split function – trojan Dec 20 '17 at 11:16
5

GS1 US published the check digit calculation algorithm for GTIN. It uses padding to calculate various barcodes and is actually a lot simpler than other methods I found above here.

It works with GTIN barcodes: GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN) and GTIN-14 (ITF-14).

function isValidBarcode(value) {
  // We only allow correct length barcodes
  if (!value.match(/^(\d{8}|\d{12,14})$/)) {
    return false;
  }

  const paddedValue = value.padStart(14, '0');

  let result = 0;
  for (let i = 0; i < paddedValue.length - 1; i += 1) {
    result += parseInt(paddedValue.charAt(i), 10) * ((i % 2 === 0) ? 3 : 1);
  }

  return ((10 - (result % 10)) % 10) === parseInt(paddedValue.charAt(13), 10);
}
RJD22
  • 10,230
  • 3
  • 28
  • 35
2

This is what I came up with:

/**
 * Test a string for valid EAN5 EAN8 EAN13 EAN14 EAN18
 * @see: https://www.activebarcode.com/codes/ean13.html
 * @param  {string} ean A string to be tested
 * @return {boolean} true for a valid EAN
 * @author Vitim.us <https://stackoverflow.com/a/65928239/938822>
 */
function isValidEAN(ean) {
    function testChecksum(ean) {
        const digits = ean.slice(0, -1);
        const checkDigit = ean.slice(-1) | 0;
        let sum = 0;
        for (let i = digits.length - 1; i >= 0; i--) {
            sum += (digits.charAt(i) * (1 + (2 * (i % 2)))) | 0;
        }
        sum = (10 - (sum % 10)) % 10;
        return sum === checkDigit;
    }
    ean = String(ean);
    const isValidLength = ean.length === 18 || ean.length === 14 || ean.length === 13 || ean.length === 8 || ean.length === 5;
    return isValidLength && /^\d+$/.test(ean) && testChecksum(ean);
}
Vitim.us
  • 20,746
  • 15
  • 92
  • 109
1

Here is my solution, checking for different length barcodes using the specification to calculate the check digit at the end (see note):

// ean/gtin validation for 8, 12, 13 & 14 digit barcodes
function codeOnBlur(barcode) {

    var barcodeLengthArr = [8, 12, 13, 14];
    var allowedChars = new RegExp(/\d{8,14}/); // >7 & <15
    // put numbers in array and convert to type Int.
    var barcodeArray = barcode.split('');
    for( var i = 0; i < barcodeArray.length; i++) {
        barcodeArray[i] = parseInt(barcodeArray[i], 10);
    }
    // get the last digit for checking later
    var checkDigit = barcodeArray.slice(-1)[0];
    // we'll need a to compare it to this:
    var remainder = 0;

    // check if input (barcode) is in the array and check against the regex. 
    if (($.inArray(barcode.length, barcodeLengthArr) > -1) && (allowedChars.test(barcode))) {
        console.log("barcodeArray ", barcodeArray, " :: checkDigit ", checkDigit);

        // Pop the last item from the barcode array, test if the length is 
        // odd or even (see note on calculating the check digit) and 
        // multiply each item in array based in position: 
        var total = 0;
        barcodeArray.pop();
        // odd length after pop
        if (barcodeArray.length % 2 === 1) {
            for (var i = barcodeArray.length - 1; i >= 0; i--) {
                barcodeArray[i] = i % 2 === 0 ? barcodeArray[i] * 3 : barcodeArray[i] * 1;
                total += barcodeArray[i];
            }
        // even length after pop
        } else if (barcodeArray.length % 2 === 0) {

            for (var i = barcodeArray.length - 1; i >= 0; i--) {
                barcodeArray[i] = i % 2 === 0 ? barcodeArray[i] * 1 : barcodeArray[i] * 3;
                total += barcodeArray[i];
            }
        } else {
            // validation passed = false
        }
        // calculate the remainder of totalrounded up to nearest multiple of 10:
        remainder = (Math.ceil((total + 1) / 10) * 10) - total;
        console.log("loop total = ", total, ", remainder: ", remainder);

        if ( remainder === checkDigit ) {
            //validation passed = true;
            return;
        } else {
              //validation passed = false; 
        }

    } else {
        //validation Passed = false;
    }
}

I'm certain this code can be tidied up some :)

Manually checking the "integrity bit" or check digit:

     barcode: 13:   4  0  1  1  2  0  0  2  9  6  9  0  8
               8:                  5  0  8  1  8  9  0  7

  multiplier:       3  1  3  1  3  1  3  1  3  1  3  1  check digit  

To take the 8 digit code working backwards:

0*1 + 9*3 + 8*1 + 1*3 + 8*1 + 0*3 + 5*1 = 73

Difference from 73 to 80 is 7 (the specification will have you round up to
 the nearest power of 10).   

7 is both the check digit and the remainder of 80-73.

Rin and Len
  • 447
  • 6
  • 22
0

I managed to create a simplified barcode validation function testing the EAN digit check (Modulo-10 algorithm) which is used across different numeric barcodes and for book ISBN numbers.

It works with any EAN barcodes of any length like: GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN), ISBN, GTIN-14 (ITF-14) and GTIN18 (SSCC)

function isValidBarcode(number) {
  const checkDigit = String(number).slice(0, -1).split('').reverse().reduce((sum, v, i) => sum + v * (i % 2 || 3), 0)*9%10
  return /^\d+$/.test(number) && String(checkDigit) === String(number).at(-1)
}

If you're targeting a specific length of barcode you can also check that:

const isValidBarcodeAndSize =  [12,13].includes(String(myBarcode).length) && isValidBarcode(myBarcode)
paulcol.
  • 2,910
  • 1
  • 21
  • 19
-1

I'm sorry if this code is a little too long, but this is what I have for verifying an EAN13 barcode:

function isBarcode(barcode) {
    if (typeof barcode === 'number') {
        throw 'RuntimeError: Barcode MUST NOT be in number format'
    } else if (barcode.length!==12) {
        throw 'RuntimeError: String length is not 12'
    };
    var _= barcode.toString().split("")
    var _1 = 0
    var _2 = 0
    var __
    for ($=0;$<=10;++$) {
        _1+=+_[$]
    };for ($=10;$>=0;$-=2) {
       _2+=+_[$]
    };_2*=2
    var _3 = _1+_2
    __=+_3.toString().substring(1,2)
    if (__>9) {
        __=+_3.toString().substring(1,2)
    } else if (__===0) {
        __=10
    };
    __=10-__
    if (__===+_[11]) {
        return true
    }
    return false
};
ElectroBit
  • 1,152
  • 11
  • 16
  • Please explain why this works. This will prevent the OP from simply copying and pasting your code without understanding how it works, or if it is at all suitable for their task at hand. – rayryeng Aug 11 '14 at 22:22
  • @rayryeng: I won't copy anything without asking for permissions ;) I like clever solutions, but I think this piece of code is not really maintainable and/or readable. I would also welcome an explanation. – dom Aug 12 '14 at 15:33
  • @dom - ahahaha I agree :) – rayryeng Aug 12 '14 at 15:37