- Appqr
Appqr
You made an app. It's on both stores. You need one QR code for your landing page, business card, and press kit. Every other tool charges a monthly subscription for this. AppQR is free, runs on your machine, and works forever.
npx appqr setup \
--ios "https://apps.apple.com/app/id123456789" \
--android "https://play.google.com/store/apps/details?id=com.myapp" \
--url "https://myapp.com/go"
✓ redirect.html → ./appqr-output/redirect.html
✓ qr.png → ./appqr-output/qr.png
→ Upload redirect.html to your site at: https://myapp.com/go
→ Done! For future campaigns, run: appqr campaign
How it works
Scan QR
↓
Phone opens: myapp.com/go?d=eyJpb3MiOiJodHRw...
↓
redirect.html detects device (client-side JS, no server)
↓
iOS user → App Store
Android → Google Play
The QR code encodes both store URLs as base64 in the URL itself. redirect.html is a tiny generic file that decodes and redirects — it contains zero campaign data and never needs to change. Every new campaign just generates a new QR image.
No AppQR servers are ever in the redirect chain. Everything runs on the developer's own website.
Install
npm install -g appqr
Or run without installing:
npx appqr setup ...
Usage
Step 1 — Setup (once per app)
appqr setup \
--ios "https://apps.apple.com/app/id123456789" \
--android "https://play.google.com/store/apps/details?id=com.myapp" \
--url "https://myapp.com/go"
With campaign tracking on your first QR:
appqr setup \
--ios "https://apps.apple.com/app/id123456789" \
--android "https://play.google.com/store/apps/details?id=com.myapp" \
--url "https://myapp.com/go" \
--ios-params "ct=launch&pt=12345&mt=8" \
--android-params "utm_source=launch&utm_campaign=v1&utm_medium=organic" \
--output ./public/go
Upload redirect.html to your site. That's the only file you ever upload. Never touch it again.
Step 2 — New campaign QR (as many times as you want)
appqr campaign \
--ios "https://apps.apple.com/app/id123456789" \
--android "https://play.google.com/store/apps/details?id=com.myapp" \
--url "https://myapp.com/go" \
--ios-params "ct=instagram_launch&pt=12345&mt=8" \
--android-params "utm_source=instagram&utm_campaign=launch&utm_medium=paid" \
--output ./qrs/instagram
✓ qr.png → ./qrs/instagram/qr.png
iOS URL: https://apps.apple.com/app/id123456789?ct=instagram_launch&pt=12345&mt=8
Android URL: https://play.google.com/store/apps/details?id=com.myapp&utm_source=instagram...
No file upload needed — redirect.html already handles this.
No upload. No website change. Just a new QR image for each campaign.
Show campaign param formats
appqr params
Use as a Node.js package
import { setup, campaign } from 'appqr'
// First time setup
const result = await setup({
ios: 'https://apps.apple.com/app/id123456789',
android: 'https://play.google.com/store/apps/details?id=com.myapp',
hostedUrl: 'https://myapp.com/go',
iosParams: 'ct=launch&pt=12345&mt=8',
androidParams: 'utm_source=launch&utm_campaign=v1&utm_medium=organic',
outputPath: './public/go'
})
// → result.redirectPath upload this once
// → result.qrPath use this as your QR image
// Campaign QR — no upload needed
const qr = await campaign({
ios: 'https://apps.apple.com/app/id123456789',
android: 'https://play.google.com/store/apps/details?id=com.myapp',
hostedUrl: 'https://myapp.com/go',
iosParams: 'ct=instagram_launch&pt=12345&mt=8',
androidParams: 'utm_source=instagram&utm_campaign=launch&utm_medium=paid',
outputPath: './qrs/instagram'
})
// → qr.qrPath your campaign QR image
Full TypeScript types included. No @types/appqr needed.
MCP — For AI agents (Claude, Cursor, Copilot)
AppQR ships with a built-in MCP (Model Context Protocol) server. AI agents that support MCP can generate smart QR codes natively — no CLI knowledge, no manual steps.
Add to Claude Desktop
{
"mcpServers": {
"appqr": {
"command": "npx",
"args": ["appqr", "mcp"]
}
}
}
Add to Cursor
{
"mcpServers": {
"appqr": {
"command": "npx",
"args": ["appqr", "mcp"]
}
}
}
After connecting, the agent can:
"Generate a smart QR code for my app. App Store: apps.apple.com/app/id123. Play Store: play.google.com/store/apps/details?id=com.app. Host it at myapp.com/go."
The agent calls appqr_setup or appqr_campaign directly and gets back the file paths.
MCP tools exposed
| Tool | Description |
|---|---|
appqr_setup | First time setup — generates redirect.html + qr.png |
appqr_campaign | New campaign QR — generates qr.png only |
appqr_validate | Validate store URLs before generating |
Campaign parameter formats
Apple App Store
| Param | What it is | Required | Example |
|---|---|---|---|
ct | Campaign token — your label | Recommended | ct=instagram_launch |
pt | Provider token — your numeric Apple ID | Recommended | pt=12345 |
mt | Media type — always 8 for apps | Yes | mt=8 |
--ios-params "ct=instagram_launch&pt=12345&mt=8"
Google Play (standard UTM)
| Param | What it is | Required | Example |
|---|---|---|---|
utm_source | Traffic source | Yes | utm_source=instagram |
utm_medium | Marketing medium | Yes | utm_medium=paid |
utm_campaign | Campaign name | Yes | utm_campaign=launch |
utm_content | Specific ad or creative | No | utm_content=banner_v1 |
utm_term | Keyword for search ads | No | utm_term=todo+app |
--android-params "utm_source=instagram&utm_campaign=launch&utm_medium=paid"
Why not just use Bitly / QR Tiger / Branch?
| Others | AppQR | |
|---|---|---|
| Smart iOS/Android redirect | ✓ paid | ✓ free |
| Pricing | $15–40/month | free |
| Your QR works if they shut down | ✗ | ✓ always |
| Campaign tracking params | ✓ paid | ✓ free |
| Separate Apple & Google params | ✗ rarely | ✓ yes |
| Agent / CLI friendly | ✗ | ✓ yes |
| MCP server | ✗ | ✓ yes |
| Your campaign data on their server | ✓ always | ✗ never |
Repository structure
appqr/
├── public/
│ ├── index.html # Landing page
│ └── llms.txt # AI agent discovery
├── api/
│ └── ping.js # Telemetry (Vercel serverless)
├── cli/
│ ├── bin/appqr.js # CLI entry point
│ ├── src/
│ │ ├── core.js # Core logic
│ │ ├── mcp.mjs # MCP server
│ │ ├── telemetry.js # Anonymous counters
│ │ ├── index.js # npm package exports
│ │ └── index.d.ts # TypeScript types
│ └── package.json
├── vercel.json
└── README.md # This file
Deploy the web backend (Vercel + Supabase)
The web backend hosts the landing page and collects anonymous usage telemetry (install count, usage count, agent vs human split — no personal data).
1. Deploy to Vercel
git clone https://github.com/harishakumarn/appqr
cd appqr
vercel deploy
Or connect via the Vercel dashboard — import the repo, one click deploy.
2. Set up Supabase (optional — for telemetry)
Run this SQL in your Supabase project:
create table counters (
id integer primary key default 1,
installs bigint default 0,
setups bigint default 0,
human_runs bigint default 0,
agent_runs bigint default 0,
qrs_generated bigint default 0,
constraint single_row check (id = 1)
);
insert into counters (id) values (1);
create or replace function increment_counter(col_name text)
returns void as $$
begin
execute format(
'update counters set %I = %I + 1 where id = 1',
col_name, col_name
);
end;
$$ language plpgsql;
Add to Vercel environment variables:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your-service-role-key
3. Update telemetry URL
In cli/src/telemetry.js, replace the TELEMETRY_URL with your Vercel deployment URL.
4. Publish to npm
cd cli
npm publish
Privacy & telemetry
AppQR sends one anonymous ping per run. No URLs, no campaign data, no store links, no personal information, no device identifiers are ever collected.
The only thing tracked is a counter increment for one of:
installed, setup, human_run, agent_run, qr_generated
Opt out permanently:
echo 'export APPQR_NO_TELEMETRY=1' >> ~/.zshrc
Or per-run:
APPQR_NO_TELEMETRY=1 appqr setup ...
Contributing
PRs welcome. The codebase is intentionally small — core logic is under 200 lines.
git clone https://github.com/harishakumarn/appqr
cd appqr/cli
npm install
node bin/appqr.js setup --help
License
MIT © AppQR Contributors
Server Config
{
"mcpServers": {
"appqr": {
"command": "npx",
"args": [
"appqr",
"mcp"
]
}
}
}