How to Scan and Parse GS1 Barcodes
GS1 is a not-for-profit, international organization developing and maintaining its own standards for barcodes. GS1 defines the rules to encode a barcode content so that it contains information about a product: its GTIN (Global Trade Item Number), the weight or dimensions of the item, its price, the lot/batch code, the date when it was fabricated and so on.
A GS1 barcode is a concatenation of data elements. Every single element starts with an application identifier (AI), a two to four digits number. The number is followed by the actual information.
A data element is delimited either by:
- the end of the whole barcode
- a specification that states that this information has a fixed count of characters or digits
- a special character named FNC1 which is a non-printable character whose byte is 29. It is usually for information with variable length
The GS1 family of barcodes contains various barcode formats. Databar barcodes are most often used to label fresh foods or coupons. Code 128 and ITF-14 are often used in a distribution environment. 2D barcodes like Data Matrix and QR Code are used in a wide range of industries, from retail and manufacturing to logistics and healthcare.
Here are some GS1 barcode examples:
-
GS1 DataBar Stacked
-
GS1 DataMatrix
-
GS1 Composite
In the next part, let’s build a web barcode reader in HTML5 to scan and parse GS1 barcodes with the JavaScript edition of Dynamsoft Barcode Reader used as the barcode reading engine.
Getting started with Dynamsoft Barcode Reader
New Project
Create a new TypeScript project with Vite.
npm create vite@latest gs1barcodereader -- --template vanilla-ts
Add the Dependency
Install Dynamsoft Barcode Reader:
npm install dynamsoft-javascript-barcode@9.6.21
Initialize Dynamsoft Barcode Reader
In main.ts
, initialize a reader instance of Dynamsoft Barcode Reader. A license is needed to use Dynamsoft Barcode Reader. You can apply for one here.
import { BarcodeReader } from "dynamsoft-javascript-barcode";
BarcodeReader.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial
BarcodeReader.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.21/dist/";
let reader:BarcodeReader;
init();
async function init(){
reader = await BarcodeReader.createInstance();
}
Read Barcodes from an Image
-
Add a hidden file input and use a button to trigger it.
HTML:
<div> <button id="decodeImageBtn">Decode an image</button> <input type="file" id="imageFile" style="display:none;"/> </div>
JS:
document.getElementById("decodeImageBtn")?.addEventListener("click",async function(){ if (reader) { document.getElementById("imageFile")?.click(); }else{ alert("Please wait for the initialization of Dynamsoft Barcode Reader."); } });
-
Add the event listener for
change
of the file input. When a new image is selected, display it on the page and read barcodes from it.document.getElementById("imageFile")?.addEventListener("change",function(){ let fileInput = document.getElementById("imageFile") as HTMLInputElement; if (fileInput.files && fileInput.files.length>0) { let file = fileInput.files[0]; let fileReader = new FileReader(); fileReader.onload = function(e){ loadImage(e.target?.result as string); }; fileReader.onerror = function () { console.warn('oops, something went wrong.'); }; fileReader.readAsDataURL(file); } }); function loadImage(dataURL:string){ let img = document.getElementById("selectedImg") as HTMLImageElement; img.onload = async function(){ let results = await reader.decode(img); console.log(results); } img.src = dataURL; }
Parse GS1 Barcodes
Now that we get the GS1 barcode results, we can try to parse them.
Here, we use an open-source GS1 barcode parser in JavaScript.
-
Include the parser in the head of
index.html
.<script src="https://cdn.jsdelivr.net/npm/gs1-barcode-parser@1.0.1/dist/gs1-barcode-parser-1.0.1.min.js"></script>
-
Separate the barcode text in segments.
We mainly have to use FNC1 to separate the text.
text = text.replace(//g,"|"); let segments = text.split("|");
-
Handle special cases. First, GS1 Databar (except Expanded) does not have the application identifier. Second, a GS1 composite code is composed of two barcodes. Normally, a Databar 1D code and a PDF417 2D code. The barcode text of the two are joined with
|
. We have to add the application identifier to the text of the Databar and split the barcode values in a composite code for the parser to work.let segments = text.split("|"); if (result.barcodeFormatString === "GS1 Composite Code" || (result.barcodeFormatString.indexOf("GS1 Databar") != -1 && result.barcodeFormatString.indexOf("Expanded") == -1)) { segments[0] = "01" + segments[0]; //add application identifier }
-
Parse the segments and combine all the results.
let parsedCodeItems:any[] = []; for (let index = 0; index < segments.length; index++) { const segment = segments[index]; const parsedResult = parseBarcode(segment); parsedCodeItems = parsedCodeItems.concat(parsedResult["parsedCodeItems"]); } return parsedCodeItems;
-
List the parsed results in a table.
function buildBarcodeTable(result:TextResult){ const table = document.createElement("table"); const items:{key:string,value:any}[] = []; items.push({key:"Format",value:result.barcodeFormatString}); items.push({key:"Text",value:result.barcodeText}); items.push({key:"Bytes",value:result.barcodeBytes}); try { let codeItems; codeItems = parseGS1Barcode(result); for (let index = 0; index < codeItems.length; index++) { const item = codeItems[index]; if (typeof(item.data) === "object" && "getYear" in item.data) { items.push({key:item.dataTitle,value:item.data.toDateString()}); }else{ items.push({key:item.dataTitle,value:item.data}); } } } catch (error) { console.log(error); } const headRow = document.createElement("tr"); const keyTitleCell = document.createElement("th"); keyTitleCell.innerText = "Key"; keyTitleCell.style.minWidth = "30vw"; const valueTitleCell = document.createElement("th"); valueTitleCell.innerText = "Value"; headRow.appendChild(keyTitleCell); headRow.appendChild(valueTitleCell) table.appendChild(headRow); for (let index = 0; index < items.length; index++) { const item = items[index]; const dataRow = document.createElement("tr"); const keyCell = document.createElement("td"); keyCell.innerText = item.key; const valueCell = document.createElement("td"); valueCell.innerText = item.value; dataRow.appendChild(keyCell); dataRow.appendChild(valueCell); table.appendChild(dataRow); } return table; }
The following is the table for the above GS1 barcode.
Key | Value |
---|---|
Format | GS1 Composite Code |
Text | 14987047104034|172512001023011 |
Bytes | 49,52,57,56,55,48,52,55,49,48,52,48,51,52,49,55,50,53,49,50,48,48,49,48,50,51,48,49,49 |
GTIN | 14987047104034 |
USE BY OR EXPIRY | Wed Dec 31 2025 |
BATCH/LOT | 23011 |
Add Live Scanning with the Camera
Next, we can add the live scanning ability so that the web barcode reader can read GS1 barcodes with the camera.
-
Add elements for the camera.
HTML:
<div id="scanner" style="display:none"> <svg class="dce-bg-loading" viewBox="0 0 1792 1792"><path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z" /></svg> <svg class="dce-bg-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224zm-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5z" /></svg> <div class="dce-video-container"></div> <div class="dce-scanarea"> <div class="dce-scanlight"></div> </div> <div class="sel-container"> <select class="dce-sel-camera"></select> <select class="dce-sel-resolution"></select> </div> <button id="closeBtn">Close</button> </div>
CSS:
#closeBtn { position: fixed; right: 0; top: 0; } #scanner { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: white; } @keyframes dce-rotate{from{transform:rotate(0turn);}to{transform:rotate(1turn);}} @keyframes dce-scanlight{from{top:0;}to{top:97%;}} .dce-bg-loading{animation:1s linear infinite dce-rotate;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;} .dce-bg-camera{display:none;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;} .dce-video-container{position:absolute;left:0;top:0;width:100%;height:100%;} .dce-scanarea{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;} .dce-scanarea .dce-scanlight{display:none;position:absolute;width:100%;height:3%;border-radius:50%;box-shadow:0px 0px 2vw 1px #00e5ff;background:#fff;animation:3s infinite dce-scanlight;user-select:none;} .sel-container{position: absolute;left: 0;top: 0;} .sel-container .dce-sel-camera{display:block;} .sel-container .dce-sel-resolution{display:block;margin-top:5px;}
-
Initialize an instance of Dynamsoft Camera Enhancer and bind it to the UI element we just created.
import { CameraEnhancer } from "dynamsoft-camera-enhancer"; CameraEnhancer.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.5/dist/"; let enhancer:CameraEnhancer; async function init(){ enhancer = await CameraEnhancer.createInstance(); await enhancer.setUIElement(document.getElementById("scanner") as HTMLDivElement); enhancer.setVideoFit("cover"); }
-
Start the camera when the
scan
button is clicked and stop the camera when theclose
button is clicked.document.getElementById("liveScanBtn")?.addEventListener("click",function(){ startScan(); }); document.getElementById("closeBtn")?.addEventListener("click",function(){ stopScan(); }); function startScan(){ if (!reader || !enhancer) { alert("Please wait for the initialization of Dynamsoft Barcode Reader."); return; } enhancer.open(true); } function stopScan(){ enhancer.close(true); }
-
When the camera is opened, start a processing loop to read barcodes from the camera. If a barcode is found, capture the camera frame and list the parsed results.
enhancer.on("played",function(){ startDecodingLoop(); //start the loop when the camera is opened }); function startDecodingLoop(){ stopDecodingLoop(); interval = setInterval(captureAndDecode,50); } function stopDecodingLoop(){ clearInterval(interval); decoding = false; } async function captureAndDecode(){ if (decoding === true) { return; } if (reader && enhancer) { if (enhancer.isOpen() === false) { return; } decoding = true; let frame = enhancer.getFrame(); let results = await reader.decode(frame); if (results.length > 0) { let img = document.getElementById("selectedImg") as HTMLImageElement; img.onload = function(){}; img.src = frame.toCanvas().toDataURL(); listResults(results); stopScan(); } decoding = false; } }
All right, we’ve now finished the web GS1 barcode reader.
Source Code
Check out the source code of the demo to have a try: