QR Codes Explained: From Factory Labels to Generation and Decoding in Code

Point a phone at a small square on a machine and its maintenance page opens. Scan another square and the phone offers to join a Wi-Fi network. The same visual format can carry a product identifier, a ticket, or a short message.

A QR code is a way to store data in a picture that software can locate, read, and recover even when some information is damaged. The application decides what to do with the recovered data.

This guide explains the original idea, the structure of the symbol, the encoding and decoding pipelines, and practical implementation choices. It includes generation examples in Python, JavaScript, and Go, a Python decoder, and libraries for other languages.


Where QR Codes Came From

QR stands for Quick Response. In 1994, a team led by Masahiro Hara at DENSO, whose QR business later became part of DENSO WAVE, introduced the code. Industrial users needed more information than their existing barcodes conveniently held, including Japanese characters, while keeping scanning fast.

The design used two dimensions for capacity and distinctive corner patterns for rapid detection. Automotive production adopted it for tracking and workflow management. DENSO WAVE’s decision not to exercise its patent rights over standardized QR codes helped wider adoption; international standardization followed in 2000. The company’s history of QR Code describes that progression.

The underlying idea remains useful: move a small piece of machine-readable information across a visual interface, without typing, a cable, or an already established network connection.


What Does a QR Code Actually Store?

A QR symbol contains encoded data. It does not inherently contain a website, a payment system, or a command to execute.

PayloadExamplePossible scanner behavior
Plain textMachine A17: inspect filterDisplay the message
HTTPS URLhttps://example.com/assets/A17Offer to open a browser
Asset identifierASSET:A17Let a custom application look up the asset
Wi-Fi configurationWIFI:T:WPA;S:Workshop;P:demo-password;;Offer to join a network
Contact dataA vCard recordOffer to create a contact
Application tokenA short opaque ticket identifierSubmit it to an application for validation

Wi-Fi and contact handling are payload conventions, separate from the QR symbol format. Support varies between scanners. The example Wi-Fi password is illustrative; special characters in real credentials need escaping according to the convention. See ZXing’s barcode content formats.

Decoding an image can happen offline. Opening its URL or validating a ticket against a server may require connectivity. A generic phone camera may show a custom payload as text even though your own application understands it.

Static and Dynamic QR Codes

A static QR code stores a fixed payload. Once printed, those bits do not change.

A service marketed as a dynamic QR code usually stores a fixed URL whose server-side destination can change:

Printed QR → https://example.com/q/A17 → redirect → current asset page

You update the redirect mapping, not the printed symbol. This supports changing destinations and recording redirect requests, but it adds a dependency on the domain and service. Request counts are not necessarily unique human scans: previews, retries, and automated requests can affect them.

For a product label, a short URL on a domain you control is often a practical design. A QR code has no inherent expiry date; a service, token, or destination may expire.


Inside the Black-and-White Square

This article focuses on QR Code Model 2, the familiar square format. Other QR-family formats have different structures and limits.

Each small square is a module. A module is a logical cell, not necessarily one screen pixel. When rendering, one module might occupy an 8 × 8 pixel block.

ComponentPurpose
Three finder patternsLocate the symbol and establish its orientation
SeparatorsKeep the finder patterns visually distinct from adjacent content
Timing patternsAlternating modules that help establish the grid spacing
Alignment patternsHelp account for distortion; present from version 2 onward
Format informationRecords the error-correction level and selected mask
Version informationExplicit version metadata for versions 7 and above
Data regionHolds encoded payload and error-correction codewords
Quiet zoneClear surrounding margin that separates the symbol from its background

The positions and reserved regions are visible in Nayuki’s QR encoder implementation.

Model 2 versions range from 1 to 40. Version 1 is 21 × 21 modules; each increment adds four modules per side, ending at 177 × 177. The formula is:

modules per side = 17 + 4 × version

Capacity depends on version, encoding mode, and error correction. A larger version adds room but also needs more physical space to preserve module size. See DENSO WAVE’s version and capacity guide.

Allow at least four clear modules on every side for a standard QR symbol. A version 1 symbol rendered at eight pixels per module therefore occupies (21 + 4 + 4) × 8 = 232 pixels per side, including the margin. See DENSO WAVE’s code-area guidance.


How Encoding Turns Data into a QR Code

An encoder follows this pipeline:

Payload → segments → data bits → error-correction blocks
        → module placement → mask selection → rendered image
  1. Select modes. Numeric, alphanumeric, byte, and Kanji modes encode different character sets efficiently. Mixed content can use multiple segments.
  2. Choose a version. Find a symbol with enough capacity at the requested error-correction level.
  3. Build the bitstream. Add mode and length fields, content bits, a terminator, and padding as required.
  4. Add redundancy. Split codewords into blocks, calculate Reed–Solomon parity, and interleave the blocks.
  5. Place bits. Fill available modules in a prescribed two-column zigzag, starting at the lower right and skipping reserved patterns.
  6. Choose a mask. Evaluate eight patterns that flip selected data-region modules; choose one with a favorable visual penalty score.
  7. Write metadata and render. Store the mask and correction level, then draw the modules and quiet zone.

Masking reduces difficult visual patterns. It is reversible and is not encryption. Nayuki’s library provides readable implementations across six languages.

A Small Encoding Example

For a deliberately chosen byte-mode, version 1 encoding of Hi, the beginning of the bitstream is:

Mode:              0100             byte mode
Byte count:        00000010         2 bytes for versions 1–9
H (0x48):          01001000
i (0x69):          01101001

This is only the beginning: termination, padding, correction data, placement, and masking still follow. The mode and length parsing can also be inspected in ZXing’s bitstream decoder.

For international text, distinguish characters from bytes. UTF-8 characters can occupy multiple bytes. Extended Channel Interpretation (ECI) can identify a character encoding, but library and scanner support must be checked. Test your actual languages and emojis across intended readers rather than assuming every byte-mode payload is interpreted identically. The node-qrcode documentation describes its encoding support and limitations.

Error Correction: Why Damage Does Not Always Break a Scan

QR codes use Reed–Solomon error correction to reconstruct damaged codewords within the correction budget.

LevelApproximate codeword restoration capabilityPractical starting point
L7%Clean, controlled presentation
M15%General-purpose labels and screens
Q25%Labels exposed to some wear
H30%Greater redundancy where space permits

These figures are approximate codeword-based capabilities, not permission to cover that percentage of the picture. Damage location, block distribution, and whether the scanner can still locate the symbol matter. A destroyed finder pattern can prevent decoding before error correction helps.

Higher correction consumes capacity and can increase symbol size. At a fixed printed size, that can make modules smaller and harder to capture. Choose the level together with payload length and physical dimensions. See DENSO WAVE’s error-correction explanation.


How a Scanner Decodes the Image

A typical camera-based reader performs two related jobs: finding a usable module grid and recovering its data.

Camera image → locate symbol → correct perspective → sample grid
             → read metadata → unmask → recover codewords
             → correct errors → parse payload → application action

The image-processing stage distinguishes dark and light regions, finds candidate corner patterns, estimates orientation and perspective, and samples module centers. Blur, reflections, low resolution, or a cropped margin can prevent a reliable grid from being formed. OpenCV exposes detection and decoding together or separately through QRCodeDetector.

With a sampled grid, the decoder reads format/version information, reverses the mask, follows the placement order, and separates the interleaved blocks. It applies Reed–Solomon correction and parses the reconstructed data segments. ZXing’s decoder implementation shows the block correction and payload-decoding stages.

The application then interprets the result. Successfully recovering a URL does not establish that its destination is trustworthy. Likewise, recovering a ticket token does not establish that the ticket is valid or unused.


Generate and Decode a QR Code with Python

Create an isolated environment and install the generator and decoder dependencies:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install "qrcode[pil]" opencv-python-headless

Save this as generate_qr.py:

import qrcode

payload = "https://example.com/assets/A17"
qr = qrcode.QRCode(
    version=None,
    error_correction=qrcode.constants.ERROR_CORRECT_M,
    box_size=8,
    border=4,
)
qr.add_data(payload)
qr.make(fit=True)
qr.make_image(fill_color="black", back_color="white").save("asset-qr.png")
print(f"Generated version {qr.version}: {payload}")

version=None with fit=True lets the library choose the size. box_size is pixels per module; border is measured in modules. These options come from python-qrcode.

Save this as decode_qr.py:

import cv2

image = cv2.imread("asset-qr.png")
if image is None:
    raise SystemExit("Could not load asset-qr.png")

text, points, straight = cv2.QRCodeDetector().detectAndDecode(image)
if points is None:
    raise SystemExit("No QR code detected")
if not text:
    raise SystemExit("A QR code was detected, but no nonempty text was decoded")

print(text)
assert text == "https://example.com/assets/A17"

Run the round trip:

python generate_qr.py
python decode_qr.py

The decoder prints the original URL. This example handles one text payload; it does not open the destination. For multiple symbols, OpenCV provides detectAndDecodeMulti. For arbitrary binary content, choose an API that exposes raw bytes rather than assuming a text result is sufficient.


Generate a QR Code with JavaScript and Node.js

Install the encoder in your project:

npm install qrcode

Save as generate-qr.cjs:

const QRCode = require('qrcode');

async function main() {
  const payload = 'https://example.com/assets/A17';
  const options = { errorCorrectionLevel: 'M', margin: 4, scale: 8 };

  await QRCode.toFile('asset-qr.png', payload, options);
  await QRCode.toFile('asset-qr.svg', payload, { ...options, type: 'svg' });
  console.log('Saved PNG and SVG QR codes');
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
node generate-qr.cjs

PNG suits raster displays; SVG scales cleanly for print layouts. node-qrcode also supports browser canvas rendering.

For browser decoding, jsQR accepts RGBA pixel data, such as ImageData obtained from a canvas:

// In a bundled browser application after: npm install jsqr
import jsQR from 'jsqr';

export function decodeCanvas(canvas) {
  const context = canvas.getContext('2d');
  if (!context) throw new Error('A 2D canvas context is required');
  const frame = context.getImageData(0, 0, canvas.width, canvas.height);
  const result = jsQR(frame.data, frame.width, frame.height);
  return result ? result.data : null;
}

Draw the image or video frame onto the canvas first. Camera capture and permission handling are separate application work. Cross-origin images also need appropriate CORS access before their pixels can be read.


Generate a QR Code with Go

In a new example directory:

go mod init example.com/qr-demo
go get github.com/skip2/go-qrcode

Save as main.go:

package main

import (
    "log"

    "github.com/skip2/go-qrcode"
)

func main() {
    err := qrcode.WriteFile(
        "https://example.com/assets/A17",
        qrcode.Medium,
        256,
        "asset-qr.png",
    )
    if err != nil {
        log.Fatal(err)
    }
}
go run .

The size argument requests the output image dimension in pixels. go-qrcode is an encoder; choose a separate reader when your application also scans images.


QR Libraries for Different Languages

Generation and decoding are separate capabilities. A library that creates a module matrix may still require your own PNG, SVG, or display renderer.

Language or environmentLibraryUseful capability
Pythonpython-qrcodeGenerate PNG and SVG symbols
Python / C++OpenCV QRCodeDetectorDetect and decode images in a vision pipeline
JavaScript / TypeScriptnode-qrcodeGenerate images and browser canvas output
JavaScript / TypeScriptjsQRDecode a symbol from RGBA image data
C / C++Nayuki QR Code generatorGenerate module matrices, including for embedded rendering
C++ZXing-C++Read and write multiple barcode formats
Java / AndroidZXingEncode and decode QR and other barcode formats
Gogo-qrcodeGenerate QR images
C# / .NETQRCoderGenerate QR codes with several rendering options
RustqrcodeEncode QR data and render through supported output paths

Choose based on required formats, raw-byte access, Unicode behavior, license, platform dependencies, and project maintenance. Pin dependencies for a shipped application. A barcode library’s decoding API is not necessarily a complete camera interface.

Embedded Implementation Considerations

On a small device, generating a QR matrix and drawing it on a display can be much simpler than decoding a camera image. With Nayuki’s C implementation, the application supplies buffers and renders the resulting modules; the library does not require an image-file workflow. Its C source and examples are useful starting points.

For a setup screen, encode a short device identifier or onboarding URL. Reserve enough display area for complete modules and the quiet zone. Camera decoding adds frame-buffer, image-processing, and compute requirements, so check those against the target hardware early.


Practical Use Cases and Design Choices

Use casePayload choiceWhat the application must handle
Asset maintenanceStable asset URL or identifierAccess control and current maintenance records
Device onboardingDevice identity plus a short-lived pairing tokenDevice binding, expiry, and pairing confirmation
Event entryOpaque ticket tokenValidity and atomic redemption to prevent reuse
Offline inspectionCompact, versioned recordSchema validation and authenticity if needed
Guest Wi-FiWi-Fi configuration stringReader compatibility and credential rotation
Menus and documentationStable HTTPS URLLong-lived hosting and readable mobile content
InventoryItem, batch, or serial identifierIntegration with stock and manufacturing records

A QR label is easy to copy. For tickets, checking that the QR text has the right shape is insufficient: the system must decide whether that token is authorized and still usable. Offline signatures can establish who issued a payload, but they do not by themselves prevent copying or solve synchronized redemption.

For an embedded product, treat scanning as one step in the overall workflow: scan, identify, authenticate when needed, confirm, and recover from failure. The broader systems engineering guide explains how to define and validate such end-to-end behavior.


Make the Printed Code Easy to Scan

Start with a short payload, dark modules on a plain light background, and a complete quiet zone. Render raster output at an integer number of pixels per module and avoid resizing that blurs module edges. Use vector output when the print workflow supports it.

Do not assume maximum error correction compensates for tiny printing, glare, or a logo covering important patterns. Decide the physical size using the intended camera, distance, lighting, and print process.

Before shipping a label or screen:

  1. Decode the generated file and compare the exact result with the intended payload.
  2. Scan the final printed or displayed version with representative readers.
  3. Check realistic distance, angle, lighting, surface reflections, and wear.
  4. Test international text and special characters if they are part of your payload.
  5. Exercise missing connectivity, unavailable destinations, and invalid or expired tokens.
SymptomThings to check
No symbol detectedQuiet zone, contrast, focus, module size, cropped finder patterns
Detected but not decodedBlur, damage, excessive density, perspective, reader limitations
Correct symbol but unexpected textCharacter encoding, ECI support, byte-versus-text handling
URL decodes but page failsDomain, connectivity, redirect, and server availability
Looks good on screen but fails on paperActual printed dimensions, ink spread, finish, and scaling

A good first implementation is a short payload generated by an established library, decoded by an independent reader, and tested on the actual surface where people will scan it. That closes the loop between the bits you intended to encode and the action the user expects.