qr-code.js

Introduction

QRCode.js is a professional JavaScript/TypeScript library for creating customized QR codes, offering a blend of simplicity and sophistication. With versatile styling options—dot shapes, colors, gradients, embedded images, borders, and text—it enables you to design unique, visually appealing QR codes that work flawlessly with standard scanners. QRCode.js is part of QR-Platform: All-in-One QR Codes Management Solution.

This documentation provides an overview of all available options to help you create the perfect QR code for your needs.

Installation

# Using npm
npm install @qr-platform/qr-code.js

# Using yarn
yarn add @qr-platform/qr-code.js

# Using pnpm
pnpm add @qr-platform/qr-code.js

Basic Usage

import { QRCodeJs } from '@qr-platform/qr-code.js';

// Create a basic QR code
const qrCode = new QRCodeJs({
  data: 'https://example.com',
});

// Render the QR code to a container
qrCode.append(document.getElementById('qr-container'));

Core Options

data

shape

margin

isResponsive

qrOptions

Options that affect the QR code generation algorithm.

typeNumber

mode

errorCorrectionLevel

Layout Options

Options controlling the positioning and scaling of the QR code within its container.

scale

offset

verticalOffset

horizontalOffset

Styling Options

Dots (dotsOptions)

Controls the appearance of individual QR code dots.

type

color

size

gradient

Corner Squares (cornersSquareOptions)

These options override dotsOptions for the three large corner squares of the QR code.

type

color

gradient

Corner Dots (cornersDotOptions)

These options override cornersSquareOptions for the smaller dots within the corner squares.

type

color

gradient

Background (backgroundOptions)

Controls the QR code background.

color

round

gradient

Image Embedding

image

Override Behavior with setImage and useImage

imageOptions

Options for the embedded image.

mode

imageSize

margin

crossOrigin

fill

Gradients

Gradients can be applied to multiple elements: dotsOptions, cornersSquareOptions, cornersDotOptions, backgroundOptions, and imageOptions.fill.

Gradient Structure

Example

{
  dotsOptions: {
    gradient: {
      type: 'linear',
      rotation: Math.PI / 4, // 45 degrees
      colorStops: [
        { offset: 0, color: '#8F00FF' }, // Start color
        { offset: 1, color: '#0080FF' }  // End color
      ]
    }
  }
}

Borders

QRCode.js provides border features:

borderOptions

Options for adding decorative borders around the QR code. Borders can be configured globally using QRCodeJs.setBorder() / QRCodeJs.setBorderId() or on a per-instance basis using the builder pattern initiated with QRCodeJs.useBorder() / QRCodeJs.useBorderId().

hasBorder

thickness

color

radius

noBorderThickness

background

inner

borderOuter

borderInner

decorations

Each decoration object can have these properties:

Example Usage of Borders

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  borderOptions: {
    hasBorder: true,
    thickness: 40,
    color: '#0033CC',
    radius: '10%'
  }
});
// Then create QR code with custom border text
const qrCode = new QRCodeJs({
  data: 'https://example.com',
  borderOptions: {
    hasBorder: true,
    thickness: 40,
    color: '#0033CC',
    radius: '10%',
    decorations: {
      bottom: {
        enableText: true,
        type: 'text',
        value: 'Scan Me',
        style: {
          fontFace: 'Arial',
          fontSize: 24,
          fontColor: '#FFFFFF'
        }
      }
    }
  }
});

Border Text Methods

QRCode.js provides dedicated methods for managing text on QR code borders, allowing for convenient text configuration across all sides.

Static Methods for Global Text Settings

setText()

setTextId()

Builder Methods for Instance-Specific Text

useText()

useTextId()

TextOptions Structure

The TextOptions object allows you to specify text for each side of the QR code border:

interface TextOptions {
  topValue?: string | null;    // Text for top border (null explicitly disables)
  rightValue?: string | null;  // Text for right border
  bottomValue?: string | null; // Text for bottom border
  leftValue?: string | null;   // Text for left border
}

Override Behavior

All text methods accept an optional { override: true } parameter to ensure the text values take precedence over any text settings applied at a later stage:

This is particularly useful when you need to ensure specific text appears regardless of other configuration.

Example: Combining Text Methods with Border Options


// Set global default for all QR codes
QRCodeJs.setText({ 
  topValue: 'SCAN ME',
  bottomValue: 'www.example.com'
});

// Create QR code with custom border that uses the global text
const qrCode = new QRCodeJs({
  data: 'https://example.com',
  borderOptions: {
    hasBorder: true,
    thickness: 40,
    color: '#0033CC',
    radius: '10%'
    // No decoration settings needed - will use the global text
  }
});

// Chain builder methods for more complex setup
const qrChained = QRCodeJs.useBorder('Rounded Border (Large)')
  .useText({ 
    topValue: 'POWERED BY',
    bottomValue: 'QR-PLATFORM'
  })
  .options({ data: 'https://example.com/chained' });

Clearing Text Settings

To remove text from borders:

// Clear global text settings
QRCodeJs.setText(null);

// Clear text on specific sides
QRCodeJs.setText({ 
  topValue: null,     // Explicitly remove top text
  bottomValue: null,  // Explicitly remove bottom text
});

// Use a predefined "empty" template to clear all sides
QRCodeJs.useText('empty-text-options').options({
  data: 'https://example.com'
});

Scan Validation

The QRCode.js library offers functionality to validate that generated QR codes are scannable.

validateScanning()

Node.js Usage

QRCode.js can also be used in Node.js environments.

Installation

Follow the standard installation steps using npm or yarn.

Basic Usage

import { QRCodeJs, Options } from '@qr-platform/qr-code.js/node'; // Import from '@qr-platform/qr-code.js/node'
import fs from 'fs';

const options: Options = {
  data: 'https://example.com',
};

const qrCode = new QRCodeJs(options);

qrCode.serialize().then(svgString => {
  if (svgString) {
    fs.writeFileSync('qrcode.svg', svgString);
    console.log('QR Code saved to qrcode.svg');
  }
});

Key Differences & Considerations

QRCode.js provides a comprehensive system for generating QR codes with advanced features:

Centralized Configuration with Settings (SettingsOptions, setSettings, useSettings)

For a comprehensive way to define or apply a complete QR code configuration in one go, QRCode.js provides:

SettingsOptions Object

The SettingsOptions object allows you to define multiple aspects of a QR code configuration simultaneously:

Refer to the TypeScript Types and Definitions for the full structure.

Static QRCodeJs.setSettings()

The QRCodeJs.setSettings(settings: SettingsOptions | null) static method sets multiple global defaults at once.

Example:

const myGlobalPreset: SettingsOptions = {
  name: 'CompanyStandard',
  data: 'https://company.com/default-link', // Will call QRCodeJs.setData()
  image: 'https://company.com/assets/logo.png', // Will call QRCodeJs.setImage()
  templateId: 'company-wide-template', // Assumes this template ID exists, will call QRCodeJs.setTemplateId()
  options: { // Will call QRCodeJs.setOptions()
    qrOptions: { errorCorrectionLevel: 'H' },
    margin: 5
  }
};

QRCodeJs.setSettings(myGlobalPreset);

// Subsequent QRCodeJs instances will use these global defaults
const qr1 = new QRCodeJs({ /* data will be 'https://company.com/default-link' */ });
const qr2 = new QRCodeJs({ data: 'https://company.com/specific-page' }); // Overrides data from preset

// To clear all global settings:
// QRCodeJs.setSettings(null);

Builder useSettings()

The QRCodeBuilder.useSettings(settings: SettingsOptions) method applies a SettingsOptions object as a new baseline for a specific builder chain.

Example:

const eventSpecificSettings: SettingsOptions = {
  name: 'ConferenceQR',
  data: 'https://conference-event.com/details', // Baseline data for this builder
  image: 'event-logo.png', // Baseline image
  style: { dotsOptions: { type: 'classy', color: '#005FAB' } }, // Baseline style
  borderId: 'event-frame' // Baseline border
};

const qrEvent = QRCodeJs.useTemplate('basic') // Initial template (will be reset by useSettings)
  .useStyle({ dotsOptions: { color: 'red'} }) // This style will also be reset
  .useSettings(eventSpecificSettings) // Resets builder and applies eventSpecificSettings as baseline
  .useOptions({ margin: 20 }) // Further customizes the baseline from eventSpecificSettings
  .options({ data: 'https://conference-event.com/live-updates' }) // Final data override
  .build();

qrEvent.append(document.getElementById('event-qr-container'));

Using Templates, Styles, Borders, Data, Options, and Settings

QRCode.js offers flexible ways to manage configurations, from setting global defaults that apply to all new instances to using a fluent builder pattern for specific instances.

Setting Global Defaults

Static methods on the QRCodeJs class allow you to define default configurations that will be automatically applied to all QRCodeJs instances created after these defaults are set. This is useful for establishing a baseline style or configuration for your application.

Any options provided during the instantiation of new QRCodeJs({...}) or through builder methods will override these global defaults for that specific instance, unless an override: true was used with a static setter for that specific property. Call any of these setters with null to clear the respective global default.

Example: Setting various global defaults

// Set a global template and data with override
QRCodeJs.setTemplate('dots');
QRCodeJs.setData('https://example-global.com', { override: true }); // This data will be hard to override

const qr1 = new QRCodeJs({ /* data will be https://example-global.com */ });
const qrWithDifferentData = new QRCodeJs({ data: 'https://another-link.com' }); // data will still be https://example-global.com due to override

// Using setSettings to define multiple global defaults
const globalBrandSettings: SettingsOptions = {
  templateId: 'brand-template', // Assumes this ID exists
  style: { dotsOptions: { color: '#AA0000' } }, // Dark red dots
  image: 'https://brand.com/logo.svg', // Global brand logo
  data: 'https://brand-default.com', // Default data for this setting
  options: { margin: 10, qrOptions: { errorCorrectionLevel: 'M' } }
};
QRCodeJs.setSettings(globalBrandSettings);
// This will override the previous QRCodeJs.setTemplate('dots').
// However, the data 'https://example-global.com' (set with override:true) will persist.
// All other aspects from globalBrandSettings (style, image, options) will apply.

const qrBrand = new QRCodeJs({ /* data is 'https://example-global.com', other options from globalBrandSettings apply */ });

// Reset all global settings
QRCodeJs.setSettings(null); // This clears all static defaults, including the overridden data.
const qrAfterClear = new QRCodeJs({ data: 'https://new-data.com' }); // Now uses 'https://new-data.com'

Using the Builder Pattern

The static use methods (e.g., QRCodeJs.useTemplate(), QRCodeJs.useStyle(), QRCodeJs.useSettings()) initiate a builder pattern. They return a QRCodeBuilder instance pre-configured with the specified settings. This approach does not set global defaults.

You must chain these calls with .options(finalOptions) (which also builds the instance) or .build() to get the final QRCodeJs instance. The .options() method takes the final configuration, including the required data property (unless provided by useData, useSettings, or a global default with override) and any ultimate overrides.

Example: Builder Pattern Usage

// Start with a template, then layer styles and data
const qrBuilder1 = QRCodeJs.useTemplate('rounded')
  .useStyle({ dotsOptions: { color: '#007BFF' } }) // Blue override for dots
  .useData('Built with template and style')
  .options({ margin: 10 }); // Final options and build

// Using useSettings to establish a baseline for the builder
const eventSettings: SettingsOptions = {
  data: 'https://myevent.com',
  image: 'event-logo.png',
  styleId: 'event-style' // Assumes 'event-style' is a defined style
};
const qrEvent = QRCodeJs.useSettings(eventSettings) // Establishes baseline from eventSettings
  .useText({ topValue: 'SCAN FOR EVENT DETAILS' }) // Adds text to the baseline
  .useOptions({ qrOptions: { errorCorrectionLevel: 'H' } }, { override: true }) // These options take high precedence over final .options()
  .options({ data: 'Final Event Data Override', qrOptions: { errorCorrectionLevel: 'M' } });
  // Final data overrides eventSettings.data.
  // errorCorrectionLevel 'M' from .options() is overridden by 'H' from .useOptions() with override:true.

Configuration Precedence

Understanding the order in which options are applied is key:

  1. Base Defaults: The library’s inherent defaults (baseQRTemplateOptions).
  2. Static Global Defaults: Set by QRCodeJs.setTemplate(), QRCodeJs.setStyle(), QRCodeJs.setData(), QRCodeJs.setOptions(), QRCodeJs.setSettings(), etc.
    • QRCodeJs.setSettings() calls individual static setters, so its components follow this rule.
    • Static setters with { override: true } (e.g., setData('data', { override: true })) will have their specific property take precedence over less specific global defaults or later non-overriding instance options.
  3. Builder Methods:
    • If QRCodeBuilder.useSettings(settings) is called, it resets previous builder steps for that instance and establishes settings as the new baseline.
    • Other builder methods (useTemplate, useStyle, useData, useOptions, etc.) are applied sequentially. If multiple methods affect the same property, later calls generally override earlier ones within the builder chain (either before useSettings or after it on the new baseline).
    • Builder methods with { override: true } (e.g., useData('data', { override: true })) will have their specific property take precedence within the builder’s accumulated state before the final .options() call, overriding values from the final .options() for those specific properties.
  4. Final .options() call on Builder / Constructor Options: Options passed directly here (e.g., new QRCodeJs(options) or builder.options(options)) override global defaults and accumulated builder configurations, unless a global or builder setting for a specific property was set with { override: true }.

In summary for a single property (e.g., data or image):

Complete Examples

Basic QR Code with Custom Dots

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  dotsOptions: {
    type: 'rounded',
    color: '#0033CC',
    size: 12
  }
});

qrCode.append(document.getElementById('qr-container'));

QR Code with Custom Corners and Background

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  shape: 'square',
  qrOptions: {
    errorCorrectionLevel: 'H'
  },
  dotsOptions: {
    type: 'classy',
    color: '#000000'
  },
  cornersSquareOptions: {
    type: 'outpoint',
    color: '#FF0000'
  },
  cornersDotOptions: {
    type: 'dot',
    color: '#FF0000'
  },
  backgroundOptions: {
    color: '#FFECDB',
    round: 0.2
  }
});
const qrCode = new QRCodeJs({
  data: 'https://example.com',
  image: 'https://example.com/logo.png',
  imageOptions: {
    mode: 'center',
    imageSize: 0.3,
    margin: 1,
    crossOrigin: 'anonymous',
    fill: {
      color: 'rgba(255,255,255,1)'
    }
  },
  dotsOptions: {
    type: 'dot',
    color: '#4267B2'
  }
});

QR Code with Border

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  dotsOptions: {
    type: 'rounded',
    color: '#0033CC'
  },
  borderOptions: {
    hasBorder: true,
    thickness: 50,
    color: '#002683',
    radius: '5%'
  }
});

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  dotsOptions: {
    type: 'extraRounded',
    gradient: {
      type: 'radial',
      colorStops: [
        { offset: 0, color: '#8F00FF' },
        { offset: 1, color: '#0080FF' }
      ]
    }
  },
  backgroundOptions: {
    color: '#FFFFFF',
    round: 0.1
  },
  borderOptions: {
    hasBorder: true,
    thickness: 50,
    color: '#002683',
    radius: '40%',
    decorations: {
      top: {
        enableText: true,
        type: 'text',
        value: 'SCAN ME',
        style: {
          fontFace: 'Helvetica',
          fontSize: 28,
          fontColor: '#ffffff',
          letterSpacing: 2,
          fontWeight: 'bold'
        }
      },
      bottom: {
        enableText: true,
        type: 'text',
        style: {
          fontFace: 'Arial',
          fontSize: 20,
          fontColor: '#ffffff'
        }
      }
    },
    borderOuter: {
      color: '#001255',
      thickness: 10
    },
    borderInner: {
      color: '#334499',
      thickness: 5
    }
  }
});

QR Code with Gradients

const qrCode = new QRCodeJs({
  data: 'Gradient Example',
  dotsOptions: {
    type: 'rounded',
    gradient: {
      type: 'linear',
      rotation: Math.PI / 4,
      colorStops: [
        { offset: 0, color: '#ff5733' },
        { offset: 1, color: '#c70039' }
      ]
    }
  },
  backgroundOptions: {
    gradient: {
      type: 'radial',
      colorStops: [
        { offset: 0, color: '#ffffff' },
        { offset: 1, color: '#e0e0e0' }
      ]
    }
  },
  cornersSquareOptions: {
    type: 'dot',
    gradient: {
      type: 'linear',
      rotation: 0,
      colorStops: [
        { offset: 0, color: '#c70039' },
        { offset: 1, color: '#900c3f' }
      ]
    }
  }
});

QR Code with Border Layout Adjustments

const qrCode = new QRCodeJs({
  data: 'Layout within Border',
  scale: 0.75, // Scale the QR code down to 75% within the border
  offset: -15, // Move the QR code up slightly within the border
  dotsOptions: {
    type: 'square',
    color: '#333333'
  },
  borderOptions: {
    hasBorder: true,
    thickness: 60,
    color: '#CCCCCC',
    radius: '10%',
    decorations: {
      bottom: {
        enableText: true,
        type: 'text',
        value: 'SCALED & OFFSET',
        style: {
          fontFace: 'Arial',
          fontSize: 24,
          fontColor: '#555555',
          fontWeight: 'normal'
        }
      }
    }
  }
});

Circular QR Code with Custom Styling

const qrCode = new QRCodeJs({
  data: 'https://example.com',
  shape: 'circle',
  dotsOptions: {
    type: 'rounded',
    color: '#6200EA'
  },
  cornersDotOptions: {
    type: 'dot',
    color: '#00C853'
  },
  cornersSquareOptions: {
    type: 'rounded',
    color: '#00C853'
  },
  backgroundOptions: {
    color: '#FFFFFF'
  }
});

API Reference

Constructors

new QRCodeJs(options: QRCodeJsOptions)

Methods

append()

Appends the QR code to a container element.

qrCode.append(
  container: HTMLElement,
  options?: { clearContainer?: boolean }
): QRCodeJs | undefined

serialize()

Converts the QR code to an SVG string.

qrCode.serialize(inverted?: boolean): Promise<string | undefined>

download()

Downloads the QR code as a file.

qrCode.download(
  downloadOptions?: { 
    name?: string; 
    extension: 'svg' | 'png' | 'jpeg' | 'webp' 
  },
  canvasOptions?: CanvasOptions
): Promise<void>

update()

Updates the QR code with new options.

qrCode.update(options?: RecursivePartial<Options>): void

validateScanning()

Validates that the QR code is scannable.

qrCode.validateScanning(
  validatorId?: string,
  debug?: boolean
): Promise<ScanValidatorResponse>

Metadata Methods

These helper methods allow attaching or retrieving metadata on a QR code instance.

qrCode.setId(id?: string): this
qrCode.setName(name?: string): this
qrCode.setDescription(description?: string): this
qrCode.setMetadata(metadata?: Record<string, any>): this

qrCode.getId(): string | undefined
qrCode.getName(): string | undefined
qrCode.getDescription(): string | undefined
qrCode.getMetadata(): Record<string, any> | undefined
qrCode.getSettings(): SettingsOptions & { options: Options }

Static Methods

These methods are called directly on the QRCodeJs class (e.g., QRCodeJs.setTemplate()).

Configuration Defaults & Builder Initiators

The following static methods are available on the QRCodeJs class.

Global Defaults:

All set... methods return typeof QRCodeJs for chaining.

Builder Initiators:

These methods initiate a QRCodeBuilder instance.

All use... methods return a QRCodeBuilder instance for chaining.

Example Signatures (Illustrative):

// Global Defaults
static setData(data: string | null, overrideOpts?: { override?: boolean }): typeof QRCodeJs;
static setOptions(options: RecursivePartial<Options> | null, overrideOpts?: { override?: boolean }): typeof QRCodeJs;
static setSettings(settings: SettingsOptions | null): typeof QRCodeJs;

// Builder Initiators & Methods
static useData(data: string, overrideOpts?: { override?: boolean }): QRCodeBuilder;
static useOptions(options: RecursivePartial<Options>, overrideOpts?: { override?: boolean }): QRCodeBuilder;
static useSettings(settings: SettingsOptions): QRCodeBuilder;

// (Other set... and use... methods follow similar patterns)

Fluent Builder Pattern (useTemplate, useStyle, useSettings, build, etc.)

QRCode.js offers a fluent builder pattern for a more readable and chainable way to configure and create QR codes, especially when combining templates, styles, and custom options.

Overview

Instead of passing all options to the constructor, you can start with a base template, style, border, or image using QRCodeJs.useTemplate(), QRCodeJs.useStyle(), QRCodeJs.useBorder(), or QRCodeJs.useImage(). These methods return a QRCodeBuilder instance. You can then chain further .useTemplate(), .useStyle(), .useBorder(), .useImage(), and finally .options() or .build() to finalize the configuration.

How Builder Methods Work Together

You can chain useTemplate, useStyle, useBorder, and useImage calls. The options are merged progressively. If multiple methods define the same option (e.g., dotsOptions.color from a template and a style, or image from useImage and a template), the option from the last applied method in the chain for that specific property will generally take precedence. The final .options() call provides the ultimate override.

Examples

1. Start with a Template, Add Options:

const qrFromTemplate = QRCodeJs.useTemplate('rounded') // Start with 'rounded' template
  .options({ // Merge specific options
    data: 'Data for rounded QR',
    margin: 10
  })
  .build(); // Build the final instance

qrFromTemplate.append(document.getElementById('qr-container-1'));

2. Start with a Style, Add Options:

const myStyle = {
  dotsOptions: { type: 'dots', color: '#FF6347' }, // Tomato dots
  backgroundOptions: { color: '#F0F8FF' } // AliceBlue background
};

const qrFromStyle = QRCodeJs.useStyle(myStyle) // Start with custom style
  .options({ // Merge specific options
    data: 'Data for styled QR',
    qrOptions: { errorCorrectionLevel: 'H' }
  })
  .build();

qrFromStyle.append(document.getElementById('qr-container-2'));

3. Chain Template and Style:

const qrCombined = QRCodeJs.useTemplate('dots') // Start with 'dots' template (black dots)
  .useStyle({ dotsOptions: { color: '#4682B4' } }) // Apply style, overriding dot color to SteelBlue
  .useImage('https://example.com/logo.png') // Add an image
  .options({ data: 'Template + Style + Image' })
  .build();

qrCombined.append(document.getElementById('qr-container-3'));

4. Build without final options:

// Assume 'data' is part of the template or style
const qrBuildDirectly = QRCodeJs.useTemplate({ 
    data: 'Data in template',
    dotsOptions: { type: 'square' }
  })
  .build(); // Build directly if all options are set

qrBuildDirectly.append(document.getElementById('qr-container-4'));

TypeScript Types

Main Options Interface

interface Options {
  // Core Data & QR Algorithm
  data: string; // Required: Content to encode
  qrOptions: {
    typeNumber: number; // Default 0 (auto)
    mode?: Mode; // Default: auto-detected
    errorCorrectionLevel: ErrorCorrectionLevel; // Default 'Q'
  };

  // Overall Shape & Layout
  shape: ShapeType; // Default 'square'
  margin?: number; // Default 0: Space around QR code (pixels)
  isResponsive?: boolean; // Default false: Allow SVG resizing
  scale?: number; // Default 1: Scale QR code within container/border (0-1.5)
  offset?: number; // Default 0: Vertical offset relative to center
  verticalOffset?: number; // Default 0: Absolute vertical offset
  horizontalOffset?: number; // Default 0: Absolute horizontal offset

  // Dot Styling
  dotsOptions: {
    type: DotType; // Default 'square'
    color: string; // Default '#000000'
    gradient?: Gradient;
    size: number; // Default 10 (pixels)
  };

  // Corner Square Styling (Overrides dotsOptions)
  cornersSquareOptions?: {
    type?: CornerSquareType; // Default: inherits dotsOptions.type or 'dot'
    color?: string; // Default: inherits dotsOptions.color or '#000000'
    gradient?: Gradient;
  };

  // Corner Dot Styling (Overrides cornersSquareOptions)
  cornersDotOptions?: {
    type?: CornerDotType; // Default: inherits cornersSquareOptions.type or 'dot'
    color?: string; // Default: inherits cornersSquareOptions.color or '#000000'
    gradient?: Gradient;
  };

  // Background Styling
  backgroundOptions?: {
    color?: string; // Default '#FFFFFF'
    gradient?: Gradient;
    round?: number | string; // Default 0: Corner rounding (0-1 or %)
  } | false; // Set to false to disable background

  // Image Embedding
  image?: string | Buffer | Blob; // Image source (URL, Buffer, Blob)
  imageOptions: {
    mode?: ImageMode; // Default 'center'
    imageSize: number; // Default 0.4: Relative image size (0-1)
    margin: number; // Default 0: Margin around image (dot units)
    crossOrigin?: string; // Default undefined: CORS setting
    fill?: {
      color: string; // Default 'rgba(255,255,255,1)'
      gradient?: Gradient;
    };
  };

  borderOptions?: {
    hasBorder: boolean; // Master switch to enable/disable borders
    thickness: number; // Thickness of the main border in pixels
    color: string; // Color of the main border
    radius: string; // Corner rounding of the border (e.g., '10%', '20px')
    noBorderThickness: number; // Thickness for border sides with disabled decorations
    background?: string; // Background color for the border area
    inner?: {
      radius: string;
      scale: number;
      horizontalOffset: number;
      verticalOffset: number;
    };
    borderOuter?: {
      color: string;
      thickness: number;
    };
    borderInner?: {
      color: string;
      thickness: number;
    };
    decorations?: {
      top?: DecorationOptions;
      right?: DecorationOptions;
      bottom?: DecorationOptions;
      left?: DecorationOptions;
    };
  };
}

// Supporting Interfaces
interface Gradient {
  type: 'linear' | 'radial';
  rotation?: number;
  colorStops: Array<{ offset: number; color: string }>;
}

interface DecorationOptions {
  disabled: boolean;
  enableText: boolean;
  offset: number;
  curveAdjustment: number;
  curveDisabled: boolean;
  curveRadius: string;
  type: 'text' | 'image';
  value: string;
  style?: {
    fontFace: string;
    fontSize: number;
    fontColor: string;
    letterSpacing: number;
    fontWeight: 'normal' | 'bold';
  };
}

// Enums
enum ShapeType {
  square = 'square',
  circle = 'circle'
}

enum Mode {
  numeric = 'numeric',
  alphanumeric = 'alphanumeric',
  byte = 'byte',
  kanji = 'kanji',
  unicode = 'unicode'
}

enum ErrorCorrectionLevel {
  L = 'L', // 7% error recovery
  M = 'M', // 15% error recovery
  Q = 'Q', // 25% error recovery
  H = 'H'  // 30% error recovery
}

enum DotType {
  dot = 'dot',
  square = 'square',
  rounded = 'rounded',
  extraRounded = 'extra-rounded',
  classy = 'classy',
  classyRounded = 'classy-rounded',
  verticalLine = 'vertical-line',
  horizontalLine = 'horizontal-line',
  randomDot = 'random-dot',
  smallSquare = 'small-square',
  tinySquare = 'tiny-square',
  star = 'star',
  plus = 'plus',
  diamond = 'diamond'
}

enum CornerSquareType {
  dot = 'dot',
  square = 'square',
  rounded = 'rounded',
  classy = 'classy',
  outpoint = 'outpoint',
  inpoint = 'inpoint'
}

enum CornerDotType {
  dot = 'dot',
  square = 'square',
  heart = 'heart',
  rounded = 'rounded',
  classy = 'classy',
  outpoint = 'outpoint',
  inpoint = 'inpoint'
}

enum ImageMode {
  center = 'center',
  overlay = 'overlay',
  background = 'background'
}

Metadata Management

QRCode.js provides comprehensive metadata management capabilities for organizing, tracking, and categorizing QR code instances. This section covers the various metadata features available through both instance methods and the builder pattern.

Overview

Metadata in QRCode.js allows you to:

Instance Metadata Methods

Once a QR code instance is created, you can manage its metadata using chainable methods:

Setting Metadata

const qrCode = new QRCodeJs({
  data: 'https://company.com/products/laptop-pro'
});

// Chain metadata methods for clean configuration
qrCode
  .setId('product-laptop-pro-2024')
  .setName('Laptop Pro QR Code')
  .setDescription('QR code for Laptop Pro product page with specifications and pricing')
  .setMetadata({
    productId: 'laptop-pro-001',
    category: 'electronics',
    subcategory: 'laptops',
    brand: 'TechCorp',
    price: 1299.99,
    inStock: true,
    campaign: 'back-to-school-2024',
    trackingEnabled: true,
    analytics: {
      expectedScans: 1000,
      conversionTarget: 50
    }
  });

Retrieving Metadata

// Access individual metadata components
const qrId = qrCode.getId(); // 'product-laptop-pro-2024'
const qrName = qrCode.getName(); // 'Laptop Pro QR Code'
const qrDescription = qrCode.getDescription(); // 'QR code for Laptop Pro...'
const customMetadata = qrCode.getMetadata(); // { productId: 'laptop-pro-001', ... }

// Get comprehensive settings and configuration
const settings = qrCode.getSettings();
console.log('Complete QR Configuration:', settings);

Builder Pattern Metadata

The builder pattern provides elegant metadata assignment during QR code construction:

const enterpriseQR = QRCodeJs.useTemplate('corporate')
  .useId('campaign-summer-2024-001')
  .useName('Summer Campaign Landing Page')
  .useDescription('Primary QR code for summer marketing campaign directing to landing page')
  .useMetadata({
    campaignId: 'summer-2024',
    campaignType: 'seasonal',
    targetAudience: ['millennials', 'gen-z'],
    channels: ['social-media', 'print', 'email'],
    budget: 25000,
    duration: {
      start: '2024-06-01',
      end: '2024-08-31'
    },
    kpis: {
      primary: 'conversion-rate',
      secondary: ['engagement', 'reach']
    },
    geography: {
      regions: ['north-america', 'europe'],
      languages: ['en', 'es', 'fr', 'de']
    }
  })
  .options({
    data: 'https://company.com/summer-campaign-2024',
    image: 'https://company.com/assets/summer-logo.png'
  });

Enterprise Metadata Patterns

Content Management System Integration

class CMSQRManager {
  static createContentQR(content) {
    const metadata = {
      contentId: content.id,
      contentType: content.type,
      title: content.title,
      author: content.author,
      publishDate: content.publishDate,
      lastModified: content.lastModified,
      tags: content.tags,
      language: content.language,
      seoScore: content.seoScore,
      readingTime: content.estimatedReadingTime
    };

    return QRCodeJs.useTemplate('cms-standard')
      .useId(`cms-${content.type}-${content.id}`)
      .useName(`${content.title} - ${content.type}`)
      .useDescription(`QR code for ${content.type}: ${content.title}`)
      .useMetadata(metadata)
      .options({
        data: `https://cms.company.com/content/${content.id}`,
        qrOptions: { errorCorrectionLevel: 'M' }
      });
  }
}

// Usage
const articleQR = CMSQRManager.createContentQR({
  id: 'art-2024-0315',
  type: 'article',
  title: 'Future of Sustainable Technology',
  author: 'Dr. Jane Smith',
  publishDate: '2024-03-15',
  lastModified: '2024-03-20',
  tags: ['technology', 'sustainability', 'innovation'],
  language: 'en',
  seoScore: 94,
  estimatedReadingTime: '8 minutes'
});

Multi-Channel Campaign Management

class CampaignQRGenerator {
  constructor(campaignConfig) {
    this.config = campaignConfig;
  }

  createChannelQR(channel, specific = {}) {
    const baseMetadata = {
      campaignId: this.config.id,
      campaignName: this.config.name,
      channel: channel.name,
      channelType: channel.type,
      medium: channel.medium,
      budget: channel.budget,
      expectedReach: channel.expectedReach,
      createdAt: new Date().toISOString(),
      ...this.config.globalMetadata
    };

    return QRCodeJs.useTemplate(this.config.template)
      .useId(`${this.config.id}-${channel.name}-${Date.now()}`)
      .useName(`${this.config.name} - ${channel.displayName}`)
      .useDescription(`${this.config.name} campaign QR for ${channel.displayName}`)
      .useMetadata({ ...baseMetadata, ...specific })
      .options({
        data: `${this.config.baseUrl}?utm_source=${channel.name}&utm_medium=${channel.medium}&utm_campaign=${this.config.id}`,
        ...channel.qrOptions
      });
  }
}

// Usage
const campaignGen = new CampaignQRGenerator({
  id: 'holiday-2024',
  name: 'Holiday Sale Campaign',
  template: 'festive',
  baseUrl: 'https://store.company.com/holiday-sale',
  globalMetadata: {
    brand: 'TechCorp',
    season: 'holiday',
    year: 2024,
    department: 'marketing'
  }
});

const socialQR = campaignGen.createChannelQR(
  {
    name: 'instagram',
    displayName: 'Instagram',
    type: 'social',
    medium: 'social-media',
    budget: 5000,
    expectedReach: 50000,
    qrOptions: { margin: 20 }
  },
  {
    platform: 'instagram',
    contentType: 'story',
    demographics: 'young-adults'
  }
);

Analytics and Tracking Integration

class AnalyticsQRWrapper {
  static enhanceWithAnalytics(qrInstance, analyticsConfig) {
    const enhancedMetadata = {
      ...qrInstance.getMetadata(),
      analytics: {
        trackingId: analyticsConfig.trackingId,
        platform: analyticsConfig.platform,
        goals: analyticsConfig.goals,
        customDimensions: analyticsConfig.customDimensions,
        autoTrack: analyticsConfig.autoTrack,
        enableHeatmap: analyticsConfig.enableHeatmap,
        retentionPeriod: analyticsConfig.retentionPeriod
      },
      privacy: {
        gdprCompliant: true,
        anonymized: analyticsConfig.anonymized,
        consentRequired: analyticsConfig.consentRequired
      }
    };

    qrInstance.setMetadata(enhancedMetadata);
    return qrInstance;
  }

  static createAnalyticsReport(qrInstance) {
    const metadata = qrInstance.getMetadata();
    const settings = qrInstance.getSettings();
    
    return {
      qrInfo: {
        id: qrInstance.getId(),
        name: qrInstance.getName(),
        description: qrInstance.getDescription()
      },
      configuration: settings,
      metadata: metadata,
      analyticsReadiness: this.validateAnalyticsSetup(metadata),
      reportGeneratedAt: new Date().toISOString()
    };
  }

  static validateAnalyticsSetup(metadata) {
    const analytics = metadata?.analytics;
    return {
      hasTrackingId: !!analytics?.trackingId,
      hasGoals: !!(analytics?.goals && analytics.goals.length > 0),
      gdprCompliant: metadata?.privacy?.gdprCompliant === true,
      autoTrackEnabled: analytics?.autoTrack === true
    };
  }
}

// Usage
const productQR = QRCodeJs.useTemplate('product')
  .useId('product-smartphone-x1')
  .useName('Smartphone X1 Product Page')
  .useMetadata({
    productId: 'smartphone-x1',
    category: 'mobile',
    price: 699.99
  })
  .options({
    data: 'https://store.company.com/smartphone-x1'
  });

const enhancedQR = AnalyticsQRWrapper.enhanceWithAnalytics(productQR, {
  trackingId: 'GA-123456789',
  platform: 'google-analytics',
  goals: ['purchase', 'add-to-cart', 'view-specifications'],
  customDimensions: {
    productCategory: 'mobile',
    priceRange: '600-800',
    season: 'q1-2024'
  },
  autoTrack: true,
  enableHeatmap: true,
  retentionPeriod: '2-years',
  anonymized: true,
  consentRequired: false
});

const analyticsReport = AnalyticsQRWrapper.createAnalyticsReport(enhancedQR);
console.log('QR Analytics Report:', analyticsReport);

Metadata Best Practices

1. Consistent Schema Design

// Define a standardized metadata schema
const MetadataSchema = {
  // Core identification
  id: 'string (required)',
  name: 'string (required)',
  description: 'string (required)',
  
  // Lifecycle tracking
  createdAt: 'ISO 8601 timestamp',
  updatedAt: 'ISO 8601 timestamp',
  createdBy: 'string (user identifier)',
  lastModifiedBy: 'string (user identifier)',
  version: 'string (semantic versioning)',
  
  // Business context
  businessUnit: 'string',
  department: 'string',
  project: 'string',
  campaign: 'string',
  
  // Technical details
  environment: 'development | staging | production',
  region: 'string',
  locale: 'string (ISO 639-1)',
  
  // Analytics and tracking
  analytics: {
    enabled: 'boolean',
    platform: 'string',
    goals: 'string[]',
    customDimensions: 'Record<string, any>'
  },
  
  // Compliance and governance
  dataRetention: 'string (duration)',
  privacyLevel: 'public | internal | confidential | restricted',
  complianceFlags: 'string[]'
};

2. Validation and Type Safety

class MetadataValidator {
  static validateRequiredFields(metadata) {
    const required = ['id', 'name', 'description'];
    const missing = required.filter(field => !metadata[field]);
    
    if (missing.length > 0) {
      throw new Error(`Missing required metadata fields: ${missing.join(', ')}`);
    }
  }

  static sanitizeMetadata(metadata) {
    return {
      ...metadata,
      // Ensure timestamps are ISO strings
      createdAt: metadata.createdAt || new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      // Sanitize strings
      name: metadata.name?.trim(),
      description: metadata.description?.trim(),
      // Ensure arrays are arrays
      tags: Array.isArray(metadata.tags) ? metadata.tags : [],
      // Validate email format for creator fields
      createdBy: this.validateEmail(metadata.createdBy)
    };
  }

  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return email && emailRegex.test(email) ? email : null;
  }
}

// Usage with validation
function createValidatedQR(data, metadata) {
  try {
    MetadataValidator.validateRequiredFields(metadata);
    const sanitizedMetadata = MetadataValidator.sanitizeMetadata(metadata);
    
    return QRCodeJs.useTemplate('validated')
      .useId(sanitizedMetadata.id)
      .useName(sanitizedMetadata.name)
      .useDescription(sanitizedMetadata.description)
      .useMetadata(sanitizedMetadata)
      .options({ data });
  } catch (error) {
    console.error('QR creation failed:', error.message);
    throw error;
  }
}

3. Metadata Versioning and Migration

class MetadataVersionManager {
  static CURRENT_VERSION = '2.1.0';
  
  static migrateMetadata(metadata) {
    const version = metadata.schemaVersion || '1.0.0';
    let migrated = { ...metadata };
    
    if (this.isVersionLess(version, '2.0.0')) {
      migrated = this.migrateTo2_0_0(migrated);
    }
    
    if (this.isVersionLess(version, '2.1.0')) {
      migrated = this.migrateTo2_1_0(migrated);
    }
    
    migrated.schemaVersion = this.CURRENT_VERSION;
    migrated.lastMigration = new Date().toISOString();
    
    return migrated;
  }
  
  static migrateTo2_0_0(metadata) {
    return {
      ...metadata,
      // Migrate old tracking field to new analytics structure
      analytics: {
        enabled: metadata.tracking?.enabled || false,
        platform: metadata.tracking?.platform || 'unknown',
        goals: metadata.tracking?.events || []
      },
      // Remove deprecated fields
      tracking: undefined
    };
  }
  
  static migrateTo2_1_0(metadata) {
    return {
      ...metadata,
      // Add new compliance fields with defaults
      compliance: {
        dataRetention: metadata.compliance?.dataRetention || '1-year',
        privacyLevel: metadata.compliance?.privacyLevel || 'internal'
      }
    };
  }
  
  static isVersionLess(version1, version2) {
    const v1Parts = version1.split('.').map(Number);
    const v2Parts = version2.split('.').map(Number);
    
    for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
      const v1Part = v1Parts[i] || 0;
      const v2Part = v2Parts[i] || 0;
      
      if (v1Part < v2Part) return true;
      if (v1Part > v2Part) return false;
    }
    
    return false;
  }
}

Integration Examples

Database Integration

class QRDatabaseManager {
  static async saveQRMetadata(qrInstance) {
    const metadata = {
      id: qrInstance.getId(),
      name: qrInstance.getName(),
      description: qrInstance.getDescription(),
      customMetadata: qrInstance.getMetadata(),
      settings: qrInstance.getSettings(),
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    // Save to database (pseudo-code)
    return await database.qrCodes.create(metadata);
  }
  
  static async loadQRMetadata(qrId) {
    const record = await database.qrCodes.findById(qrId);
    if (!record) return null;
    
    // Recreate QR instance with stored metadata
    const qrInstance = new QRCodeJs(record.settings.options || { data: record.data });
    
    qrInstance
      .setId(record.id)
      .setName(record.name)
      .setDescription(record.description)
      .setMetadata(record.customMetadata);
    
    return qrInstance;
  }
}

This comprehensive metadata management system enables enterprise-level QR code organization, tracking, and governance while maintaining flexibility for various use cases and integration requirements.

License and Support

QRCode.js by QR-Platform is free for personal projects, open-source projects, or general non-commercial use. For commercial use, a license is required.

See the full license at LICENSE.md for more information. For commercial licenses, including full source code and support, contact qr.platform.com@gmail.com.