# How to Build a QR Code Generator and Reader in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 20-25 min
- Compatibility: FlutterFlow Free+ (Custom Widgets required)
- Last updated: March 2026

## TL;DR

Build both a QR code generator and reader in FlutterFlow using Custom Widgets. The generator uses the qr_flutter package to render a QR code from any string input. The reader uses the mobile_scanner package to decode QR codes from the device camera, parsing URLs, plain text, vCard, and WiFi configurations. Add save-to-image functionality using RepaintBoundary to export generated QR codes as shareable images.

## QR code generation and scanning with data type detection

QR codes are used everywhere: event tickets, product lookups, WiFi sharing, contact exchange, and payment links. This tutorial builds both sides in FlutterFlow. The generator takes any string input and renders a scannable QR code using the qr_flutter package, with the option to save it as an image. The reader uses mobile_scanner to decode QR codes from the camera and automatically detects the data type (URL, vCard, WiFi config, or plain text) to take the appropriate action.

## Before you start

- A FlutterFlow project (any plan that supports Custom Widgets)
- Basic understanding of Custom Widgets and Action Parameters
- Camera permissions configured for your app (for the scanner)
- Physical device or emulator with camera for testing the reader

## Step-by-step guide

### 1. Create the QR code generator Custom Widget

In FlutterFlow, go to Custom Code and create a new Custom Widget named QRCodeGenerator. Add a parameter: data (String) for the content to encode, and an optional size (Double, default 200). Add the qr_flutter package as a dependency. In the widget body, return a QrImageView widget with data set to widget.data, version set to QrVersions.auto, and size set to widget.size. Wrap it in a Container with a white background and padding of 16 for a clean border around the QR code. This widget can encode any string: URLs, plain text, contact info, or WiFi credentials.

```
// Custom Widget: QRCodeGenerator
import 'package:qr_flutter/qr_flutter.dart';

class QRCodeGenerator extends StatelessWidget {
  final String data;
  final double? size;

  const QRCodeGenerator({
    required this.data,
    this.size = 200,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
      ),
      child: QrImageView(
        data: data,
        version: QrVersions.auto,
        size: size ?? 200,
        gapless: true,
        errorCorrectionLevel: QrErrorCorrectLevel.M,
      ),
    );
  }
}
```

**Expected result:** A Custom Widget that renders a QR code from any string data, usable on any page by passing the data parameter.

### 2. Build the generator page with text input and QR display

Create a QRGeneratorPage with a TextField at the top for entering the data to encode (hint text: 'Enter URL, text, or data'). Below, add the QRCodeGenerator Custom Widget with its data parameter bound to the TextField value via Page State. Add a DropDown for quick data templates: URL, WiFi, Contact (vCard), Plain Text. When WiFi is selected, show additional TextFields for network name, password, and encryption type (WPA/WEP/none), then compose the WiFi QR format: 'WIFI:T:{encryption};S:{ssid};P:{password};;'. For vCard, show name, phone, and email fields and compose: 'BEGIN:VCARD\nVERSION:3.0\nN:{name}\nTEL:{phone}\nEMAIL:{email}\nEND:VCARD'. The QR code updates in real time as the user types.

**Expected result:** Users can type or compose data and see the QR code update in real time. Templates help format WiFi and vCard data correctly.

### 3. Add save-to-image functionality for the generated QR code

Create a Custom Action named saveQRCodeAsImage. This action wraps the QR widget in a RepaintBoundary with a GlobalKey, renders it to an image using boundary.toImage(), converts to PNG bytes, and saves to the device gallery using the image_gallery_saver package (or shares directly via share_plus). In the QRGeneratorPage, add a Button labeled 'Save QR Code' below the QR widget. On Tap, call this Custom Action. Alternatively, use a simpler approach: add a Share button that calls a Custom Action composing the QR data into a shareable format and opens the native share sheet, or use the screenshot package to capture the widget area.

```
// Custom Action: saveQRAsImage
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'package:share_plus/share_plus.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

Future saveQRAsImage(GlobalKey repaintKey) async {
  final boundary = repaintKey.currentContext!
      .findRenderObject() as RenderRepaintBoundary;
  final image = await boundary.toImage(pixelRatio: 3.0);
  final byteData = await image.toByteData(
    format: ui.ImageByteFormat.png,
  );
  final bytes = byteData!.buffer.asUint8List();
  final dir = await getTemporaryDirectory();
  final file = File('${dir.path}/qr_code.png');
  await file.writeAsBytes(bytes);
  await Share.shareXFiles([XFile(file.path)], text: 'QR Code');
}
```

**Expected result:** Users can save or share the generated QR code as a PNG image from the generator page.

### 4. Create the QR code reader Custom Widget with camera scanning

Create a new Custom Widget named QRCodeReader. Add the mobile_scanner package as a dependency. Add an Action Parameter callback named onScanned (returns String) that sends the decoded data back to the parent page. In the widget body, return a MobileScanner widget with onDetect callback. When a barcode is detected, extract the rawValue, call the onScanned callback with the value, and optionally stop the scanner to prevent repeated scans by calling controller.stop(). Add a camera overlay with a transparent center square and semi-transparent borders to guide the user where to point the camera.

```
// Custom Widget: QRCodeReader
import 'package:mobile_scanner/mobile_scanner.dart';

class QRCodeReader extends StatefulWidget {
  final Future Function(String scannedData) onScanned;
  const QRCodeReader({required this.onScanned, super.key});

  @override
  State<QRCodeReader> createState() => _QRCodeReaderState();
}

class _QRCodeReaderState extends State<QRCodeReader> {
  final MobileScannerController _controller = MobileScannerController();
  bool _hasScanned = false;

  @override
  Widget build(BuildContext context) {
    return MobileScanner(
      controller: _controller,
      onDetect: (capture) {
        if (_hasScanned) return;
        final barcode = capture.barcodes.firstOrNull;
        if (barcode?.rawValue != null) {
          _hasScanned = true;
          _controller.stop();
          widget.onScanned(barcode!.rawValue!);
        }
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
```

**Expected result:** A camera-based QR scanner widget that detects codes and sends the decoded string back via a callback.

### 5. Build the scanner page with data type detection and actions

Create a QRScannerPage with the QRCodeReader Custom Widget filling the screen. Wire the onScanned callback to set Page State scannedData with the result and show a BottomSheet displaying the decoded content. Create a Custom Function named detectQRDataType that analyzes the scanned string: if it starts with 'http://' or 'https://', return 'url'; if it starts with 'WIFI:', return 'wifi'; if it starts with 'BEGIN:VCARD', return 'vcard'; otherwise return 'text'. Based on the detected type, show different action buttons in the BottomSheet: URL shows 'Open Link' (Launch URL action) and 'Copy' (Clipboard); WiFi shows 'Connect' (parse SSID and password, display them); vCard shows 'Save Contact' (parse name, phone, email); plain text shows 'Copy to Clipboard'. Add a 'Scan Again' button that resets the scanner.

**Expected result:** The scanner reads QR codes and presents context-appropriate actions based on the data type: open URLs, display WiFi credentials, show contact info, or copy text.

## Complete code example

File: `QR Code System Architecture`

```text
Custom Widgets:
├── QRCodeGenerator
│   ├── Package: qr_flutter
│   ├── Parameters: data (String), size (Double?)
│   └── Renders: QrImageView with white Container wrapper
└── QRCodeReader
    ├── Package: mobile_scanner
    ├── Action Parameter: onScanned (String callback)
    └── Renders: MobileScanner with single-scan guard

QR Generator Page:
├── DropDown (template: URL | WiFi | vCard | Plain Text)
├── TextField (data input, or composed from template fields)
│   ├── [WiFi mode] → SSID + Password + Encryption fields
│   └── [vCard mode] → Name + Phone + Email fields
├── QRCodeGenerator Widget (data: Page State qrData)
├── Button ("Save QR Code")
│   └── On Tap → saveQRAsImage Custom Action
└── Button ("Share")
    └── On Tap → shareContent Custom Action

QR Scanner Page:
├── QRCodeReader Widget (fills screen)
│   └── onScanned → Set Page State: scannedData → Show BottomSheet
└── BottomSheet (scan result)
    ├── Text (scanned data)
    ├── Text (detected type badge)
    ├── Actions (conditional by type):
    │   ├── URL → Button "Open Link" (Launch URL) + "Copy"
    │   ├── WiFi → Text (SSID + Password) + "Copy Password"
    │   ├── vCard → Text (Name + Phone + Email) + "Save Contact"
    │   └── Text → Button "Copy to Clipboard"
    └── Button ("Scan Again" → reset scanner)

Data Type Detection (Custom Function):
├── starts with 'http://' or 'https://' → 'url'
├── starts with 'WIFI:' → 'wifi'
├── starts with 'BEGIN:VCARD' → 'vcard'
└── else → 'text'

QR Format Standards:
├── WiFi: WIFI:T:WPA;S:MyNetwork;P:MyPassword;;
├── vCard: BEGIN:VCARD\nVERSION:3.0\nN:Name\nTEL:+1234\nEMAIL:a@b.com\nEND:VCARD
└── URL: https://example.com/page
```

## Common mistakes

- **Generating QR codes with too much data causing unreadable dense patterns** — QR codes have a maximum capacity of about 3KB for alphanumeric data. Encoding long JSON strings, paragraphs of text, or full page URLs creates extremely dense QR patterns that cameras cannot decode reliably, especially on small screens. Fix: Keep encoded data short. For long content, store the data in Firestore and encode only the document ID or a short URL. Use a URL shortener for long web addresses.
- **Not stopping the scanner after a successful detection** — The mobile_scanner onDetect fires continuously while a QR code is in view. Without a guard, the callback executes dozens of times per second, causing repeated navigation or BottomSheet openings. Fix: Add a boolean _hasScanned flag. Set it to true on first detection and call controller.stop(). Only process the scan when _hasScanned is false. Add a Scan Again button that resets the flag and restarts the controller.
- **Forgetting to request camera permissions for the scanner** — The mobile_scanner widget needs camera access. Without permissions, the scanner shows a black screen or crashes on launch with no error message visible to the user. Fix: In FlutterFlow project settings, enable camera permissions for both iOS (NSCameraUsageDescription) and Android (CAMERA permission). Add a pre-check Custom Action that verifies permission is granted before navigating to the scanner page.

## Best practices

- Keep QR data under 500 characters for reliable scanning across all devices and camera qualities
- Use error correction level M (15% recovery) for a balance between data density and scan reliability
- Stop the scanner after first detection to prevent duplicate processing
- Add a visible scanning guide overlay (transparent center square) to help users aim the camera
- Support standard QR formats: WiFi (WIFI:T:...), vCard (BEGIN:VCARD...), and URLs for maximum interoperability
- Add a white quiet zone (padding) around generated QR codes for better scanner recognition
- Test scanning with both high-quality and low-quality phone cameras to verify readability

## Frequently asked questions

### What is the maximum amount of data a QR code can hold?

A QR code can hold up to 7,089 numeric characters, 4,296 alphanumeric characters, or about 2,953 bytes of binary data. However, practical limits are much lower because dense codes are hard to scan. Keep data under 500 alphanumeric characters for reliable scanning.

### Can I customize the QR code colors or add a logo in the center?

Yes. The qr_flutter package supports custom foreground and background colors via the dataModuleStyle and eyeStyle properties. For a center logo, use a Stack widget with the QR code and a small Image overlaid in the center. Error correction level H (30%) allows the logo to obscure some modules without losing scannability.

### Does the scanner work offline without an internet connection?

Yes. The mobile_scanner package decodes QR codes locally on the device using the camera. No internet connection is needed for scanning. However, if the decoded content is a URL, the user will need internet to open it.

### How do I scan QR codes from images in the photo gallery?

Use the MobileScannerController's analyzeImage method. Let the user pick an image from their gallery using FlutterFlowUploadButton, then pass the file path to analyzeImage(). This decodes QR codes from static images without the camera.

### What is the WiFi QR code format standard?

The WiFi QR format is: WIFI:T:{encryption};S:{ssid};P:{password};;  where T is WPA, WEP, or nopass; S is the network name; and P is the password. Most phones can auto-join WiFi networks when scanning this format.

### Can RapidDev help build a QR-based ticketing or inventory system?

Yes. A production QR system for event tickets, inventory tracking, or access control needs secure encoded payloads, server-side validation, duplicate scan prevention, offline capability, and admin dashboards. RapidDev can design the full system beyond basic generation and scanning.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-qr-code-generator-and-reader-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-qr-code-generator-and-reader-in-flutterflow
