Dynamic QR codes
Print a QR code once, change where it points later, and see how often it is scanned.
A dynamic QR code encodes a short link such as https://qrdx.dev/q/aB3dE5f instead of your final URL. Scanning it opens the short link, which redirects to the destination you set. You can change the destination at any time, so printed codes never go out of date, and every scan is counted.
Static QR codes from the playground and the qrdx library stay free and unlimited. Dynamic codes need a QRdx account.
Create a dynamic code
- Dashboard: open Dashboard, click New code, enter the destination URL and an optional title. Download the code as PNG or SVG.
- Playground: choose the URL content type, enter your link and turn on Make it dynamic. The QR code switches to a short link and keeps your design. If you edit the URL afterwards, click Point it to the new URL to update the destination. Save design to code stores the current design with the code.
Destinations must be http:// or https:// links. Links with a username or password, and links to other QRdx short links, are rejected.
Manage codes
From the dashboard you can:
- Edit link: change the destination or title. The printed code keeps working and opens the new destination on the next scan.
- Edit design in playground: opens the code in the editor, still bound to its short link.
- Archive: the short link shows a "no longer active" page until you restore the code. Archived codes don't count toward your plan limit.
- Move to trash: the short link shows a "no longer active" page (HTTP 410) and the code stops counting toward your plan. Restore it from Trash, or delete it forever there, which also removes its scan history. Deleting through the API (
DELETE /api/v1/codes/{id}) is permanent immediately. - Duplicate: copies the destination, design, folder, tags and notes into a new code with a new short link and no scans.
- Version history: every change is recorded on the code's page; restoring a version saves the current settings first, so a restore can be undone.
Organize codes
- Folders: nest up to 5 levels deep. Drag codes onto a folder or use Move to folder. Deleting a folder moves its codes and subfolders up one level.
- Tags: filter by several tags to see codes that have all of them.
- Favourites and notes: star codes and keep private notes; notes are searchable.
- Search covers titles, destinations, notes, tags and slugs (paste a short link to find its code).
- Bulk actions: select codes to move, tag, pause, archive, favourite, trash or download them as a ZIP of PNG or SVG files.
- CSV import: Codes → Import creates up to 1,000 dynamic codes from a CSV (destination, title, folder, tags, optional slug). Importing more than one row needs Pro.
Codes in a team belong to the team. Every member can create and edit them; members can delete their own codes, owners and admins can delete any team code.
Codes never die: downgrading your plan never turns off existing codes. Over the limit you can still edit and archive codes, you just can't create or restore more until you're under it.
Plans
| Free | Pro | Business | |
|---|---|---|---|
| Active dynamic codes | 10 | Unlimited | Unlimited |
| Scans | Unlimited | Unlimited | Unlimited |
| Scan history | 90 days | 3 years | Unlimited |
| Bulk creation via the API | No | Up to 100 per request | Up to 100 per request |
Team codes use the best plan among the team's owners. See Plans and limits for everything else each plan includes.
Scan analytics
Each code has an analytics page with scans over time, unique visitors, top countries and cities, devices, operating systems, browsers and referrers for the last 7, 30 or 90 days, 1 year or 3 years (limited by your plan's history). You can export the scans in the selected range as CSV.
What is recorded for each scan:
- Time (UTC), and country, region and city when the hosting platform provides them (Vercel geo headers on qrdx.dev)
- Device type, operating system and browser, detected from the user agent
- The referrer's host name only, never the full URL. Most QR scans have no referrer.
- A visitor hash: a salted SHA-256 of the IP address, user agent and day. It counts unique visitors per day; the IP address itself is never stored.
Known bots, link previews and browser prefetches are not counted.
REST API
Manage codes programmatically under https://qrdx.dev/api/v1/codes. Requests are authenticated with an OAuth access token (Authorization: Bearer <token>) that has the codes:read or codes:write scope. Token requests act on your personal workspace. Requests from a signed-in browser session also work and act on the active team.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/codes | List codes. Query: status (active, archived, all), q, limit (≤ 100), offset |
POST | /api/v1/codes | Create one code, or an array of up to 100 (arrays need Pro) |
GET | /api/v1/codes/{id} | Get one code |
PATCH | /api/v1/codes/{id} | Update destinationUrl, title, style, domainId (custom domains) or archived |
DELETE | /api/v1/codes/{id} | Delete a code and its scans |
GET | /api/v1/codes/{id}/scans | Analytics for range (7d, 30d, 90d, 365d, 1095d; limited by your plan's history). Add limit (≤ 500) for individual scans, and before (the last scannedAt) for the next page |
Create a code:
curl https://qrdx.dev/api/v1/codes \
-H "Authorization: Bearer $QRDX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"destinationUrl": "https://example.com/menu", "title": "Spring menu"}'{
"data": {
"id": "cm3x...",
"slug": "aB3dE5f",
"shortLink": "https://qrdx.dev/q/aB3dE5f",
"defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
"domain": null,
"destinationUrl": "https://example.com/menu",
"title": "Spring menu",
"contentType": "url",
"status": "active",
"style": null,
"scanCount": 0,
"lastScannedAt": null,
"archivedAt": null,
"createdAt": "2026-10-31T12:00:00.000Z",
"updatedAt": "2026-10-31T12:00:00.000Z",
"organizationId": null
}
}Change the destination:
curl -X PATCH https://qrdx.dev/api/v1/codes/cm3x... \
-H "Authorization: Bearer $QRDX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"destinationUrl": "https://example.com/summer-menu"}'style takes the same design fields as the playground (fgColor, bgColor, bodyPattern, cornerEyePattern, level, ...). Encode shortLink in your QR code, for example with the qrdx library.
Errors use one JSON shape, { "code": "...", "message": "...", "status": 400 }:
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid body or query (details in data) |
| 401 | UNAUTHORIZED, INVALID_TOKEN | Missing session, or an invalid or expired token |
| 402 | LIMIT_REACHED, PLAN_REQUIRED | Over your plan's active code limit, or bulk creation without Pro |
| 403 | INSUFFICIENT_SCOPE, FORBIDDEN | Token without the needed scope, or a team role that can't do this |
| 404 | NOT_FOUND | The code doesn't exist or isn't yours |
| 429 | RATE_LIMITED | Too many requests; retry after the Retry-After header |
Self-hosting
Dynamic codes work on self-hosted instances too:
NEXT_PUBLIC_SHORT_LINK_BASE_URLsets the origin of short links. It defaults to your app URL.SCAN_HASH_SALTsets the secret salt for visitor hashes. Without it, one is derived fromBETTER_AUTH_SECRET.- Country, region and city come from Vercel's geo headers. Behind Cloudflare only the country is available (
CF-IPCountry); elsewhere locations show as unknown. - Rate limits apply when Upstash Redis is configured.
Last updated on