To calculate a UPC, EAN, GTIN-14 or ISBN-13 check digit, start with every digit except the last one. Multiply those digits alternately by 3 and 1, starting with the rightmost, and add up the products. The check digit is whatever number brings that total up to the next multiple of 10, or in code, (10 - sum % 10) % 10. ISBN-10 works differently: it uses weights 10 down to 2, modulo 11, and an X stands for 10.
Below are worked examples you can check by hand, a check digit calculator you can paste into JavaScript, Python, Excel/Google Sheets or SQL, and the bugs that show up in real code.
The GS1 mod-10 algorithm in four steps
For a UPC-A that's the first 11 digits. For an EAN-13 it's the first 12, and for a GTIN-14 the first 13.
The rightmost digit gets weight 3, the next one to the left gets 1, then 3, then 1, and so on.
Multiply each digit by its weight and add everything together.
The check digit is the difference between the sum and the next multiple of 10. If the sum is already a multiple of 10, the check digit is 0.
GS1 uses this same algorithm for every identifier with a mod-10 check digit, including UPC-A, EAN-13, EAN-8, GTIN-14, ISBN-13, GLN and SSCC. One function covers all of them.
Worked example: UPC-A 036000291452
The body is 03600029145. Working from the right edge:
| Digit | 0 | 3 | 6 | 0 | 0 | 0 | 2 | 9 | 1 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Weight | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 |
| Product | 0 | 3 | 18 | 0 | 0 | 0 | 6 | 9 | 3 | 4 | 15 |
The products add up to 58, and the next multiple of 10 is 60, so the check digit is 2. The full code is 036000291452, which matches what's printed on the package.
Worked example: EAN-13 4006381333931
The body is 400638133393. It has 12 digits, an even number, so from the left the weights run 1, 3, 1, 3...:
| Digit | 4 | 0 | 0 | 6 | 3 | 8 | 1 | 3 | 3 | 3 | 9 | 3 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Weight | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 |
| Product | 4 | 0 | 0 | 18 | 3 | 24 | 1 | 9 | 3 | 9 | 9 | 9 |
The sum is 89, so the check digit is 90 − 89 = 1, giving 4006381333931.
Look at the first column of each table. The UPC starts with weight 3 and the EAN starts with weight 1. That's the whole reason to count from the right. If you count from the left, you need a different rule for every length.
The same formula at other lengths
| Code | Type | Body | Weighted sum | Check digit |
|---|---|---|---|---|
96385074 |
EAN-8 | 9638507 |
86 | 4 |
9780306406157 |
ISBN-13 | 978030640615 |
93 | 7 |
10614141000996 |
GTIN-14 | 1061414100099 |
84 | 6 |
30614141000990 |
GTIN-14 | 3061414100099 |
90 | 0 |
The last row is the edge case: the sum is already a multiple of 10, so the check digit is 0, not 10.
Counting from the right has another benefit: leading zeros don't matter. A zero adds nothing to the sum, so 036000291452, 0036000291452 and 00036000291452 all have the same check digit. That's why you can pad any GTIN to 14 digits without recalculating anything (the GTIN guide explains how the formats nest).
ISBN-10: modulo 11 and the X
ISBN-10 predates the GS1 system and uses a different method. Multiply the first nine digits by 10, 9, 8 and so on down to 2. Add the products and take the remainder modulo 11. The check digit is (11 - remainder) % 11, and a result of 10 is written as X.
| ISBN | Products (weights 10 to 2) | Sum | Sum mod 11 | Check |
|---|---|---|---|---|
0-306-40615-? |
0, 27, 0, 42, 24, 0, 24, 3, 10 | 130 | 9 | 11 − 9 = 2 |
0-8044-2957-? |
0, 72, 0, 28, 24, 10, 36, 15, 14 | 199 | 1 | 11 − 1 = 10 = X |
To validate, weight all ten characters from 10 down to 1, counting X as 10. A valid ISBN-10 gives a total divisible by 11: 130 + 2 = 132 = 12 × 11, and 199 + 10 = 209 = 19 × 11.
Converting to ISBN-13 removes the X. Drop the old check digit, put 978 in front, and run the GS1 mod-10 formula on the result. 0-306-40615-2 becomes 9780306406157, and 0-8044-2957-X becomes 9780804429573. BarcodeReport's ISBN lookup treats both forms as the same book.
What a check digit catches, and what it doesn't
The 3-1 weighting catches every single-digit error. Changing one digit shifts the sum by 1 to 9, or by 3 times that, and none of those amounts is a multiple of 10.
It catches most swaps of neighboring digits, but not all of them. Swapping digits a and b changes the sum by 2 × (a − b), which is invisible whenever the two digits differ by exactly 5. For example, swap the 3 and 8 in 4006381333931 and you get 4006831333931, which also passes. ISBN-10's modulus of 11 catches every single-digit error and every adjacent swap. That stronger guarantee is why it needs an eleventh symbol, X.
Most importantly, a valid check digit doesn't mean the barcode is real. Roughly one in ten random 12-digit strings will pass. The check proves the number is well formed. It doesn't prove anyone assigned it to a product. To confirm that, look the code up: a product page like /036000291452 or a call to the BarcodeReport API tells you whether a product actually has that number.
Copy-paste implementations
The JavaScript and Python versions are tested against the examples above. The Excel and SQL versions implement the same arithmetic.
function gs1CheckDigit(body) { // UPC-A, EAN-13, EAN-8, GTIN-14, ISBN-13, SSCC
if (!/^\d+$/.test(body)) throw new Error(`digits only: ${body}`);
let sum = 0;
for (let i = 0; i < body.length; i++) {
const digit = body.charCodeAt(body.length - 1 - i) - 48; // walk from the right
sum += digit * (i % 2 === 0 ? 3 : 1);
}
return (10 - (sum % 10)) % 10;
}
function isValidGtin(code) {
const s = String(code).replace(/[\s-]/g, '');
if (!/^(\d{8}|\d{12,14})$/.test(s)) return false;
return gs1CheckDigit(s.slice(0, -1)) === Number(s[s.length - 1]);
}
function isbn10CheckDigit(body) {
if (!/^\d{9}$/.test(body)) throw new Error(`need 9 digits: ${body}`);
let sum = 0;
for (let i = 0; i < 9; i++) sum += Number(body[i]) * (10 - i);
const r = (11 - (sum % 11)) % 11;
return r === 10 ? 'X' : String(r);
}
function isbn10To13(isbn10) {
const body = '978' + String(isbn10).replace(/[\s-]/g, '').slice(0, 9);
return body + gs1CheckDigit(body);
}
gs1CheckDigit('03600029145'); // 2
isValidGtin('4006381333931'); // true
isbn10CheckDigit('080442957'); // 'X'
isbn10To13('0-306-40615-2'); // '9780306406157'
import re
def gs1_check_digit(body: str) -> int:
"""GS1 mod-10 check digit: UPC-A, EAN-13, EAN-8, GTIN-14, ISBN-13, SSCC."""
if not re.fullmatch(r"[0-9]+", body): # not \d or isdigit(): both accept non-ASCII digits
raise ValueError(f"digits only: {body!r}")
total = sum(int(d) * (3 if i % 2 == 0 else 1) for i, d in enumerate(reversed(body)))
return (10 - total % 10) % 10
def is_valid_gtin(code: str) -> bool:
s = re.sub(r"[\s-]", "", str(code))
if not re.fullmatch(r"[0-9]{8}|[0-9]{12,14}", s):
return False
return gs1_check_digit(s[:-1]) == int(s[-1])
def isbn10_check_digit(body: str) -> str:
if not re.fullmatch(r"[0-9]{9}", body):
raise ValueError(f"need 9 digits: {body!r}")
total = sum(int(d) * w for d, w in zip(body, range(10, 1, -1)))
r = (11 - total % 11) % 11
return "X" if r == 10 else str(r)
def is_valid_isbn10(code: str) -> bool:
s = re.sub(r"[\s-]", "", code).upper()
return bool(re.fullmatch(r"[0-9]{9}[0-9X]", s)) and isbn10_check_digit(s[:9]) == s[9]
assert gs1_check_digit("03600029145") == 2
assert is_valid_gtin("4006381333931")
assert isbn10_check_digit("080442957") == "X"
Check digit for the body in A2 (e.g. 03600029145 -> 2):
=MOD(10 - MOD(SUMPRODUCT(--MID(A2, LEN(A2) + 1 - SEQUENCE(LEN(A2)), 1), 2 - (-1) ^ SEQUENCE(LEN(A2))), 10), 10)
TRUE if the full code in A2 is valid (e.g. 036000291452):
=MOD(SUMPRODUCT(--MID(A2, LEN(A2) + 1 - SEQUENCE(LEN(A2)), 1), 2 + (-1) ^ SEQUENCE(LEN(A2))), 10) = 0
ISBN-10 check character for 9 digits in A2 (e.g. 080442957 -> X):
=LET(r, MOD(11 - MOD(SUMPRODUCT(--MID(A2, SEQUENCE(9), 1), 11 - SEQUENCE(9)), 11), 11), IF(r = 10, "X", r))
SEQUENCE(LEN(A2)) numbers the positions from the right, and 2 - (-1)^n turns them into weights 3, 1, 3, 1. The validation formula uses 2 + (-1)^n so the check digit gets weight 1 and the formula only has to test for a multiple of 10. Older Excel versions don't have SEQUENCE; use ROW(INDIRECT("1:" & LEN(A2))) instead.
CREATE OR REPLACE FUNCTION gs1_check_digit(body text) RETURNS int
LANGUAGE sql IMMUTABLE STRICT AS $$
SELECT ((10 - SUM(substr(body, length(body) + 1 - i, 1)::int
* CASE WHEN i % 2 = 1 THEN 3 ELSE 1 END) % 10) % 10)::int
FROM generate_series(1, length(body)) AS i
$$;
CREATE OR REPLACE FUNCTION is_valid_gtin(code text) RETURNS boolean
LANGUAGE sql IMMUTABLE STRICT AS $$
SELECT CASE
WHEN code !~ '^([0-9]{8}|[0-9]{12,14})$' THEN false
ELSE gs1_check_digit(left(code, -1)) = right(code, 1)::int
END
$$;
SELECT gs1_check_digit('03600029145'); -- 2
SELECT code FROM products WHERE NOT is_valid_gtin(code); -- rows to fix
MySQL 8 and SQLite don't have generate_series by default. Generate the positions with a WITH RECURSIVE CTE and replace ::int with CAST(... AS UNSIGNED) in MySQL or CAST(... AS INTEGER) in SQLite. The arithmetic stays the same.
Validating vs generating
Generating happens once, when you create a number. If you license a GS1 company prefix, you take the prefix and an item reference, calculate the check digit, and the GTIN is complete. If you buy individual UPCs instead (we recommend UPCs.com, at $10 per code; see how to get a UPC barcode), they arrive with the check digit already calculated. Never calculate your way to numbers you haven't licensed or bought, because a correct check digit on someone else's number is still someone else's number.
Validating happens every time a code enters your systems: a scanner read, a supplier spreadsheet, a form field. Validate at that point and reject bad codes. Never "fix" a failed check digit by recalculating it. If someone typed 036000291453, recalculating turns the typo into 036000291452. That might be what they meant, or the typo might be in a different digit, in which case you've just stored a valid-looking GTIN for the wrong product. Send the code back to whoever entered it.
For a whole file, validate the check digits locally first. It's free and instant. Then look up the survivors with bulk lookup or the batch API to find out which ones are real products.
Common bugs
-
Storing codes as numbers. JavaScript numbers, Excel cells and
INT/BIGINTcolumns all drop leading zeros. Weighting from the right means the check digit still comes out right, but the code itself is now broken for lookups and joins. JavaScript numbers can't represent every integer above 2^53 (a 16-digit number), so an 18-digit SSCC parsed withNumber()can come back wrong. Keep identifiers as strings. -
Weighting from the left. Code that hard-codes EAN-13's left-to-right pattern (1, 3, 1, 3...) gives wrong answers for UPC-A. On
03600029145it returns 8 instead of 2. -
Returning 10. If you leave off the final
% 10, a sum like 90 produces a "check digit" of 10.30614141000990is a valid GTIN-14 that hits exactly this case. - Passing in the full code. Generators expect the body without the check digit. If you pass all 12 digits of a UPC to a generator, you get the check digit of a 12-digit body, which is meaningless.
-
Loose digit tests. Python's
str.isdigit()and\dboth accept non-ASCII digits such as²or٣. Use[0-9]. -
Mishandling X. Accept lowercase
xon input, allow it only in the last position, and only for ISBN-10.
Frequently asked questions
Take the first 11 digits of the UPC and multiply them alternately by 3 and 1, starting with 3 on the leftmost digit, which is the same as starting with 3 on the rightmost. Add the products, then find how much you need to reach the next multiple of 10. For 03600029145 the sum is 58, so the check digit is 2 and the full UPC is 036000291452.
No. A GS1 check digit is always a single digit from 0 to 9. When the weighted sum is already a multiple of 10, the check digit is 0, which is why correct code applies a final modulo 10 after subtracting from 10. Only ISBN-10, which uses modulo 11, can produce a value of 10, and it writes that value as the letter X.
An X at the end of an ISBN-10 is the check digit and stands for the number 10. ISBN-10 calculates its check digit modulo 11, so the result can be anything from 0 to 10, and X keeps the code at ten characters. The X disappears when you convert to ISBN-13, which uses the GS1 modulo 10 method; 0-8044-2957-X becomes 9780804429573.
No. A valid check digit only shows the number is well formed and has not been mistyped in a detectable way. About one in ten random numbers of the right length will pass the check. To know whether a GTIN is actually assigned to a product, look it up in a barcode database or verify the company prefix with GS1; the check digit cannot tell you who owns the number.
Yes. UPC-A, EAN-13, EAN-8, GTIN-14 and ISBN-13 all use the same GS1 modulo 10 algorithm with weights of 3 and 1 applied from the rightmost digit. The tables look different only because an EAN-13 body has 12 digits, so reading left to right its weights start at 1, while an 11-digit UPC body starts at 3. One function handles every length.
Next steps
- What is a GTIN?: how 8, 12, 13 and 14-digit codes nest, and where the indicator digit fits.
- Barcode lookup in Google Sheets and Excel: pair the validation formula with a lookup formula.
- Barcode lookup in JavaScript and Node.js: validate first, then call the API only for codes that pass.
- API documentation: confirm that a valid code belongs to a real product.
- Barcode not found?: what to do when a code passes the check but isn't in any database.