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.
| Fact | Value |
|---|---|
| Tool | Retool |
| Difficulty | Beginner |
| Time required | 15-20 min |
| Compatibility | Retool Cloud and Self-hosted |
| Last updated | March 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
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.
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.
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 code70 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.M81 });82 83 // Send back the canvas data URL for download84 setTimeout(function() {85 const canvas = qrContainer.querySelector('canvas');86 if (canvas) {87 window.Retool.modelUpdate({88 imageDataUrl: canvas.toDataURL('image/png'),89 encodedData: data90 });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.
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`.
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}910// 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": 20015}1617// For URL QR codes:18{19 "data": "{{ 'https://yourapp.com/item/' + table1.selectedRow.id }}",20 "label": "Scan to view",21 "size": 16022}Expected result: When a row is selected in table1, the QR code updates to encode that row's data.
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.
1// Button On Click → Run Script:2await utils.downloadFile(3 customComponent1.model.imageDataUrl,4 'qrcode-' + (table1.selectedRow.id || 'download') + '.png',5 'image/png'6);78// 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 Code13// </a>Expected result: Clicking the Download button saves the QR code as a PNG file to the user's downloads folder.
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 }}`.
1// Barcode Scanner component event handler:2// Inspector → On Scan → Run Script:34// Store the scanned value in a temporary state5await scannedValue.setValue(barcodeScanner1.value);67// Then trigger a lookup query8await fetchItemByBarcode.trigger({9 additionalScope: {10 barcodeValue: barcodeScanner1.value11 }12});1314// SQL query: fetchItemByBarcode15// 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.
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.
1// JS Query: generateBatchQRPrint2// Opens a print window with QR codes for all items34const items = query1.data; // Array of { id, name, barcode } objects56const qrItems = items.map(item => ({7 id: item.id,8 name: item.name,9 data: `ITEM:${item.id}:${item.barcode}`10}));1112// Build print HTML using QR as images via API13const 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>`;3334const win = window.open('', '_blank');35win.document.write(printHtml);36win.document.close();37win.print();3839return { 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
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, H58 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.M81 });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: true90 });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.
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().
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation