How to Extract Raw Binary Data from QR Codes in JavaScript

A barcode or bar code is a method of representing data in a visual, machine-readable form. There are all kinds of barcodes and mainly, they can be divided into 1D barcodes and 2D barcodes.

We can get binary data from barcodes and there are different ways to interpret the data. For example, in EAN-13, code 1110010 in the right hand represents the digit 0. As for QR code, which is two-dimensional and can store more data, has several modes to represent data:

Input mode Mode indicator Max. characters Possible characters, default encoding
Numeric only 1 7,089 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Alphanumeric 2 4,296 0–9, A–Z (upper-case only), space, $, %, *, +, -, ., /, :
Binary/byte 4 2,953 ISO/IEC 8859-1
Kanji/kana 8 1,817 Shift JIS X 0208
Structured Append 3 unlimited Not specific

PS: structured append is a mode in which the data is divided into several barcodes.

In this article, we are going to create a JavaScript library for reading the binary data of a barcode, with a special focus on QR code as this format has multiple modes.

What you’ll build: A reusable TypeScript library that extracts raw binary data from QR codes and other barcodes, handling all five QR encoding modes (numeric, alphanumeric, byte, Kanji, structured append), powered by Dynamsoft Barcode Reader.

Key Takeaways

  • QR codes support five data encoding modes — numeric, alphanumeric, binary/byte, Kanji, and structured append — each requiring different decoding logic to extract raw bytes.
  • The Uint8Array from Dynamsoft Barcode Reader’s item.bytes property contains the raw binary payload, which can be decoded as text, rendered as an image, or saved as a blob.
  • Structured append mode splits data across multiple QR codes; the reader must sort by page number and concatenate byte arrays before interpreting the result.
  • This library is packaged as an NPM module with Vite, making it reusable in any JavaScript or TypeScript project.

Common Developer Questions

  • How do I extract raw byte array data from a QR code in JavaScript?
  • How do I decode binary-encoded images or files stored inside QR codes?
  • How do I read and merge structured append QR codes in TypeScript?

Prerequisites

Dynamsoft Barcode Reader is used for reading the barcodes. Get a 30-day free trial license to follow along.

You also need Node.js installed to scaffold the project with Vite.

Step 1: Create a New TypeScript Project

Create a new project with Vite and the typescript template:

npm create vite@latest BarcodeDataReader -- --template vanilla-ts

Step 2: Define Barcode Data Interfaces

Define several interfaces and an enum for reading the binary data of barcodes.

  • Define a BarcodeDetail interface:

     export interface BarcodeDetail {
       mode?:number; //The data encoding mode
       model?:number; //Number of models
       errorCorrectionLevel?:number;
       columns?:number; //The column count
       rows?:number; //The row count
       page?:number; //Position of the particular code in structured append codes
       totalPage?:number; //Total number of structured append codes
       parityData?:number; //A value obtained by XORing byte by byte the ASCII/JIS values of all the original input data before division into symbol blocks.
       version?:number; //The version of the code.
     }
    
  • Define a Barcode interface:

     export interface Barcode {
       bytes:Uint8Array;
       details:BarcodeDetail;
       barcodeType?:string;
     }
    

    The binary data is stored as Uint8Array.

  • Define a ReadingResult interface:

     export interface ReadingResult {
       text?:string;
       img?:HTMLImageElement;
       blob?:Blob;
     }
    

    We can get the result as plain text, an HTML image element or blob.

    The type is decided by the DataType passed to the binary data reader.

     export enum DataType {
       text = 0,
       image = 1,
       unknown = 2
     }
    

Step 3: Build the Binary Data Reader Class

  1. Create a new BarcodeDataReader.ts file with the following template:

    export class BarcodeDataReader{
      async read(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{
        if (barcodes.length == 0) {
          throw new Error("No barcodes given");
        }
        let results:ReadingResult[] = [];
        return results;
      }
    }
    
  2. Read the binary data based on different modes.

    async read(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{
      if (barcodes.length == 0) {
        throw new Error("No barcodes given");
      }
      let results:ReadingResult[] = [];
      const mode = barcodes[0].details.mode;
      if (mode == 1) {
        results = this.readNumericBarcodes(barcodes);
      }else if (mode == 2) {
        results = this.readAlphaNumericBarcodes(barcodes);
      }else if (mode == 4) {
        results = await this.readByteEncodingBarcodes(barcodes,dataType);
      }else if (mode == 8) {
        results = this.readKanjiBarcodes(barcodes);
      }else if (mode == 3) {
        results = await this.readStructuredAppendBarcodes(barcodes,dataType);
      }else {
        results = await this.readByteEncodingBarcodes(barcodes,DataType.text);
      }
      return results;
    }
    
  3. Implement reading the data in numeric, alphanumeric and Kanji modes. The three modes’ data is plain text and they can share a similar logic.

    private readAlphaNumericBarcodes(barcodes:Barcode[]):ReadingResult[]{
      return this.decodeText(barcodes,"ASCII");
    }
    
    private readNumericBarcodes(barcodes:Barcode[]):ReadingResult[]{
      return this.decodeText(barcodes,"ASCII");
    }
    
    private readKanjiBarcodes(barcodes:Barcode[]):ReadingResult[]{
      return this.decodeText(barcodes,"SHIFT-JIS");
    }
    
    private decodeText(barcodes:Barcode[],encoding:string){
      let results:ReadingResult[] = [];
      for (let index = 0; index < barcodes.length; index++) {
        const barcode = barcodes[index];
        const decoder = new TextDecoder(encoding);
        const text = decoder.decode(barcode.bytes);
        let result = {
          text:text
        }
        results.push(result);
      }
      return results;
    }
    
  4. Implement reading the data in byte mode.

    As the data type is unknown in byte mode, we have to get the reading results based on the user-specified data type.

    If the data type is text, we have to detect the encoding. The chardet library is used here for detection.

    private async readByteEncodingBarcodes(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{
      let results:ReadingResult[] = [];
      for (let index = 0; index < barcodes.length; index++) {
        const barcode = barcodes[index];
        let result:ReadingResult = await this.getResultBasedOnDataType(barcode.bytes,dataType);
        results.push(result);
      }
      return results;
    }
    
    async getResultBasedOnDataType(data:Uint8Array,dataType:DataType) {
      let result:ReadingResult;
      if (dataType == DataType.text) {
        const charset = chardet.analyse(data);
        const decoder = new TextDecoder(charset[0].name);
        const text = decoder.decode(data);
        result = {
          text:text
        }
      }else if (dataType == DataType.image) {
        const img = await this.getImageFromUint8Array(data);
        result = {
          img:img
        }
      }else{
        result = {
          blob:this.getBlobFromUint8Array(data)
        }
      }
      return result;
    }
       
    getBlobFromUint8Array(data:Uint8Array) {
      return new Blob([data]);
    }
    getImageFromUint8Array(data:Uint8Array):Promise<HTMLImageElement>{
      return new Promise<HTMLImageElement>((resolve, reject) => {
        const img = document.createElement("img");  
        const blob = this.getBlobFromUint8Array(data);
        img.onload = function(){
          resolve(img);
        }
        img.onerror = function(error) {
          console.error(error);
          reject(error);
        }
        img.src = URL.createObjectURL(blob);
        console.log(img.src)
      })
    }
    
  5. Implement reading the data in structured append mode.

    We have to sort the barcodes based on their page numbers, merge the bytes into one and then get the result.

    private async readStructuredAppendBarcodes(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{
      let results:ReadingResult[] = [];
      barcodes.sort((a, b) => (a.details.page ?? 0) - (b.details.page ?? 0))
      let concatedData:Uint8Array = new Uint8Array();
      for (let index = 0; index < barcodes.length; index++) {
        const barcode = barcodes[index];
        let merged = new Uint8Array(barcode.bytes.length + concatedData.length);
        merged.set(concatedData);
        merged.set(barcode.bytes, concatedData.length);
        concatedData = merged;
      }
      let result = await this.getResultBasedOnDataType(concatedData,dataType);
      results.push(result);
      return results;
    }
    

Step 4: Test QR Code Binary Data Extraction

We can then update index.html to use Dynamsoft Barcode Reader to read QR codes and this library to read the binary data.

Here is the basic code:

let router = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
//read barcodes from an image
let result = await router.capture(document.getElementById("image"),"ReadBarcodes_Balance");
let dataType = document.getElementById("dataTypeSelect").selectedIndex;
let barcodes = [];
for (let index = 0; index < result.items.length; index++) {
  const item = result.items[index];
  if (item.type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE) {
    let data = new Uint8Array(item.bytes.length);
    data.set(item.bytes);
    let barcode = {
      bytes:data,
      details:item.details
    }
    barcodes.push(barcode);
  }
}

let results = await dataReader.read(barcodes,dataType)
console.log(results);

You can visit this online demo to have a try.

If you don’t have QR codes at hand, you can use the QR codes and the QR code generator in this article.

Here is a screenshot of a test of reading two QR codes which have an image encoded:

Sample

Step 5: Package and Publish as an NPM Library

We can publish it as a library onto NPM for ease of use.

  1. Install devDependencies:

    npm install -D @types/node vite-plugin-dts
    
  2. Create a new vite.config.ts file:

    // vite.config.ts
    import { resolve } from 'path';
    import { defineConfig } from 'vite';
    import dts from 'vite-plugin-dts';
    // https://vitejs.dev/guide/build.html#library-mode
    export default defineConfig({
      build: {
        lib: {
          entry: resolve(__dirname, 'src/index.ts'),
          name: 'barcode-data-reader',
          fileName: 'barcode-data-reader',
        },
      },
      plugins: [dts()],
    });
    
  3. Add the entry points of our package to package.json.

    {
      "main": "./dist/barcode-data-reader.umd.cjs",
      "module": "./dist/barcode-data-reader.js",
      "types": "./dist/index.d.ts",
      "exports": {
        "import": {
          "types": "./dist/index.d.ts",
          "default": "./dist/barcode-data-reader.js"
        },
        "require": {
          "types": "./dist/index.d.ts",
          "default": "./dist/barcode-data-reader.umd.cjs"
        }
      },
      "files": [
        "dist/*.css",
        "dist/*.js",
        "dist/*.cjs",
        "dist/*.d.ts"
      ]
    }
    

Run npm run build. Then, we can have the packaged files in the dist.

Common Issues and Edge Cases

  • Encoding detection fails for short byte-mode payloads. The chardet library needs a minimum number of bytes for reliable charset detection. For very short strings (under ~20 bytes), force a known encoding like UTF-8 instead of relying on auto-detection.
  • Structured append QR codes arrive out of order. If your scanner returns results in an unpredictable order, always sort by details.page before concatenating. Missing pages will produce corrupted output — validate that barcodes.length equals details.totalPage before merging.
  • Binary blob URLs are not revoked. Each call to URL.createObjectURL() allocates memory. In long-running apps that decode many images from QR codes, call URL.revokeObjectURL() after the image has loaded to prevent memory leaks.

Source Code

Get the source code of the library to have a try:

https://github.com/tony-xlh/barcode-data-reader