Skip to the content.

How-To: Create and Use External Readers

This guide explains how to create custom barcode reader plugins and integrate them into Quagga2.

Using Existing External Readers

QR Code Reader

The most common external reader is quagga2-reader-qr for QR code support:

import Quagga from '@ericblade/quagga2';
import QRReader from 'quagga2-reader-qr';

// Register the external reader
Quagga.registerReader('qr', QRReader);

// Use it in your config
Quagga.init({
  decoder: {
    readers: ['qr']
  }
});

External Reader Priority

External readers follow the same priority rules as built-in readers:

  1. Registration first: Call Quagga.registerReader(name, ReaderClass) before using the reader
  2. Position determines priority: The reader’s position in the readers array determines when it attempts to decode
  3. First success wins: The first reader to return a valid result is used

Example: Prioritizing External Readers

// Register external reader
Quagga.registerReader('my_custom_reader', MyCustomReader);

// External reader tried first, then built-in readers
Quagga.init({
  decoder: {
    readers: ['my_custom_reader', 'ean_reader', 'code_128_reader']
  }
});

Example: External Reader as Fallback

// Register external reader
Quagga.registerReader('my_fallback_reader', MyFallbackReader);

// Built-in readers tried first, external as fallback
Quagga.init({
  decoder: {
    readers: ['ean_reader', 'code_128_reader', 'my_fallback_reader']
  }
});

Creating Custom Readers

Quagga2 exports the BarcodeReader prototype that you can extend to create custom readers.

Basic Reader Structure

import { Readers } from '@ericblade/quagga2';

const { BarcodeReader } = Readers;

class MyCustomReader extends BarcodeReader {
  FORMAT = 'my_custom_format';
  
  constructor(config, supplements) {
    super(config, supplements);
    // Custom initialization
  }
  
  decode(row, start) {
    // Implement barcode decoding logic
    // row: Array<number> - binary line data
    // start: number - starting position
    
    // Return null if no barcode found
    // Return Barcode object if successful
    return null;
  }
}

export default MyCustomReader;

Reader Return Format

Your decode() method should return either null (no match) or a result object:

{
  code: '1234567890',        // The decoded barcode value
  start: 0,                   // Start position in the row
  end: 100,                   // End position in the row
  startInfo: { start: 0, end: 10 },  // Start pattern info
  format: 'my_custom_format', // Your reader's format name
  decodedCodes: [...]         // Optional: decoded character info
}

Pattern Matching Utilities

BarcodeReader provides useful methods for pattern matching:

Example: Simple Pattern Reader

class SimpleBarReader extends BarcodeReader {
  FORMAT = 'simple_bar';
  START_PATTERN = [1, 1, 1];  // Three bars pattern
  
  decode(row, start) {
    if (row) {
      this._row = row;
    }
    
    // Find start pattern
    const startInfo = this._findPattern(this.START_PATTERN, 0, false);
    if (!startInfo) {
      return null;
    }
    
    // Decode the barcode content
    const result = this._decodeContent(startInfo.end);
    if (!result) {
      return null;
    }
    
    return {
      code: result.code,
      start: startInfo.start,
      end: result.end,
      startInfo: startInfo,
      format: this.FORMAT
    };
  }
  
  _decodeContent(offset) {
    // Implement your decoding logic
    return null;
  }
}

Image-Based Readers

For non-linear barcodes (like QR codes), implement decodeImage() instead:

class MyImageReader extends BarcodeReader {
  FORMAT = 'my_image_format';
  
  // Override decode to return null (not a linear barcode)
  decode() {
    return null;
  }
  
  // Implement image-based decoding
  async decodeImage(imageWrapper) {
    // imageWrapper.data - pixel data
    // imageWrapper.size - { x, y } dimensions
    
    // Process the image and decode
    const result = await this.processImage(imageWrapper);
    
    if (!result) {
      return null;
    }
    
    return {
      codeResult: {
        code: result.data,
        format: this.FORMAT
      }
    };
  }
}

Testing Your Reader

  1. Unit tests: Test pattern matching and decoding logic in isolation
  2. Integration tests: Use Quagga.decodeSingle() with test images
  3. Live testing: Test with real camera input

Example Test

import Quagga from '@ericblade/quagga2';
import MyCustomReader from './my-custom-reader';

Quagga.registerReader('my_custom', MyCustomReader);

const result = await Quagga.decodeSingle({
  src: './test-image.jpg',
  decoder: {
    readers: ['my_custom']
  }
});

console.log('Result:', result?.codeResult?.code);

Best Practices

  1. Set unique FORMAT: Use a distinctive format name to identify your reader
  2. Handle edge cases: Return null gracefully when patterns don’t match
  3. Validate checksums: Implement checksum validation when the format supports it
  4. Consider performance: Optimize pattern matching for real-time scanning
  5. Test thoroughly: Test with various image qualities and conditions

← Back to How-To Guides