GS1 DataMatrix for EU FMD and US DSCSA: what is in the code and what to check on the line
Prescription medicines in the European Union and the United States carry a serialized GS1 DataMatrix code on every pack. The rules come from two different laws — the Falsified Medicines Directive (FMD) in the EU and the Drug Supply Chain Security Act (DSCSA) in the US — but the code itself is built the same way: a product number, a serial number, a batch and an expiry date, as GS1 fields in one ECC 200 symbol.
What is in the code
| Field | GS1 AI | EU FMD | US DSCSA |
|---|---|---|---|
| Product number | 01 — GTIN, 14 digits | Product code (GTIN, or a national number in GTIN format) | NDC, carried inside the GTIN |
| Serial number | 21 — up to 20 characters | Required, unique per pack | Required, unique per package |
| Batch / lot | 10 — up to 20 characters | Required | Required |
| Expiry date | 17 — YYMMDD | Required | Required |
| National reimbursement number | 710–714 and others | Where a member state requires it | — |
Two details matter for software that reads these codes:
- Field order is not fixed. Packs in the EU often go 01, 17, 10, 21; many US packs go 01, 21, 17, 10. A reader must accept any order.
- Variable-length fields end with a separator. Batch (10) and serial (21) have no fixed length, so when another field follows, the symbol carries the GS character (ASCII 29, FNC1 in the symbol) after them. Lose it, and the next field merges into the serial number — see The GS separator in GS1 codes.
What you can check on the line, and what you cannot
A camera on a packaging line works offline, and that defines what it can verify:
- On the line: the code reads; all required fields are present; the GTIN check digit is correct; the expiry date is a valid YYMMDD and matches the job; the batch matches the job; the serial number has not been printed twice in this run; the printed string matches the one issued for this pack.
- Not on the line: whether the serial number is registered and active. In the EU that is the job of the European Medicines Verification System (EMVS) — the pack is verified and decommissioned against it at the pharmacy. In the US it is the job of the trading partners' systems and verification requests between them. A line can only make sure that the uploaded serial numbers and the printed ones are the same.
Parsing the code in Python
The example reads a pack photo with the dmr package and splits the result into fields in any order. It handles the two-digit AIs used on medicine packs; a full parser also needs the four-digit ones (such as 7xx).
import dmr
# Fixed-length AIs (GS1 General Specifications): length of the data after the AI.
FIXED = {"00": 18, "01": 14, "02": 14, "11": 6, "13": 6, "15": 6, "16": 6, "17": 6, "20": 2}
GS = "\x1d"
def parse_gs1(text: str) -> dict:
"""Split a GS1 element string into {AI: value}, in any field order.
Simplified: two-digit AIs only (01, 17, 10, 21, 11 ...)."""
fields, i = {}, 0
while i < len(text):
if text[i] == GS:
i += 1
continue
ai = text[i:i + 2]
if ai in FIXED:
n = FIXED[ai]
fields[ai] = text[i + 2:i + 2 + n]
i += 2 + n
else: # variable length (10, 21): up to the next GS or the end
end = text.find(GS, i)
end = len(text) if end < 0 else end
fields[ai] = text[i + 2:end]
i = end
return fields
def gtin_ok(gtin: str) -> bool:
digits = [int(c) for c in gtin]
total = sum(d * (3 if i % 2 == 0 else 1) for i, d in enumerate(reversed(digits[:-1])))
return (10 - total % 10) % 10 == digits[-1]
img = dmr.Image("pack.jpg", dmr.LoadAs.GRAY)
result = dmr.Scanner(dmr.Settings.single()).scan(img.view())
if result.ok:
f = parse_gs1(result.text.decode("ascii"))
print(f, "GTIN check digit ok:", gtin_ok(f["01"]))For an EU-style code (01)09506000134352(17)271231(10)ABC123(21)SN12345XYZ it prints {'01': '09506000134352', '17': '271231', '10': 'ABC123', '21': 'SN12345XYZ'}; a US-style code with serial number first gives the same fields in its own order.
One caution about the reader's built-in GS1 filter (--gs1, Settings::gs1): it is made for Chestny ZNAK codes and accepts only a serial number (21) straight after the GTIN (01). An FMD code ordered 01, 17, 10, 21 is rejected by it. For pharma codes read in the default mode, as above, and check the fields in your program.
Printing test codes
For line commissioning and label templates it helps to have codes with known content. The dmgen generator validates GS1 fields before encoding (AI lengths, GTIN check digit) and writes PNG or JPEG:
dmgen --gs1 "(01)09506000134352(17)271231(10)ABC123(21)SN12345XYZ" -o fmd.png
dmgen --gs1 "(01)00312345678906(21)10203040506070(17)280630(10)LOT42" -o dscsa.pngThe same is available without installing anything in the online generator.
Try it
The libscanner libraries read and create GS1 DataMatrix codes with any field order: pip install libscanner-dmr for reading, pip install libscanner-dmgen for generation (Windows x64, Linux x86-64, ARM64, ARMv7). Check a pack photo in the online scanner; headers and examples are on GitHub.
Chestny ZNAK is a trademark of CRPT LLC (ООО «Оператор-ЦРПТ»); GS1 is a trademark of GS1 AISBL. The product is not affiliated with or certified by their owners.