QR Code Camera Scanning
This guide shows how to add a QR code scanner button to your ai12z bot welcome screen that activates the device camera, scans a QR code, and automatically sends the scanned content as a message to the bot.
Overview
The QR code camera scanner enables users to:
- Tap a button in the welcome screen to activate the device camera
- Scan a QR code and automatically send its content as a bot message
- Works on mobile and desktop devices with a camera
This is different from the QR Code Query Parameter Launch guide, which covers generating QR codes that link to your bot. This guide covers scanning QR codes from within the bot using the device camera.
How It Works
- The user taps the Scan button in the bot welcome screen
- The browser requests camera permission
- The camera activates and displays a viewfinder with a scanning area
- The user points their camera at a QR code
- Once detected, the scanned content is automatically sent as a message to the bot
Implementation
Step 1: Add the Scan Button and Scanner Elements
Add the scan button and the required scanner container elements to your bot's welcome screen HTML. The ai12z-qr-reader div is where the camera viewfinder renders, and the status and toggle elements provide user feedback and control:
<button id="ai12z-qr-scan-btn" class="ai12z-icon-btn" title="Scan QR Code" type="button"
onclick="ai12zToggleQrScanner()">
<span class="ai12z-icon">📷</span>
<span class="ai12z-icon-label">Scan</span>
</button>
<!-- QR Scanner container — required for the camera viewfinder -->
<div id="ai12z-qr-reader" style="width: 100%; max-width: 400px;"></div>
<p id="ai12z-qr-status"></p>
| Element | Purpose |
|---|---|
ai12z-qr-scan-btn | The scan button. Its label changes to "Stop Scanning" while the camera is active. |
ai12z-qr-reader | Container where the camera viewfinder renders. Must exist before scanning starts. |
ai12z-qr-status | Displays status messages to the user (e.g., "Point camera at a QR code") |
Step 2: Add the QR Scanner Bootstrap Script
Since the bot welcome screen HTML renders inside a Shadow DOM, standard <script> tags won't execute. The scanner uses an <img> tag with an onload handler to bootstrap the scanner logic:
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
style="display:none"
onload="
if (window._ai12zQrInit) return;
window._ai12zQrInit = true;
var _qrRoot = this.getRootNode();
/* Patch getElementById for shadow DOM compatibility */
if (_qrRoot !== document) {
var _origById = document.getElementById;
document.getElementById = function(id) {
return _origById.call(document, id) || _qrRoot.querySelector('#' + id);
};
}
var _qr = null, _qrOn = false, _libOk = false, _libLoading = false;
function _qrEl(id) { return _qrRoot.querySelector('#' + id); }
function _loadLib(cb) {
if (_libOk) return cb();
if (_libLoading) {
var iv = setInterval(function() {
if (_libOk) { clearInterval(iv); cb(); }
}, 100);
return;
}
_libLoading = true;
var s = document.createElement('script');
s.src = 'https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js';
s.onload = function() { _libOk = true; cb(); };
s.onerror = function() {
_libLoading = false;
var st = _qrEl('ai12z-qr-status');
if (st) st.textContent = 'Failed to load scanner library.';
};
document.head.appendChild(s);
}
function _setBtnLabel(text) {
var btn = _qrEl('ai12z-qr-scan-btn');
if (btn) {
var label = btn.querySelector('.ai12z-icon-label');
if (label) label.textContent = text;
}
}
function _onScan(txt) {
window.ai12zStopQrScanner();
if (navigator.vibrate) navigator.vibrate(100);
var st = _qrEl('ai12z-qr-status');
if (st) st.textContent = 'QR code detected!';
_setBtnLabel('Scan');
if (typeof ai12zBot !== 'undefined' && ai12zBot.sendMessage) {
ai12zBot.sendMessage(txt);
}
}
function _startScan() {
var st = _qrEl('ai12z-qr-status');
if (st) st.textContent = 'Loading scanner...';
_setBtnLabel('Loading...');
_loadLib(function() {
var st2 = _qrEl('ai12z-qr-status');
if (st2) st2.textContent = 'Requesting camera access...';
_qr = new Html5Qrcode('ai12z-qr-reader');
_qr.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: { width: 220, height: 220 }, aspectRatio: 1.0 },
_onScan
)
.then(function() {
_qrOn = true;
_setBtnLabel('Stop Scanning');
var s = _qrEl('ai12z-qr-status');
if (s) s.textContent = 'Point camera at a QR code';
})
.catch(function(err) {
_setBtnLabel('Scan');
var s = _qrEl('ai12z-qr-status');
var e = String(err);
if (e.indexOf('NotAllowedError') !== -1 || e.indexOf('Permission') !== -1) {
if (s) s.textContent = 'Camera permission denied. Please allow camera access.';
} else if (e.indexOf('NotFoundError') !== -1) {
if (s) s.textContent = 'No camera found on this device.';
} else {
if (s) s.textContent = 'Could not start camera: ' + e;
}
});
});
}
window.ai12zStopQrScanner = function() {
if (_qr && _qrOn) { _qr.stop().catch(function(){}); _qrOn = false; }
_setBtnLabel('Scan');
var st = _qrEl('ai12z-qr-status');
if (st) st.textContent = '';
};
window.ai12zToggleQrScanner = function() {
if (_qrOn) { window.ai12zStopQrScanner(); } else { _startScan(); }
};
"
/>
Key Components Explained
Shadow DOM Compatibility
The bot renders its welcome screen inside a Shadow DOM, which means:
<script>tags in the HTML won't execute — the<img onload>pattern is used as a workaround to initialize the scanner logicdocument.getElementByIdcan't find elements inside the shadow root — the script patches it to also search inside the shadow DOM
Library Loading
The scanner uses the html5-qrcode library (v2.3.8), which is loaded on demand the first time the user taps the Scan button. This avoids loading unnecessary resources until the feature is actually used.
Camera Configuration
_qr.start(
{ facingMode: 'environment' }, // Use rear camera on mobile
{ fps: 10, qrbox: { width: 220, height: 220 }, aspectRatio: 1.0 },
_onScan
)
- facingMode: 'environment' — Uses the rear-facing camera, which is better for scanning QR codes
- fps: 10 — Scans at 10 frames per second for a balance of performance and responsiveness
- qrbox — Defines a 220x220 pixel scanning area in the center of the viewfinder
Scan Result Handling
When a QR code is successfully scanned:
- The camera stops automatically
- The device vibrates briefly (on supported devices) as feedback
- The button label changes back to "Scan"
- The scanned text is sent to the bot via
ai12zBot.sendMessage(txt) - The bot processes the scanned content like any other user message
Button Label States
The scan button label updates to reflect the current state:
| State | Button Label |
|---|---|
| Idle | Scan |
| Loading library / requesting camera | Loading... |
| Camera active, scanning | Stop Scanning |
| QR code detected or stopped | Scan |
Global Functions
The bootstrap script exposes two global functions:
| Function | Description |
|---|---|
ai12zToggleQrScanner() | Starts the camera if idle, stops it if already scanning. Updates the button label accordingly. |
ai12zStopQrScanner() | Stops the camera and resets the button label back to "Scan" |
Use Cases
- Warehouse / Inventory — Scan product or bin QR codes to look up item details, stock levels, or location info through the bot
- Event Check-in — Attendees scan their ticket QR codes to check in and get event details
- Asset Management — Scan equipment tags to retrieve maintenance history or documentation
- Retail — Scan product QR codes to ask the bot for pricing, reviews, or comparisons
Troubleshooting
Camera permission denied: The user must grant camera access when the browser prompts. On iOS Safari, go to Settings > Safari > Camera and set it to Allow. On Android Chrome, go to Settings > Site Settings > Camera.
No camera found: The device does not have a camera, or the camera is in use by another application. Close other apps using the camera and try again.
Scanner not loading:
Ensure the page is served over HTTPS. Browsers require a secure context to access the camera. The html5-qrcode library also needs to load from the CDN — check that unpkg.com is not blocked by a firewall or content security policy.
QR code not detecting: Ensure adequate lighting and hold the device steady. The QR code should be within the scanning box area. Avoid glare or reflections on the QR code surface.