Create a production-ready QR code generator with custom styling, logos, and download options using APIStack's free QR Code API. Perfect for beginners!
QR codes are everywhere - restaurant menus, event tickets, business cards, product packaging. But did you know you can build your own QR code generator in just 10 minutes?
In this tutorial, we'll create a beautiful, production-ready QR code generator with:
No backend needed. No credit card required. 100% free forever with APIStack's QR Code API.
A web app that lets users:
Time to complete: 10-15 minutes
Difficulty: Beginner-friendly
Prerequisites: Basic HTML/CSS/JavaScript knowledge
First, let's get your API key from APIStack:
Go to apistack.pro/signup
Create a free account in 30 seconds
Verify your email
Check your inbox for the verification link
Copy your API key
Found in your dashboard under "API Keys"
✨ Pro tip: No credit card required! APIStack's free tier includes 1,000 API calls per month - perfect for personal projects and small businesses.
Before writing code, let's make sure everything works:
Go to the API Playground
Find the QR Code Generator API
Try generating a test QR code
Enter "https://apistack.pro" and click Generate
Scan the QR code with your phone
It should open apistack.pro
Create a new folder and add three files:
mkdir qr-generator
cd qr-generator
touch index.html style.css script.jsThat's it! No npm install, no build tools, no complicated setup.
Open index.html and add:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR Code Generator - APIStack</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>🔲 QR Code Generator</h1>
<p>Create custom QR codes instantly</p>
</div>
<div class="generator-card">
<!-- Input Section -->
<div class="input-section">
<label for="qr-text">Enter URL or Text</label>
<textarea
id="qr-text"
placeholder="https://example.com"
rows="3"
></textarea>
<!-- Options Grid -->
<div class="options-grid">
<div class="option">
<label for="qr-size">Size (px)</label>
<input
type="number"
id="qr-size"
value="300"
min="100"
max="1000"
>
</div>
<div class="option">
<label for="qr-color">Color</label>
<input
type="color"
id="qr-color"
value="#000000"
>
</div>
<div class="option">
<label for="qr-bg">Background</label>
<input
type="color"
id="qr-bg"
value="#ffffff"
>
</div>
</div>
<button id="generate-btn" class="btn-primary">
Generate QR Code
</button>
</div>
<!-- Preview Section -->
<div class="preview-section">
<div id="qr-preview">
<p class="placeholder-text">
Your QR code will appear here
</p>
</div>
<button id="download-btn" class="btn-secondary" style="display: none;">
Download QR Code
</button>
</div>
</div>
<!-- Stats Footer -->
<div class="stats-footer">
<div class="stat">
<span class="stat-value">FREE</span>
<span class="stat-label">No cost</span>
</div>
<div class="stat">
<span class="stat-value">1000</span>
<span class="stat-label">Calls/month</span>
</div>
<div class="stat">
<span class="stat-value">∞</span>
<span class="stat-label">QR codes</span>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>What's happening here: We've created a simple form with a text area for the content, inputs for customization (size, colors), and a preview area for the generated QR code.
Open style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 40px 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
}
.generator-card {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
}
.input-section label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
#qr-text {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
font-family: inherit;
resize: vertical;
transition: border-color 0.3s;
}
#qr-text:focus {
outline: none;
border-color: #667eea;
}
.options-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin: 20px 0;
}
.option label {
font-size: 0.875rem;
}
.option input[type="number"],
.option input[type="color"] {
width: 100%;
padding: 8px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
}
.option input[type="color"] {
height: 45px;
cursor: pointer;
}
.btn-primary, .btn-secondary {
width: 100%;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #10b981;
color: white;
margin-top: 16px;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}
.preview-section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#qr-preview {
width: 100%;
min-height: 300px;
background: #f9fafb;
border: 2px dashed #d1d5db;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
#qr-preview img {
max-width: 100%;
border-radius: 8px;
}
.placeholder-text {
color: #9ca3af;
text-align: center;
font-size: 1rem;
}
.stats-footer {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 30px;
}
.stat {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 12px;
text-align: center;
color: white;
}
.stat-value {
display: block;
font-size: 2rem;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.875rem;
opacity: 0.9;
}
@media (max-width: 768px) {
.generator-card {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
.stats-footer {
grid-template-columns: 1fr;
}
}Design notes: This uses a modern gradient background, glassmorphism effects, and smooth animations. The layout is fully responsive and works perfectly on mobile devices.
Now the magic part! Open script.js:
// Replace with your APIStack key
const API_KEY = 'your_apistack_key_here';
const QR_API_URL = 'https://apistack.pro/api/qr/generate';
// DOM elements
const generateBtn = document.getElementById('generate-btn');
const downloadBtn = document.getElementById('download-btn');
const qrText = document.getElementById('qr-text');
const qrSize = document.getElementById('qr-size');
const qrColor = document.getElementById('qr-color');
const qrBg = document.getElementById('qr-bg');
const qrPreview = document.getElementById('qr-preview');
let currentQrUrl = '';
// Generate QR code
async function generateQR() {
const text = qrText.value.trim();
if (!text) {
alert('Please enter some text or URL');
return;
}
// Show loading state
generateBtn.textContent = 'Generating...';
generateBtn.disabled = true;
try {
// Build API URL with parameters
const params = new URLSearchParams({
data: text,
size: qrSize.value,
color: qrColor.value.replace('#', ''),
bgcolor: qrBg.value.replace('#', '')
});
const response = await fetch(`${QR_API_URL}?${params}`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
if (!response.ok) {
throw new Error('Failed to generate QR code');
}
// Get image URL
const blob = await response.blob();
currentQrUrl = URL.createObjectURL(blob);
// Display QR code
qrPreview.innerHTML = `<img src="${currentQrUrl}" alt="QR Code" />`;
// Show download button
downloadBtn.style.display = 'block';
} catch (error) {
console.error('Error:', error);
alert('Failed to generate QR code. Please try again.');
} finally {
generateBtn.textContent = 'Generate QR Code';
generateBtn.disabled = false;
}
}
// Download QR code
function downloadQR() {
if (!currentQrUrl) return;
const link = document.createElement('a');
link.href = currentQrUrl;
link.download = `qr-code-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Event listeners
generateBtn.addEventListener('click', generateQR);
downloadBtn.addEventListener('click', downloadQR);
// Generate on Enter key (in textarea)
qrText.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
generateQR();
}
});⚠️ Important: Replace 'your_apistack_key_here' with your actual API key from step 1!
Code breakdown: We're using the Fetch API to call APIStack's QR endpoint with custom parameters. The response is a PNG image that we display and allow users to download.
Open index.html in your browser and try:
✅ Generate a basic QR code
Enter "https://google.com" and click Generate
🎨 Try different colors
Change the color to blue (#0000FF)
📏 Adjust the size
Try 500px for a larger QR code
💾 Download it
Click the download button
📱 Scan with your phone
Use your camera app to verify it works
Solution:
Solution:
The API expects hex codes without the # symbol. Our code removes it automatically with .replace('#', '')
Solution:
You just built a fully functional QR code generator in 10 minutes! This same app could be used for:
APIStack has 104 APIs - all free to start. Build weather apps, currency converters, image processors, and more.
Found this tutorial helpful? Share it with your friends!