Campaign generator

Generate a smart QR code that sends iPhone users to the App Store and Android users to Google Play.

App settings — saved in your browser
Must start with https://apps.apple.com
Must start with https://play.google.com/store
This is where your web team will upload the redirect.html file. Pick any path on your website — for example https://myapp.com/go/redirect.html. Once placed, it never moves or changes.
Must be a full URL starting with https://
Your numeric Apple ID — found in App Store Connect under your account settings. Leave blank if you don't need App Store campaign tracking.
App settings are saved automatically
Campaign details
QR Preview

Fill in the form
to generate

Recent campaigns
No campaigns yet
`; } // ── URL building ────────────────────────────────────────────────────────────── function buildUrls() { const ios = document.getElementById('ios').value.trim(); const android = document.getElementById('android').value.trim(); const pt = document.getElementById('pt').value.trim(); const campaignName = document.getElementById('campaign-name').value.trim() .toLowerCase().replace(/\s+/g, '_') || 'campaign'; const source = document.getElementById('source').value.trim().toLowerCase() || 'direct'; const medium = document.getElementById('medium').value; // Apple params: ct, pt, mt const iosParams = [ `ct=${campaignName}`, pt ? `pt=${pt}` : '', 'mt=8' ].filter(Boolean).join('&'); // Google UTM params const androidParams = [ `utm_source=${source}`, `utm_medium=${medium}`, `utm_campaign=${campaignName}` ].join('&'); const iosFull = ios + (ios.includes('?') ? '&' : '?') + iosParams; const androidFull = android + (android.includes('?') ? '&' : '?') + androidParams; return { iosFull, androidFull }; } function encodeUrls(iosUrl, androidUrl) { const payload = JSON.stringify({ ios: iosUrl, android: androidUrl }); // base64url encode return btoa(payload).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } function buildQrUrl(hosted, iosUrl, androidUrl) { const base = hosted.endsWith('/') ? hosted.slice(0, -1) : hosted; const encoded = encodeUrls(iosUrl, androidUrl); return `${base}?d=${encoded}`; } // ── Generate ───────────────────────────────────────────────────────────────── let currentQrUrl = null; function generate() { if (!validate()) return; saveSettings(); const hosted = document.getElementById('hosted').value.trim(); const campaignName = document.getElementById('campaign-name').value.trim() || 'Campaign'; const source = document.getElementById('source').value.trim() || 'direct'; const { iosFull, androidFull } = buildUrls(); const qrUrl = buildQrUrl(hosted, iosFull, androidFull); currentQrUrl = qrUrl; // Render QR const output = document.getElementById('qr-output'); output.innerHTML = ''; new QRCode(output, { text: qrUrl, width: 260, height: 260, correctLevel: QRCode.CorrectLevel.M, colorDark: '#1a1a18', colorLight: '#ffffff' }); // Show download button document.getElementById('download-btn').classList.add('show'); // Show campaign tag const tag = document.getElementById('campaign-tag'); tag.innerHTML = `${campaignName} · ${source}`; tag.classList.add('show'); // Show next steps const isFirst = !JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]').length; document.getElementById('next-steps').style.display = 'block'; document.getElementById('next-steps-first').style.display = isFirst ? 'block' : 'none'; document.getElementById('next-steps-campaign').style.display = isFirst ? 'none' : 'block'; // Save to history saveHistory({ name: campaignName, source, qrUrl, date: new Date().toLocaleDateString() }); renderHistory(); } // ── Download ───────────────────────────────────────────────────────────────── function downloadQR() { const canvas = document.querySelector('#qr-output canvas'); if (!canvas) return; const campaignName = document.getElementById('campaign-name').value.trim() .toLowerCase().replace(/\s+/g, '-') || 'campaign'; const link = document.createElement('a'); link.download = `appqr-${campaignName}.png`; link.href = canvas.toDataURL('image/png'); link.click(); } // ── History ────────────────────────────────────────────────────────────────── function saveHistory(entry) { try { const history = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); history.unshift(entry); localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(0, 10))); } catch(e) {} } function renderHistory() { const list = document.getElementById('history-list'); try { const history = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); if (!history.length) { list.innerHTML = '
No campaigns yet
'; return; } list.innerHTML = history.map((h, i) => `
${h.name}
${h.source} · ${h.date}
`).join(''); } catch(e) {} } function reloadCampaign(index) { try { const history = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); const h = history[index]; if (!h) return; currentQrUrl = h.qrUrl; const output = document.getElementById('qr-output'); output.innerHTML = ''; new QRCode(output, { text: h.qrUrl, width: 260, height: 260, correctLevel: QRCode.CorrectLevel.M, colorDark: '#1a1a18', colorLight: '#ffffff' }); document.getElementById('download-btn').classList.add('show'); const tag = document.getElementById('campaign-tag'); tag.innerHTML = `${h.name} · ${h.source}`; tag.classList.add('show'); document.getElementById('campaign-name').value = h.name; document.getElementById('source').value = h.source; } catch(e) {} } // ── Init ───────────────────────────────────────────────────────────────────── loadSettings(); renderHistory(); // Show first-time banner if no settings saved yet try { const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); if (!saved.ios && !saved.android) { document.getElementById('first-time-banner').style.display = 'block'; } } catch(e) {}