How to Build a JavaScript QR Code Generator with Python and Pyodide
In the previous article, we talked about what is QR code, its modes for encoding the data, and how to generate QR codes in Python.
In this article, we are going to build an online QR code generator to make it convenient to generate QR codes in the browser.
We will continue to use the Python library segno as the generation library. In order to run it in the browser, we will use pyodide to provide a Python environment for the browser.
What you’ll build: A browser-based QR code generator using JavaScript and the Python segno library (via Pyodide) that supports byte mode, structured append, custom error correction, and text encoding selection.
Key Takeaways
- You can run the Python segno library directly in the browser using Pyodide — no server required.
- The generator supports all QR code modes: numeric, alphanumeric, kanji, byte, and structured append for splitting data across multiple codes.
- Error correction levels (L/M/Q/H) and QR code versions (1–40) are configurable, giving fine-grained control over code density and damage resistance.
- Files can be encoded directly in byte mode, and text encoding (e.g. GBK vs. UTF-8) can be specified to optimize capacity for non-Latin scripts.
Common Developer Questions
- How do I generate QR codes dynamically in JavaScript without a server?
- How can I encode binary files into a QR code using byte mode?
- How do I split large data across multiple QR codes using structured append mode?
Prerequisites
- A modern web browser (Chrome, Firefox, Edge)
- A code editor (e.g. VS Code)
- Get a 30-day free trial license for Dynamsoft Barcode Reader if you want to scan the generated QR codes.
Step 1: Create the HTML Page
Create a new HTML file with the following template:
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Online QR Code Generator</title>
<style></style>
</head>
<body>
<h2>QR Code Generator</h2>
<script type="text/javascript"></script>
</body>
</html>
Step 2: Set Up the Python Environment with Pyodide
-
Include pyodide in the page.
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script> -
Load pyodide.
let pyodide = await loadPyodide(); -
Load
micropipand use it to install the segno library.await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); await micropip.install("segno"); -
We can test generating a QR code as SVG with the following code.
let code = ` import io import segno qrcode = segno.make_qr("Text") buff = io.BytesIO() qrcode.save(buff, kind='svg') buff.getvalue().decode("utf-8") `; let result = await pyodide.runPython(code);
Step 3: Read Data from TextArea or File Input
Next, let’s read the data to encode from a TextArea or a File Input.
HTML elements:
<label>
Content:
<select id="contentSelect">
<option>Text</option>
<option>Bytes</option>
</select>
</label>
<br/>
<textarea>Hello!</textarea>
<input style="display:none" type="file" id="fileInput"/>
If the content mode is selected as Bytes, load the file as base64 to pass it for segno to encode in byte mode. Otherwise, simply pass the text value in the TextArea.
JavaScript:
async function generateQRCodes(){
let contentIsBytes = document.getElementById("contentSelect").selectedIndex;
let base64_string = "";
if (contentIsBytes == 1) {
base64_string = await readFileAsBase64();
}
let content = document.querySelector("textarea").value;
try {
let code = `
import base64
import segno
content = '''`+content+`'''
base64_string = '''`+base64_string+`'''
if base64_string != '':
content = base64.b64decode(base64_string)
qrcode = segno.make_qr(content)
import io
buff = io.BytesIO()
qrcode.save(buff, kind='svg')
buff.getvalue().decode("utf-8")
`;
let result = await pyodide.runPython(code);
}catch(error){
alert(error);
}
}
function readFileAsBase64() {
return new Promise((resolve, reject) => {
let fileInput = document.getElementById("fileInput");
if (fileInput.files.length>0) {
let file = fileInput.files[0];
let fileReader = new FileReader();
fileReader.onload = function(e){
let dataURL = e.target.result;
let base64 = dataURL.substring(dataURL.indexOf(",")+1,dataURL.length);
resolve(base64);
};
fileReader.onerror = function () {
console.warn('oops, something went wrong.');
reject();
};
fileReader.readAsDataURL(file);
}else{
reject();
}
})
}
Step 4: Specify the QR Code Encoding Mode
A QR code has the following modes to encode the data: numeric, alphanumeric, kanji, byte, and structured append.
We can store data in several QR codes using the structured append mode. It will add some metadata describing the total page number and the page index of the QR code. If the content we need to store is larger than one QR code’s capacity (2953 bytes) or the version of the QR code will be too high if we store it in one QR code which makes it difficult to print and read, we can use this mode.
The library can decide which mode to use based on the input. But sometimes, we may need to specify the mode.
-
Add some HTML elements to specify the mode and the number of QR codes in structured append mode.
<label> Mode: <select id="modeSelect"> <option value="auto">Auto</option> <option value="numeric">Numeric</option> <option value="alphanumeric">Alphanumeric</option> <option value="kanji">Kanji</option> <option value="byte">Byte</option> <option value="structuredappend">Structrued Append</option> </select> </label> <label> Expected QR Code count for Structured Append mode: <input style="width:50px" type="number" value="2" id="countInput"/> </label> -
Pass the mode value in the Python code. Use
segno.make_sequenceto make QR codes in structured append mode and return the QR codes in a list.let count = document.getElementById("countInput").value; let mode = document.getElementById("modeSelect").selectedOptions[0].value; try { let code = ` import base64 import segno mode = '`+mode+`' if mode == "auto": mode = None content = '''`+content+`''' base64_string = '''`+base64_string+`''' if base64_string != '': content = base64.b64decode(base64_string) qrcodes = [] qrcode_svgs = [] if mode == "structuredappend": qrcode_seq = segno.make_sequence(content,symbol_count=`+count+`) for qrcode in qrcode_seq: qrcodes.append(qrcode) else: qrcode = segno.make_qr(content,mode=mode) qrcodes.append(qrcode) import io for qrcode in qrcodes: buff = io.BytesIO() qrcode.save(buff, kind='svg') svg = buff.getvalue().decode("utf-8") qrcode_svgs.append(svg) qrcode_svgs `; let result = await pyodide.runPython(code); let svgs = result.toJs(); //convert the Python list to a JavaScript array -
We can then append the QR codes into the document as
imgelements.let resultContainer = document.getElementById("result"); resultContainer.innerHTML = ""; for (let index = 0; index < svgs.length; index++) { const svg = svgs[index]; let decoded = unescape(encodeURIComponent(svg)); // Now we can use btoa to convert the svg to base64 let base64 = btoa(decoded); let imgSource = `data:image/svg+xml;base64,${base64}`; let img = document.createElement("img"); img.src = imgSource; img.className = "qrcode" img.style.width = document.getElementById("widthInput").value + "px"; resultContainer.appendChild(img); }
Step 5: Set the Error Correction Level
QR Code uses Reed–Solomon error correction so that even if the image is damaged, we can still read the data.
There are four levels of error correction. The higher the level, the more redundant data will be added to a QR code to make the QR code more resistant to damage. The highest level is H, whose percentage of data bytes that can be restored is 30%.
- L: 7%
- M: 15%
- Q: 25%
- H: 30%
-
Add some HTML elements for specifying the error correction level.
<label> Error Correction Level: <select id="errorCorrectionLevelSelect"> <option value="L">L(7%)</option> <option value="M">M(15%)</option> <option value="Q">Q(25%)</option> <option value="H">H(30%)</option> </select> </label> -
In the Python code, set the error correction level.
if mode == "structuredappend": qrcode_seq = segno.make_sequence(content,error=errorCorrectionLevel, symbol_count=`+count+`) else: qrcode = segno.make_qr(content,error=errorCorrectionLevel,mode=mode)
Step 6: Set the QR Code Version
The symbol versions of QR Code range from Version 1 to Version 40. Each version has a different module configuration or number of modules. The higher the version, the more number of modules can a QR code has so that it can store more data.
-
Add some HTML elements for specifying the version.
<label> Version: <select id="versionSelect"> <option value="0">Auto</option> </select> </label> <script> loadVersions(); function loadVersions(){ const versionSelect = document.getElementById("versionSelect"); for (let index = 1; index <= 40; index++) { const option = new Option(index,index); versionSelect.appendChild(option); } } </script> -
In the Python code, set the version.
if mode == "structuredappend": qrcode_seq = segno.make_sequence(content,error=errorCorrectionLevel, version=version, symbol_count=`+count+`) else: qrcode = segno.make_qr(content,error=errorCorrectionLevel, version=version, mode=mode)
Step 7: Specify Text Encoding for Byte Mode
We can specify the text encoding in byte mode to store more characters. For example, UTF-8 needs three bytes to encode a Chinese Hanzi while GBK only needs two bytes.
-
Add some HTML elements to select the text encoding. The list of encodings is from Python’s docs as we will encode the text in Python.
<label> Text Encoding in Byte Mode: <select id="encodingSelect"> </select> </label> <script> loadEncodings(); function loadEncodings(){ const encodingSelect = document.getElementById("encodingSelect"); const encodings = ["ascii","big5","big5hkscs","cp037","cp273","cp424","cp437","cp500","cp720","cp737","cp775","cp850","cp852","cp855","cp856","cp857","cp858","cp860","cp861","cp862","cp863","cp864","cp865","cp866","cp869","cp874","cp875","cp932","cp949","cp950","cp1006","cp1026","cp1125","cp1140","cp1250","cp1251","cp1252","cp1253","cp1254","cp1255","cp1256","cp1257","cp1258","euc_jp","euc_jis_2004","euc_jisx0213","euc_kr","gb2312","gbk","gb18030","hz","iso2022_jp","iso2022_jp_1","iso2022_jp_2","iso2022_jp_2004","iso2022_jp_3","iso2022_jp_ext","iso2022_kr","latin_1","iso8859_2","iso8859_3","iso8859_4","iso8859_5","iso8859_6","iso8859_7","iso8859_8","iso8859_9","iso8859_10","iso8859_11","iso8859_13","iso8859_14","iso8859_15","iso8859_16","johab","koi8_r","koi8_t","koi8_u","kz1048","mac_cyrillic","mac_greek","mac_iceland","mac_latin2","mac_roman","mac_turkish","ptcp154","shift_jis","shift_jis_2004","shift_jisx0213","utf_32","utf_32_be","utf_32_le","utf_16","utf_16_be","utf_16_le","utf_7","utf_8","utf_8_sig"]; let utf8Index = encodings.indexOf("utf_8"); for (let index = 0; index < encodings.length; index++) { const encoding = encodings[index]; let option = new Option(encoding,encoding); encodingSelect.appendChild(option); } encodingSelect.selectedIndex = utf8Index; } </script> -
Encode the text using the selected encoding in Python.
if mode == "byte": content = content.encode(encoding)
Step 8: Download the Generated QR Codes
The QR codes we generated are appended as img elements in the document. We can then download the QR codes as SVG or JPEG.
function downloadAsJPEG(){
downloadQRCodeImages(false);
}
function downloadAsSVG(){
downloadQRCodeImages(true);
}
function convertSVGAsJPEG(svg){
const canvas = document.createElement("canvas");
canvas.width = svg.width;
canvas.height = svg.height;
const context = canvas.getContext("2d");
context.fillStyle = "white";
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(svg, 0, 0, svg.naturalWidth, svg.naturalHeight, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL("image/jpeg")
}
function downloadQRCodeImages(isSVG){
let resultContainer = document.getElementById("result");
let images = resultContainer.getElementsByTagName("img");
for (let index = 0; index < images.length; index++) {
const image = images[index];
let a = document.createElement("a");
if (isSVG) {
a.href = image.src;
a.download = (index+1)+".svg";
}else{
a.href = convertSVGAsJPEG(image);
a.download = (index+1)+".jpg";
}
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
Read the Generated QR Codes
You can use this Web QR Code Scanner based on Dynamsoft Barcode Reader to read the QR Codes we generated in this article.
Common Issues and Edge Cases
- QR code capacity exceeded: If your data is too large for a single QR code (max 2953 bytes),
segno.make_qrwill raise aDataOverflowError. Use structured append mode to split data across multiple QR codes, or reduce the content size. - Pyodide loading is slow on first visit: Pyodide downloads a ~20 MB Python runtime on first load. Show a loading indicator while
loadPyodide()andmicropip.install("segno")complete to avoid confusing users. - Text encoding mismatch when scanning: If you generate a QR code with a non-UTF-8 encoding (e.g. GBK), the reader must know the encoding used. Most QR code readers default to UTF-8, so scanned text may appear garbled unless the reader is configured to match.
Source Code
Get the source code to have a try: