How to validate a EAN / GTIN barcode in JavaScript

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);
}

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;
    }

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 <[email protected]>
 * 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 );

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');
  }