Pimp my QR is a self-hosted QR-code generator and management console. It is intentionally built like a technical toolbox/admin app rather than a marketing SaaS: compact navigation, dark mode, dashboard shortcuts, detailed project views, artifact downloads, health checks, and scan analytics.
It does not copy QR.io branding, wording, pricing, assets, or UI.
- Static QR codes for URL, text, email, phone, SMS, Wi-Fi, vCard, WhatsApp, calendar event, location, app links, and landing pages.
- Dynamic QR codes with
/r/:slugredirects, editable destinations, enabled/disabled state, optional expiration, fallback URL, and scan tracking. - QR design controls for module style, eye style, foreground/background color, frame text, quiet zone, error correction, and logo settings.
- Live preview with scan-readability warnings.
- Logo/image upload for PNG, JPG, SVG, and WebP assets.
- Seeded templates for restaurant menus, business cards, events, Wi-Fi, social profiles, packaging, coupons, and app downloads.
- Generated landing pages for dynamic QR codes.
- Analytics for scans, device type, browser, OS, and top QR projects.
- CRUD, duplicate, archive/unarchive, favorite/unfavorite, search, tags, and activity logs.
- Export artifacts for SVG, PNG, and PDF.
- System status view for backend, database, storage, and Redis placeholder.
cp .env.example .env
npm install
npm run db:generate
npm run db:migrate
npm run db:seed
npm run devDemo login after seeding:
demo@pimpmyqr.local
demo-password
Google sign-in is optional. Email/password authentication keeps working even when Google is not configured.
- Open Google Cloud Console.
- Create or select a Google Cloud project.
- Go to
APIs & Services->Credentials. - Select
Create Credentials->OAuth client ID. - Choose
Application type:Web application. - Add authorized JavaScript origins:
http://localhost:8082
https://your-production-domain.example
- Copy the generated Web Client ID. It looks like:
1234567890-abcdefg.apps.googleusercontent.com
- Add it to
.envfor both backend verification and frontend rendering:
GOOGLE_CLIENT_ID="1234567890-abcdefg.apps.googleusercontent.com"
VITE_GOOGLE_CLIENT_ID="1234567890-abcdefg.apps.googleusercontent.com"The frontend only receives the public Google client ID. The backend verifies the Google ID token audience against GOOGLE_CLIENT_ID, requires a verified email, and returns the same JWT format as email/password login.
After changing VITE_GOOGLE_CLIENT_ID, rebuild/restart the frontend because Vite embeds VITE_ variables at build time:
docker compose down
docker compose up --buildFor local npm development, restart:
npm run devDocker:
docker compose up --buildFrontend: http://localhost:8082
Backend: http://localhost:4000
Health: http://localhost:4000/health
In Docker, the frontend proxies /api, /r, /health, /uploaded-assets, and /exports to the backend. Use http://<server>:8082 from your browser; the browser does not need to reach backend port 4000 directly.
The Docker Compose stack uses its own PostgreSQL container, database, and volume:
Container: pimpmyqr-postgres
Database: pimpqr
User: pimpqr
Volume: pimpmyqr-postgres-data
Host port: 5434 -> container port 5432
The backend connects to this database inside Docker with:
postgresql://pimpqr:pimpqr@postgres:5432/pimpqr?schema=public
Auth:
POST /api/auth/registerPOST /api/auth/loginPOST /api/auth/googleGET /api/me
QR projects:
GET /api/qrPOST /api/qrGET /api/qr/:idPATCH /api/qr/:idDELETE /api/qr/:idPOST /api/qr/:id/duplicatePOST /api/qr/:id/favoritePOST /api/qr/:id/archivePOST /api/qr/:id/exportGET /api/qr/:id/artifacts
Templates, assets, analytics:
GET /api/templatesPOST /api/assetsGET /api/assetsDELETE /api/assets/:idGET /api/analytics/summaryGET /api/analytics/qr/:id
Runtime:
GET /r/:slugGET /health
Static QR codes encode content directly. After export or printing, the encoded content cannot be changed.
Dynamic QR codes encode a redirect URL. You can edit the destination later, disable the QR, expire it, and track scans.
Heavy customization can make QR codes harder to scan. Keep strong contrast, preserve a quiet zone, prefer high error correction when using logos, and test printed designs on multiple phones before production.
Scan analytics do not store raw full IP addresses. The backend hashes an anonymized IP prefix with a salt from IP_HASH_SALT. User agent, referrer, device type, browser, and OS are stored for operational analytics.
No external tracking services are used.
npm run build
npm run test
npm run lint- Team accounts and shared workspaces.
- Custom domains for dynamic QR redirects.
- Bulk QR generation.
- CSV import/export workflows.
- White-label landing pages.
- Advanced analytics with local GeoIP support.
- Billing/subscriptions and plan limits.
- S3-compatible object storage adapter.