QRdx

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

FreeProBusiness
Active dynamic codes10UnlimitedUnlimited
ScansUnlimitedUnlimitedUnlimited
Scan history90 days3 yearsUnlimited
Bulk creation via the APINoUp to 100 per requestUp 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.

MethodPathDescription
GET/api/v1/codesList codes. Query: status (active, archived, all), q, limit (≤ 100), offset
POST/api/v1/codesCreate 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}/scansAnalytics 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 }:

StatusCodeWhen
400VALIDATION_ERRORInvalid body or query (details in data)
401UNAUTHORIZED, INVALID_TOKENMissing session, or an invalid or expired token
402LIMIT_REACHED, PLAN_REQUIREDOver your plan's active code limit, or bulk creation without Pro
403INSUFFICIENT_SCOPE, FORBIDDENToken without the needed scope, or a team role that can't do this
404NOT_FOUNDThe code doesn't exist or isn't yours
429RATE_LIMITEDToo many requests; retry after the Retry-After header

Self-hosting

Dynamic codes work on self-hosted instances too:

  • NEXT_PUBLIC_SHORT_LINK_BASE_URL sets the origin of short links. It defaults to your app URL.
  • SCAN_HASH_SALT sets the secret salt for visitor hashes. Without it, one is derived from BETTER_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

On this page