Skip to main content
RapidDev - Software Development Agency
retool-tutorial

How to Use QR Codes in Retool Applications

Retool doesn't have a native QR code component, but you can generate QR codes in an HTML component using the qrcode.js CDN library. Pass dynamic data via the model object and render the QR code to a canvas element. For scanning, Retool Mobile (the native app) includes a Barcode Scanner component. Download QR codes by converting the canvas to a data URL.

What you'll learn

  • How to generate QR codes in a Retool HTML component using the qrcode.js library
  • How to make QR codes dynamic by binding the encoded data to {{ }} expressions
  • How to scan QR codes in Retool Mobile using the built-in barcode scanner component
  • How to print or download a generated QR code from a Retool app
  • How to customize QR code size, colors, and error correction level
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read15-20 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Retool doesn't have a native QR code component, but you can generate QR codes in an HTML component using the qrcode.js CDN library. Pass dynamic data via the model object and render the QR code to a canvas element. For scanning, Retool Mobile (the native app) includes a Barcode Scanner component. Download QR codes by converting the canvas to a data URL.

Quick facts about this guide
FactValue
ToolRetool
DifficultyBeginner
Time required15-20 min
CompatibilityRetool Cloud and Self-hosted
Last updatedMarch 2026

QR Code Generation and Scanning in Retool

Retool doesn't include a built-in QR code component, but building one is straightforward using the HTML Custom Component (iframe widget) with the qrcode.js library loaded from a CDN. The library renders QR codes to a canvas element, and you control the encoded content by passing data through the model object.

Common use cases in Retool apps: generating unique QR codes per inventory item or order, scanning QR codes on a warehouse floor using Retool Mobile, displaying QR codes for customer loyalty programs or event check-ins, and generating payment QR codes linked to specific orders.

For scanning, Retool Mobile (the iOS/Android native app) includes a Barcode Scanner component that reads QR codes and returns the decoded string. This tutorial covers both generation (web) and scanning (mobile).

Prerequisites

  • A Retool app in editor mode
  • Access to the internet (for loading qrcode.js from CDN)
  • For scanning: Retool Mobile app installed on a test device
  • Basic understanding of HTML components in Retool

Step-by-step guide

1

Add an HTML component to the canvas

In the Retool component panel, search for 'Custom Component' and drag it onto the canvas. Resize it to approximately 200x200px — QR codes need to be at least 100x100 for reliable scanning. Select the component. In the Inspector, find the HTML/CSS/JS editor area. This is where you'll paste the QR code generation code.

Expected result: A blank Custom Component frame appears on the canvas, ready for HTML content.

2

Write the QR code generation HTML

Paste the following HTML into the Custom Component's HTML editor. It loads qrcode.js from a CDN and uses window.Retool.subscribe() to receive the data to encode. When the model changes (because the {{ }} expression in the Model field updates), the QR code regenerates automatically.

typescript
1<!DOCTYPE html>
2<html>
3<head>
4 <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
5 <style>
6 body {
7 margin: 0;
8 display: flex;
9 flex-direction: column;
10 align-items: center;
11 justify-content: center;
12 height: 100vh;
13 background: transparent;
14 font-family: Inter, sans-serif;
15 }
16 #qrcode {
17 padding: 8px;
18 background: white;
19 border-radius: 8px;
20 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
21 }
22 #qrcode canvas, #qrcode img {
23 display: block !important;
24 }
25 #label {
26 margin-top: 8px;
27 font-size: 12px;
28 color: #6B7280;
29 text-align: center;
30 max-width: 180px;
31 overflow: hidden;
32 text-overflow: ellipsis;
33 white-space: nowrap;
34 }
35 #error {
36 color: #EF4444;
37 font-size: 12px;
38 text-align: center;
39 }
40 </style>
41</head>
42<body>
43 <div id="qrcode"></div>
44 <div id="label"></div>
45 <div id="error"></div>
46
47 <script>
48 let qrInstance = null;
49 const qrContainer = document.getElementById('qrcode');
50 const labelEl = document.getElementById('label');
51 const errorEl = document.getElementById('error');
52
53 window.Retool.subscribe(function(model) {
54 const data = model.data || '';
55 const size = model.size || 180;
56 const label = model.label || '';
57 const darkColor = model.darkColor || '#000000';
58 const lightColor = model.lightColor || '#ffffff';
59
60 if (!data) {
61 errorEl.textContent = 'No data to encode';
62 qrContainer.innerHTML = '';
63 return;
64 }
65
66 errorEl.textContent = '';
67 labelEl.textContent = label;
68
69 // Clear previous QR code
70 qrContainer.innerHTML = '';
71 qrInstance = null;
72
73 try {
74 qrInstance = new QRCode(qrContainer, {
75 text: data,
76 width: size,
77 height: size,
78 colorDark: darkColor,
79 colorLight: lightColor,
80 correctLevel: QRCode.CorrectLevel.M
81 });
82
83 // Send back the canvas data URL for download
84 setTimeout(function() {
85 const canvas = qrContainer.querySelector('canvas');
86 if (canvas) {
87 window.Retool.modelUpdate({
88 imageDataUrl: canvas.toDataURL('image/png'),
89 encodedData: data
90 });
91 }
92 }, 100);
93
94 } catch(e) {
95 errorEl.textContent = 'Error: ' + e.message;
96 }
97 });
98 </script>
99</body>
100</html>

Expected result: The HTML component displays a QR code once model data is provided.

3

Configure the Model to pass data into the QR widget

With the Custom Component selected, find the Model section in the Inspector. Enter a JSON object that controls the QR code content and appearance. Use `{{ }}` expressions to bind to live Retool data: Required: `data` — the string to encode (URL, text, JSON, or any string up to ~2,000 chars for reliable scanning). Optional: `size`, `label`, `darkColor`, `lightColor`.

typescript
1// Inspector → Model field:
2{
3 "data": "{{ table1.selectedRow.url || table1.selectedRow.barcode || '' }}",
4 "size": 180,
5 "label": "{{ table1.selectedRow.name || '' }}",
6 "darkColor": "#111827",
7 "lightColor": "#ffffff"
8}
9
10// For order QR codes:
11{
12 "data": "{{ 'ORDER:' + table1.selectedRow.order_id + ':' + table1.selectedRow.customer_id }}",
13 "label": "Order #{{ table1.selectedRow.order_id }}",
14 "size": 200
15}
16
17// For URL QR codes:
18{
19 "data": "{{ 'https://yourapp.com/item/' + table1.selectedRow.id }}",
20 "label": "Scan to view",
21 "size": 160
22}

Expected result: When a row is selected in table1, the QR code updates to encode that row's data.

4

Enable QR code download

The HTML widget sends back a canvas data URL via modelUpdate({ imageDataUrl: ... }). Use this to implement a Download button that saves the QR code as a PNG. Add a Button component below the Custom Component. In its event handler, add an On Click → Run Script: ```javascript await utils.downloadFile(customComponent1.model.imageDataUrl, 'qrcode.png', 'image/png'); ``` Alternatively, use a Text component with an <a> tag in HTML mode to create a download link.

typescript
1// Button On Click → Run Script:
2await utils.downloadFile(
3 customComponent1.model.imageDataUrl,
4 'qrcode-' + (table1.selectedRow.id || 'download') + '.png',
5 'image/png'
6);
7
8// Text component in HTML mode (creates a download link):
9// <a href="{{ customComponent1.model.imageDataUrl }}"
10// download="qrcode.png"
11// style="display:inline-block;padding:8px 16px;background:#7C3AED;color:white;border-radius:6px;text-decoration:none;font-size:14px;">
12// Download QR Code
13// </a>

Expected result: Clicking the Download button saves the QR code as a PNG file to the user's downloads folder.

5

Add a Barcode Scanner for QR scanning in Retool Mobile

For scanning QR codes (e.g., in a warehouse app), use Retool Mobile (the iOS/Android native app). In your Retool app, add a Barcode Scanner component — it only works in Retool Mobile, not in the web browser version. In the Barcode Scanner Inspector, configure: Scan mode (QR, barcode, or both), Continuous scanning (for rapid multi-scan), and the On Scan event handler. When a QR code is scanned, the decoded text is available as `{{ barcodeScanner1.value }}`.

typescript
1// Barcode Scanner component event handler:
2// Inspector → On Scan → Run Script:
3
4// Store the scanned value in a temporary state
5await scannedValue.setValue(barcodeScanner1.value);
6
7// Then trigger a lookup query
8await fetchItemByBarcode.trigger({
9 additionalScope: {
10 barcodeValue: barcodeScanner1.value
11 }
12});
13
14// SQL query: fetchItemByBarcode
15// SELECT * FROM inventory WHERE barcode = {{ barcodeValue }}

Expected result: Scanning a QR code in Retool Mobile triggers the lookup query and populates the app with the scanned item's data.

6

Generate QR codes for all table rows at once

For batch QR generation (e.g., printing QR labels for all inventory items), use a JS query to generate all QR codes and assemble a printable HTML page. This approach generates multiple QR codes programmatically and opens a print dialog.

typescript
1// JS Query: generateBatchQRPrint
2// Opens a print window with QR codes for all items
3
4const items = query1.data; // Array of { id, name, barcode } objects
5
6const qrItems = items.map(item => ({
7 id: item.id,
8 name: item.name,
9 data: `ITEM:${item.id}:${item.barcode}`
10}));
11
12// Build print HTML using QR as images via API
13const printHtml = `
14<html><head>
15 <style>
16 body { font-family: sans-serif; }
17 .grid { display: flex; flex-wrap: wrap; gap: 16px; padding: 16px; }
18 .card { width: 120px; text-align: center; }
19 .card img { width: 100px; height: 100px; }
20 .card p { font-size: 10px; margin: 4px 0; }
21 </style>
22</head><body>
23 <div class="grid">
24 ${qrItems.map(item => `
25 <div class="card">
26 <img src="https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(item.data)}" />
27 <p><strong>${item.name}</strong></p>
28 <p>${item.id}</p>
29 </div>
30 `).join('')}
31 </div>
32</body></html>`;
33
34const win = window.open('', '_blank');
35win.document.write(printHtml);
36win.document.close();
37win.print();
38
39return { count: items.length };

Expected result: A printable browser window opens with a grid of QR code labels for all items in the query results.

Complete working example

Custom Component HTML (QR code generator)
1<!DOCTYPE html>
2<html>
3<head>
4 <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
5 <style>
6 * { box-sizing: border-box; }
7 body {
8 margin: 0;
9 display: flex;
10 flex-direction: column;
11 align-items: center;
12 justify-content: center;
13 min-height: 100vh;
14 padding: 16px;
15 background: transparent;
16 font-family: -apple-system, Inter, sans-serif;
17 }
18 #qrcode {
19 padding: 12px;
20 background: white;
21 border-radius: 8px;
22 box-shadow: 0 1px 4px rgba(0,0,0,0.12);
23 }
24 #qrcode canvas { display: block !important; }
25 #label {
26 margin-top: 8px;
27 font-size: 11px;
28 color: #6B7280;
29 text-align: center;
30 max-width: 200px;
31 overflow: hidden;
32 text-overflow: ellipsis;
33 white-space: nowrap;
34 }
35 #error { color: #EF4444; font-size: 12px; margin-top: 8px; }
36 #empty { color: #9CA3AF; font-size: 13px; text-align: center; }
37 </style>
38</head>
39<body>
40 <div id="qrcode"></div>
41 <div id="label"></div>
42 <div id="error"></div>
43 <div id="empty">Select a row to generate QR code</div>
44
45 <script>
46 const qrContainer = document.getElementById('qrcode');
47 const labelEl = document.getElementById('label');
48 const errorEl = document.getElementById('error');
49 const emptyEl = document.getElementById('empty');
50
51 window.Retool.subscribe(function(model) {
52 const data = model.data;
53 const size = parseInt(model.size) || 180;
54 const label = model.label || '';
55 const darkColor = model.darkColor || '#000000';
56 const lightColor = model.lightColor || '#ffffff';
57 const errorLevel = model.errorLevel || 'M'; // L, M, Q, H
58
59 errorEl.textContent = '';
60
61 if (!data) {
62 qrContainer.innerHTML = '';
63 emptyEl.style.display = 'block';
64 labelEl.textContent = '';
65 return;
66 }
67
68 emptyEl.style.display = 'none';
69 labelEl.textContent = label;
70 qrContainer.innerHTML = '';
71
72 try {
73 const levelMap = { L: QRCode.CorrectLevel.L, M: QRCode.CorrectLevel.M, Q: QRCode.CorrectLevel.Q, H: QRCode.CorrectLevel.H };
74 new QRCode(qrContainer, {
75 text: String(data),
76 width: size,
77 height: size,
78 colorDark: darkColor,
79 colorLight: lightColor,
80 correctLevel: levelMap[errorLevel] || QRCode.CorrectLevel.M
81 });
82
83 setTimeout(() => {
84 const canvas = qrContainer.querySelector('canvas');
85 if (canvas) {
86 window.Retool.modelUpdate({
87 imageDataUrl: canvas.toDataURL('image/png'),
88 encodedData: data,
89 isReady: true
90 });
91 }
92 }, 150);
93 } catch(e) {
94 errorEl.textContent = 'QR Error: ' + e.message;
95 window.Retool.modelUpdate({ isReady: false, error: e.message });
96 }
97 });
98 </script>
99</body>
100</html>

Common mistakes

Why it's a problem: Trying to use the Barcode Scanner component in the web browser version of Retool

How to avoid: The Barcode Scanner component only works in Retool Mobile (the native iOS/Android app). In the web browser, it will show a disabled state. For web-based QR scanning, you'd need a custom HTML component using a JavaScript camera/QR library like html5-qrcode.

Why it's a problem: Encoding too much data in a QR code (full URLs with long query strings, large JSON objects) causing scanning failures

How to avoid: QR codes work best with short strings. Store an ID in the QR code and look up the full record from the database when scanning. For example, encode 'ITEM:12345' instead of the entire item JSON.

Why it's a problem: Reading customComponent1.model.imageDataUrl immediately after the component loads without waiting for the QR to render

How to avoid: The QR canvas renders asynchronously. The HTML component sends imageDataUrl back via modelUpdate() after a 100-150ms timeout. Check {{ customComponent1.model.isReady }} before using the imageDataUrl, or trigger the download from a button rather than automatically.

Best practices

  • Use error correction level M for standard QR codes; use H (30% redundancy) for codes on printed materials that may get dirty or torn
  • Keep QR code data concise — URLs under 100 characters scan fastest. Avoid embedding full JSON objects; use an ID and look up details server-side
  • Always show a label below the QR code so users know what it encodes, especially in inventory/warehouse contexts
  • Minimum QR code display size: 100x100px for screen scanning; 200x200px for printing and scanning at distance
  • For batch printing, use the api.qrserver.com API approach rather than rendering individual HTML components for each item
  • Test QR codes on both iOS and Android devices — some encoding characters (special characters, Unicode) may scan differently
  • For the Barcode Scanner in Retool Mobile, request camera permissions in the app and test in the actual Retool Mobile app, not the web preview

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a Retool inventory app and need to generate QR codes for each inventory item. Each item has an id and name field. I want: (1) a QR code displayed when a user selects an inventory item from a table, (2) the QR code to encode the item ID, (3) a Download button to save the QR code as a PNG. Explain how to build this using a Retool Custom Component (HTML iframe) with qrcode.js, the Model configuration, window.Retool.subscribe(), window.Retool.modelUpdate(), and utils.downloadFile().

Retool Prompt

Add a QR code generator to this Retool app: (1) add a Custom Component with the qrcode.js HTML implementation, (2) set the Model to { data: '{{ table1.selectedRow.id }}', label: '{{ table1.selectedRow.name }}', size: 180 }, (3) add a Download button with On Click handler: await utils.downloadFile(customComponent1.model.imageDataUrl, 'qr-' + table1.selectedRow.id + '.png', 'image/png').

Frequently asked questions

Can I generate QR codes in Retool without using a Custom Component?

Yes — you can use an Image component with a URL from the api.qrserver.com service: set the Source to https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={{ encodeURIComponent(table1.selectedRow.id) }}. This requires an internet connection and sends the QR data to a third-party service. For offline or privacy-sensitive use cases, the Custom Component with qrcode.js is better as it generates entirely client-side.

What is the maximum amount of data I can encode in a QR code in Retool?

QR codes can theoretically store up to ~7,000 numeric characters or ~4,000 alphanumeric characters, but practical scanning reliability drops above ~500 characters. For best performance in warehouse/real-world scanning conditions, keep QR data under 100 characters and use error correction level H (highest) for printed codes.

Can I print QR codes from a Retool app?

Yes. The batch print JS query approach in this tutorial opens a print-optimized window with QR codes for all items. For single QR code printing, download the QR code as a PNG using utils.downloadFile() and print from an image viewer, or use window.print() in an HTML component to trigger the browser's print dialog for just that component.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Learning is great. Shipping is faster with help.

Our engineers have built 600+ apps on Retool and the tools around it. If your project needs to be live sooner than your learning curve allows — book a free consultation.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.