# QRdx documentation (full text) > The complete QRdx docs as Markdown: making and designing QR codes, dynamic QR codes, hosted pages and forms, smart routing, analytics, teams, notifications, webhooks, plans and limits, the developer tools (REST API, OpenAPI document, MCP server, image API, CLI, qrdx React library, shadcn/ui registry) and self-hosting. QRdx (qrdx.dev) is an open-source (AGPL-3.0) QR code platform. Index of the whole site: https://qrdx.dev/llms.txt. Each page is also available on its own at its URL plus .md. Sections, in order: - Introduction: 1 page - Help: 40 pages - Guides: 12 pages - Developers: 27 pages - React library: 22 pages - Self-hosting: 1 page --- # QRdx docs URL: https://qrdx.dev/docs Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/index.mdx Help for everyone who makes QR codes with QRdx, guides to every feature, and docs for developers. QRdx is an open-source (AGPL-3.0) QR code platform. Design static QR codes for free with no account, or sign in for dynamic codes you can change after printing, mobile pages and scan analytics. Every dynamic code keeps redirecting on every plan, even after a downgrade or cancellation. That's the [Never-Hostage Guarantee](https://qrdx.dev/guarantee). ## Where to start [#where-to-start] - [Help](https://qrdx.dev/docs/help): Plain answers for making codes, billing, teams and fixing problems. No code needed. - [Guides](https://qrdx.dev/docs/dynamic-qr-codes): Every product feature in depth: dynamic codes, pages, forms, routing, analytics and teams. - [Developers](https://qrdx.dev/docs/developers): The REST API with a Try it panel, webhooks, OAuth apps, MCP and the CLI. - [React library](https://qrdx.dev/docs/getting-started): Render and export styled QR codes with the qrdx package and the shadcn/ui registry. - [Self-hosting](https://qrdx.dev/docs/self-hosting): Run your own QRdx, dynamic codes included, with Docker Compose or Node.js. ## Quick links [#quick-links] - [Make your first QR code](https://qrdx.dev/docs/help/getting-started/first-qr-code) - [Static or dynamic, which do you need?](https://qrdx.dev/docs/help/getting-started/static-vs-dynamic) - [A code won't scan](https://qrdx.dev/docs/help/troubleshooting/wont-scan) - [Plans and limits](https://qrdx.dev/docs/plans-and-limits) - [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api) - [Install the React library](https://qrdx.dev/docs/getting-started) ## Stay up to date [#stay-up-to-date] - [Changelog](https://qrdx.dev/changelogs): every release, newest first. - [What's new and what's next](https://qrdx.dev/docs/help/whats-new). - [GitHub](https://github.com/bucharitesh/qrdx): source code, [issues](https://github.com/bucharitesh/qrdx/issues) and [discussions](https://github.com/bucharitesh/qrdx/discussions). Need a person? [Contact support](https://qrdx.dev/docs/help/contact). --- # Help center URL: https://qrdx.dev/docs/help Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/index.mdx Answers for making QR codes, dynamic codes, pages, billing and your account. No code needed. ## Common questions [#common-questions] - [Make your first QR code](https://qrdx.dev/docs/help/getting-started/first-qr-code) - [Static or dynamic, which do you need?](https://qrdx.dev/docs/help/getting-started/static-vs-dynamic) - [Change where a code points](https://qrdx.dev/docs/help/dynamic-codes/change-the-link) - [A code won't scan](https://qrdx.dev/docs/help/troubleshooting/wont-scan) - [Download and print a QR code](https://qrdx.dev/docs/help/getting-started/download-and-print) - [What happens to your codes if you stop paying](https://qrdx.dev/docs/help/account-and-billing/never-hostage) - [Cancel your subscription](https://qrdx.dev/docs/help/account-and-billing/cancel) - [A scan shows an error page](https://qrdx.dev/docs/help/troubleshooting/error-pages) ## Browse by topic [#browse-by-topic] - [Getting started](https://qrdx.dev/docs/help/getting-started/first-qr-code): Make, download and print your first code. Static or dynamic. - [Designing](https://qrdx.dev/docs/help/designing/colors-and-contrast): Colors, logos, frames and art, and codes that always scan. - [Dynamic codes](https://qrdx.dev/docs/help/dynamic-codes/create): Change the link, see scans, pause, protect and route codes. - [Pages and forms](https://qrdx.dev/docs/help/pages-and-forms/create-a-page): Mobile pages and forms, hosted by QRdx. - [Account and billing](https://qrdx.dev/docs/help/account-and-billing/plans): Plans, invoices, cancelling, your data and your account. - [Teams](https://qrdx.dev/docs/help/teams/create-and-invite): Invite people, roles and seats. - [Troubleshooting](https://qrdx.dev/docs/help/troubleshooting/wont-scan): Codes that won't scan, wrong pages, error pages and missing emails. - [Privacy and security](https://qrdx.dev/docs/help/privacy-and-security/scan-data): What a scan records, account safety and reporting abuse. ## More [#more] - [What's new and what's next](https://qrdx.dev/docs/help/whats-new), and the full [changelog](https://qrdx.dev/changelogs). - Building something? The [Developers](https://qrdx.dev/docs/developers) tab has the REST API, webhooks, MCP and the CLI. --- # Make your first QR code (Help) URL: https://qrdx.dev/docs/help/getting-started/first-qr-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/getting-started/first-qr-code.mdx Create a QR code in the playground and download it. Free, with no account and no watermark. You can make a QR code in about a minute. You don't need an account. ## Make the code [#make-the-code] 1. Open the [playground](https://qrdx.dev/playground). This is the QRdx editor. 2. **URL** is already selected. For Wi-Fi, a contact card, WhatsApp or anything else, click **Change** next to **QR code type** and pick a type. 3. Type your link in **Website address**. The preview shows your code as soon as the address is valid. 4. Optional: pick a look under **Start from a style**, or open the **Design** tab to change colors, dots, eyes, the logo and the frame. 5. Click the download icon above the preview, or press D. Choose a format and size, then download. Below the address, QRdx shows where scans will open, for example "Scans open example.com". Check it before you print. ## Before you print [#before-you-print] Check the **Scan score** under the preview. A score of 85 or more is great. Below 65, fix the checks marked with a cross. See [Check a design with the scan score](https://qrdx.dev/docs/help/designing/scan-score). Then scan the code with your own phone. ## Codes from the playground are static [#codes-from-the-playground-are-static] A code you download from the playground is **static**: the link is stored inside the code. It works forever, but you can't change it after printing. If you might need to change the link later, make it a dynamic code instead. See [Static or dynamic](https://qrdx.dev/docs/help/getting-started/static-vs-dynamic). ## Next steps [#next-steps] - [Download and print a QR code](https://qrdx.dev/docs/help/getting-started/download-and-print) - [Choose colors that scan](https://qrdx.dev/docs/help/designing/colors-and-contrast) - [Add a logo](https://qrdx.dev/docs/help/designing/logos) --- # Download and print a QR code (Help) URL: https://qrdx.dev/docs/help/getting-started/download-and-print Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/getting-started/download-and-print.mdx Pick the right file format and size, and get print-ready files with bleed and crop marks. ## Download [#download] In the [playground](https://qrdx.dev/playground), click the download icon above the preview, or press D. The **Download QR code** dialog opens with two tabs. ### Image file [#image-file] Use this for screens, email, slides and most printing. 1. Choose a format: **PNG**, **JPG**, **SVG**, **PDF** or **EPS**. 2. Choose a size: Email (200 px), Web (400 px), Social (800 px), Print (1200 px), Poster (2000 px), or type your own. 3. Turn on **Transparent background** if you need it. JPG can't be transparent. 4. Click the download button at the bottom. ### Print-ready [#print-ready] Use this when a print shop asks for a file. 1. Set the **code width** in millimeters or inches. 2. Choose **RGB** or **CMYK**. Print shops usually want CMYK. 3. Add **3 mm bleed** if the background runs to the edge of the page, so trimming never leaves a white line. 4. Turn on **Crop marks** to show where to cut. Crop marks are added to PDFs. 5. Download a **PDF**, **SVG** or **EPS** file. ## Which format to choose [#which-format-to-choose] - **PNG** or **JPG**: pixel images for screens, email and documents. - **SVG**, **PDF** or **EPS**: vector files that stay sharp at any size. Printers prefer these. ## How big to print [#how-big-to-print] Make the code about one tenth of the scanning distance. - A business card read from 20 cm needs a code about 2 cm wide. - A poster read from 1 m needs a code about 10 cm wide. Codes that hold more content need more room. The [print size calculator](https://qrdx.dev/tools/print-size) works out the minimum size for you. ## Leave a clear border [#leave-a-clear-border] Keep empty space around the code. Scanners need it to find the code. The **Padding** setting in the **Design** tab adds it for you; **Standard** (4 modules) is the safe choice. ## Test before you print [#test-before-you-print] Print one copy and scan it with two different phones, from the distance people will scan it. You can also upload a photo of a printed code to the [scan checker](https://qrdx.dev/tools/scan-checker). --- # Static or dynamic, which do you need? (Help) URL: https://qrdx.dev/docs/help/getting-started/static-vs-dynamic Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/getting-started/static-vs-dynamic.mdx Static codes store your content in the code and never change. Dynamic codes store a short link you can change after printing. ## Static QR codes [#static-qr-codes] A static code stores your content inside the code itself: the link, the Wi-Fi password or the contact card. - It works forever, with no account and no server. - It's free and unlimited. - You **can't change it** after printing. To change the content, you print a new code. - Scans aren't counted. Every code you download from the [playground](https://qrdx.dev/playground) without making it dynamic is static. ## Dynamic QR codes [#dynamic-qr-codes] A dynamic code stores a short link, such as `qrdx.dev/q/aB3xY7k`. The short link forwards each scan to your destination. - You can **change the destination** any time, even after printing. - Every scan is counted, with when, where and on which device. - You can pause it, schedule it, add a password or send scans to different links. - It needs a free QRdx account. The Free plan includes 10 dynamic codes; Pro and Business have no limit. See [Create a dynamic QR code](https://qrdx.dev/docs/help/dynamic-codes/create). ## Which to choose [#which-to-choose] Choose **dynamic** when: - the code goes on something printed that you can't easily replace, like packaging, signs or menus; - the link might change, like a seasonal offer or a menu; - you want to know how many people scan it. Choose **static** when: - the content never changes, like Wi-Fi details or a contact card; - you don't want an account; - you want nothing between the scan and your content. ## Pages are dynamic too [#pages-are-dynamic-too] A [mobile page](https://qrdx.dev/docs/help/pages-and-forms/create-a-page) hosted by QRdx gets a dynamic code. You can edit the page any time and the printed code keeps working. ## Dynamic codes keep working [#dynamic-codes-keep-working] A dynamic code keeps redirecting if you downgrade, cancel or a payment fails. See [What happens to your codes if you stop paying](https://qrdx.dev/docs/help/account-and-billing/never-hostage). --- # Choose colors that scan (Help) URL: https://qrdx.dev/docs/help/designing/colors-and-contrast Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/colors-and-contrast.mdx Keep a dark code on a light background, keep strong contrast, and use gradients carefully. Scanners look for dark squares on a light background. Most color problems come from breaking that rule. ## Change the colors [#change-the-colors] In the [playground](https://qrdx.dev/playground), open the **Design** tab, then **Colors**. - **Background** is the area behind the code. - **Foreground** colors the small squares that hold your data. - **Eyes** colors the three big squares in the corners. - **Dots** colors the center of each corner square. Each color can be solid, a linear gradient or a radial gradient. The palettes under the four colors set them all at once. ## Rules that keep a code scannable [#rules-that-keep-a-code-scannable] - **Dark code, light background.** Light codes on dark backgrounds fail in some camera apps. - **Strong contrast.** Aim for a contrast ratio of 4.5:1 or more between the code and the background. - **Gradients stay dark.** A gradient is fine when every color in it is dark enough against the background. - **Plain background.** A busy photo behind the code makes it harder to read. ## Check the contrast [#check-the-contrast] The contrast checker in the toolbar above the preview shows the ratio for your colors. It flags pairs under 4.5:1. The **Scan score** under the preview checks contrast too, along with size, clear space and the logo. See [Check a design with the scan score](https://qrdx.dev/docs/help/designing/scan-score). ## Related [#related] - [A code won't scan](https://qrdx.dev/docs/help/troubleshooting/wont-scan) - [Colors and gradients for developers](https://qrdx.dev/docs/features/colors-gradients) --- # Add a logo (Help) URL: https://qrdx.dev/docs/help/designing/logos Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/logos.mdx Put your logo in the center of a QR code without breaking scanning. ## Add your logo [#add-your-logo] 1. In the [playground](https://qrdx.dev/playground), open the **Design** tab, then **Logo**. 2. Turn on **Logo in the center**. 3. Pick one of the **Quick icons**, or click **Upload your logo**. 4. To change it later, click **Replace** or **Remove**. Logos can be PNG, JPG, WebP or SVG files up to 1 MB. A transparent PNG or an SVG looks best. ## Set error correction to High [#set-error-correction-to-high] A logo covers some of the code's squares. Error correction rebuilds them when the code is scanned. Open **Error correction** in the **Design** tab and choose **High** whenever a logo is on. High lets up to 30% of the code be covered. ## If the score drops [#if-the-score-drops] QRdx sizes the logo for you. If the **Scan score** drops after you add it, check that **Error correction** is set to **High**. If it still struggles, try **Logo QR**, which keeps every square in place. ## Want the logo in the dots instead? [#want-the-logo-in-the-dots-instead] **Logo QR** paints the dots inside your logo's shape, rather than placing the logo on top. See [Blend a logo or photo into the code](https://qrdx.dev/docs/help/designing/art). ## Related [#related] - [Check a design with the scan score](https://qrdx.dev/docs/help/designing/scan-score) - [Logos for developers](https://qrdx.dev/docs/features/logos) --- # Add a frame and a call to action (Help) URL: https://qrdx.dev/docs/help/designing/frames Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/frames.mdx Put a border and a short message like "Scan me" around your QR code. A frame tells people what they get when they scan. ## Add a frame [#add-a-frame] 1. In the [playground](https://qrdx.dev/playground), open the **Design** tab, then **Frame**. 2. Pick a frame style. 3. Type your text in **Label**, or pick a ready-made one: Scan me, Scan for menu, Join Wi-Fi, Leave a review, Follow us, Pay here, Save contact or Download the app. 4. Set the **Frame color**, **Text color** and **Text size**. Some frames also have a **Second line**. ## Frames never hurt scanning [#frames-never-hurt-scanning] Frames sit outside the code. They never cover any of its squares. Keep the frame text short. Three or four words read best from a distance. ## Download with the frame [#download-with-the-frame] The frame is part of every download, in every format. For print, see [Download and print a QR code](https://qrdx.dev/docs/help/getting-started/download-and-print). ## Related [#related] - [Frames and print for developers](https://qrdx.dev/docs/features/frames) --- # Blend a logo or photo into the code (Help) URL: https://qrdx.dev/docs/help/designing/art Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/art.mdx Logo QR and Image QR blend a picture into the dots themselves, with no AI. ## Logo QR and Image QR [#logo-qr-and-image-qr] In the [playground](https://qrdx.dev/playground), open the **Design** tab, then **Art**. Choose a mode: - **Logo QR** colors the dots inside your logo's shape. - **Image QR** turns the whole code into your photo. - **Classic** turns art off. Upload your own picture, or try a sample. Logos work best as a transparent PNG or an SVG. Photos work best with one clear subject. Art works in every download format, and there's no AI involved. ## Balance art and scanning [#balance-art-and-scanning] Choose how strong the art is: **Subtle**, **Balanced** or **Bold**. Stronger art looks better but is harder to scan. The **Scan check** below the art decodes your real code in your browser at three sizes. If it isn't great, it lists what to fix. For the best results, set **Error correction** to **High** and keep a clear border around the code. ## Related [#related] - [Check a design with the scan score](https://qrdx.dev/docs/help/designing/scan-score) - [Art QR codes for developers](https://qrdx.dev/docs/features/art-qr) --- # Check a design with the scan score (Help) URL: https://qrdx.dev/docs/help/designing/scan-score Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/scan-score.mdx The scan score tests your design the way a phone camera sees it, and tells you what to fix. Every design in the playground gets a **Scan score** from 0 to 100. It's shown under the preview, for example "Scan score 92 · Great". ## What the score means [#what-the-score-means] - **Great** (85 or more): ready to print. - **OK** (65 to 84): it should scan, but check the warnings. - **Risky** (below 65): fix the checks marked with a cross. - **Fails**: the code didn't decode in testing, or the contrast is far too low. Don't print it. ## See what to fix [#see-what-to-fix] Click the score. The **Will it scan?** panel lists every check, with the failed ones first. The score checks: - **Decoding**: the code is read at small sizes with a camera-like blur. - **Contrast**: the code and background should differ by 4.5:1 or more. - **Dark on light**: dark codes on light backgrounds scan in more apps. - **Logo size**: a logo must not cover more than error correction can rebuild. - **Eye shape**: circle and curly eyes fail in some scanner apps. - **Clear space**: the empty border around the code. Four modules is ideal. ## Quick fixes [#quick-fixes] - Low contrast: make the code darker or the background lighter. See [Choose colors that scan](https://qrdx.dev/docs/help/designing/colors-and-contrast). - Logo covers too much: set **Error correction** to **High**. See [Add a logo](https://qrdx.dev/docs/help/designing/logos). - Eyes: switch to square, rounded or gear eyes. - Clear space: set **Padding** to **Standard** or **Large**. ## Check a printed code [#check-a-printed-code] The score tests the design on screen. To test a code you already printed, take a photo and drop it into the [scan checker](https://qrdx.dev/tools/scan-checker). The image never leaves your device. ## Related [#related] - [A code won't scan](https://qrdx.dev/docs/help/troubleshooting/wont-scan) --- # Save and reuse a design (Help) URL: https://qrdx.dev/docs/help/designing/save-a-design Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/designing/save-a-design.mdx Save a design as a theme, reuse it on new codes and share the style with others. A **theme** is a saved design: colors, dots, eyes, logo and frame. It doesn't include your link or other content. ## Save a design as a theme [#save-a-design-as-a-theme] 1. In the [playground](https://qrdx.dev/playground), click the heart icon above the preview, or press S. 2. Sign in if you're asked to. 3. Name the theme and click **Save Theme**. Your themes are on the **Themes** page in your dashboard. The Free plan saves up to 10 themes; Pro and Business have no limit. ## Reuse a theme [#reuse-a-theme] On the **Themes** page, open a theme's menu: - **Edit in playground** opens it in the editor. - **Set as default** uses it for your new codes. - **Copy to team** shares it with a team workspace. - **Rename**, **Copy link**, **Publish** and **Delete** do what they say. **New theme** at the top of the page starts a new one. ## Share a style [#share-a-style] Click the share icon above the preview, or press X. QRdx asks you to save the design first, then gives you a link. The link shares the style, not your content. Anyone who opens it gets your colors, dots and eyes in the playground and adds their own link. ## Publish to the community [#publish-to-the-community] After you save a design, you can publish it to [community themes](https://qrdx.dev/community) for others to reuse. --- # Create a dynamic QR code (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/create Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/create.mdx Make a code you can change after printing, from the playground or from your dashboard. A dynamic code holds a short link that forwards to your destination. You need a free QRdx account. The Free plan includes 10 dynamic codes; Pro and Business have no limit. ## From the playground [#from-the-playground] 1. Open the [playground](https://qrdx.dev/playground) and type your link in **Website address**. 2. Under the address, find the **Dynamic QR code** card. 3. If you're signed out, click **Sign in to make it dynamic** and sign in. Your link is still there when you come back. 4. Click **Make it dynamic**. The card now shows your short link and "Scans go to" your destination. Download the code as usual: it now encodes the short link. Dynamic codes from the playground work for website links. For other content, create a [mobile page](https://qrdx.dev/docs/help/pages-and-forms/create-a-page), which also gets a dynamic code. ## From your dashboard [#from-your-dashboard] 1. Go to **Codes** in your dashboard and click **New code**. 2. Under **Link**, choose **Website link**. 3. Fill in **Destination URL** and, if you like, a **Title**. 4. Click **Create code**. You can then download the code or click **View analytics** to open it. ## Choose your own short link ending [#choose-your-own-short-link-ending] On Pro and Business, fill in **Custom back-half** to choose the end of the short link, for example `qrdx.dev/q/spring-menu`. It can be 3 to 64 letters, numbers, dashes or underscores. On Free, QRdx picks a short random ending for you. ## When you reach the limit [#when-you-reach-the-limit] On Free, only active codes count toward the 10. Archived codes and codes in the trash don't. Archive a code you no longer use, or [upgrade to Pro](https://qrdx.dev/pricing). ## Related [#related] - [Change where a code points](https://qrdx.dev/docs/help/dynamic-codes/change-the-link) - [Dynamic QR codes guide](https://qrdx.dev/docs/dynamic-qr-codes) --- # Change where a code points (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/change-the-link Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/change-the-link.mdx Edit the destination of a dynamic QR code. Printed copies open the new link on the next scan. You can change the destination of a dynamic code at any time. Every printed copy opens the new link on its next scan. ## From your dashboard [#from-your-dashboard] 1. Go to **Codes**. 2. Open the code's menu (**⋯**) and choose **Edit link**. You can also open the code and click **Edit link**. 3. Change the **Destination URL**. 4. Click **Save changes**. QRdx confirms with "Scans now open the updated destination." ## From the playground [#from-the-playground] If the code is open in the playground, change the address. The card says "Printed codes still open" your old link until you click **Point it to the new address**. ## Changes take effect right away [#changes-take-effect-right-away] QRdx doesn't cache redirects. The next scan opens the new destination, on every phone. ## Don't change the short link itself [#dont-change-the-short-link-itself] Changing the destination is always safe. Changing the code's **back-half** or **domain** is not: codes already printed with the old short link stop working. ## Undo a change [#undo-a-change] 1. Open the code from **Codes**. 2. Under **Settings**, open **Version history**. 3. Click **Restore** next to the version you want, then **Restore version**. QRdx keeps up to 100 versions of each code. ## Static codes can't change [#static-codes-cant-change] A static code stores the link inside the code. To change it, you need to print a new code. See [Static or dynamic](https://qrdx.dev/docs/help/getting-started/static-vs-dynamic). --- # See your scans (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/scans Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/scans.mdx Find how many people scanned a dynamic QR code, when, where and on which device. Every scan of a dynamic code is counted. Static codes aren't tracked. ## One code [#one-code] Go to **Codes** and click the code's name. The top of the page shows: - **Total scans** and **Scans in range** (the dates you picked). - **Unique visitors**: each visitor counts once per day. - **Last scan**. Below that are charts for countries, cities, devices, operating systems, browsers and referrers, plus when people scan and in which language. ## All your codes [#all-your-codes] The **Analytics** page in your dashboard shows totals for every code, your top codes, countries, devices and languages. **Compare codes** puts up to 4 codes side by side. ## Date ranges and history [#date-ranges-and-history] Pick 7, 30 or 90 days, 1 year or 3 years. How far back you can look depends on your plan: - **Free**: 90 days. - **Pro**: 3 years. - **Business**: unlimited. ## Download your scans [#download-your-scans] On a code's page, click **Export CSV**. You get up to 50,000 scans for the selected dates, with the time, place, device, operating system, browser and referrer of each. ## What isn't counted [#what-isnt-counted] Bots and link previews aren't counted. If one network scans the same code more than 20 times in a minute, the extra scans aren't counted. The code still opens every time. ## Privacy [#privacy] QRdx never stores a scanner's IP address. See [What QRdx records when someone scans](https://qrdx.dev/docs/help/privacy-and-security/scan-data). --- # Pause, schedule or limit a code (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/pause-schedule-limit Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/pause-schedule-limit.mdx Turn a dynamic QR code off, set start and end dates, cap its scans, and choose where ended codes send people. All of these are in one place: open the code from **Codes**, then under **Settings**, open **Access & schedule**. ## Pause a code [#pause-a-code] Turn on **Pause code** and save. You can also choose **Pause** from the code's menu in **Codes**, and **Resume** to turn it back on. Pausing works on every plan. ## Set start and end dates [#set-start-and-end-dates] Fill in **Starts**, **Ends** or both. Scans before the start or after the end don't reach your destination. Use this for a campaign with fixed dates. Pro and Business only. ## Cap the scans [#cap-the-scans] Set a **Scan limit**. After that many scans, the code ends. Pro and Business only. ## What people see [#what-people-see] When a code isn't active, scanners see a plain page with no QRdx branding: - Paused: "This campaign is paused". - Before the start date: "This campaign hasn't started yet". - After the end date or the scan limit: "This campaign has ended". ## Send people somewhere useful instead [#send-people-somewhere-useful-instead] Fill in **Fallback URL** to send scans of a paused or ended code to a page you choose, such as your homepage. It doesn't apply before the start date. ## After a downgrade [#after-a-downgrade] Settings you already saved keep working. Removing a setting never needs a paid plan. ## Related [#related] - [Protect a code with a password](https://qrdx.dev/docs/help/dynamic-codes/password) - [Code controls guide](https://qrdx.dev/docs/code-controls) --- # Protect a code with a password (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/password Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/password.mdx Ask for a password before a dynamic QR code opens its destination. Pro and Business plans can put a password on a dynamic code. ## Add a password [#add-a-password] 1. Open the code from **Codes**. 2. Under **Settings**, open **Access & schedule**. 3. Type a **Password** of 4 to 64 characters and save. ## What scanners see [#what-scanners-see] Scanners see "This QR code is password protected", with a **Password** field and a **Continue** button. After the right password, that browser stays unlocked for 6 hours. ## Share the password separately [#share-the-password-separately] Anyone with the password can open the code. Share it the way you'd share any password, not printed next to the code. ## Remove the password [#remove-the-password] Clear the **Password** field and save. Removing it works on any plan. ## Related [#related] - [Pause, schedule or limit a code](https://qrdx.dev/docs/help/dynamic-codes/pause-schedule-limit) --- # Send scans to different links (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/routing Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/routing.mdx Route each scan by device, country, language or time, and run A/B tests with smart routing. Smart routing sends each scan of a dynamic code to a different link, based on rules you set. Pro and Business only. ## Add a rule [#add-a-rule] 1. Open the code from **Codes**. 2. Under **Settings**, open **Smart routing**. 3. Click **Add rule**, or start from a template. 4. Choose a condition and the link that matching scans open. 5. Click **Save rules**. Scans that match no rule open the code's normal destination. ## Conditions you can use [#conditions-you-can-use] Device, operating system, country, region, language, time of day, date range and scan count. Each condition can also be turned around with **is not**. A rule can have up to 8 conditions. A code can have up to 20 rules. ## Templates [#templates] - **Smart app link**: iPhones to the App Store, Android phones to Google Play. - **Country** and **Visitor language**. - **Business hours**. - **Date range** for a campaign. - **First scans**: a different link for the first scans. - **A/B split**: share scans between up to 5 versions. ## The first matching rule wins [#the-first-matching-rule-wins] Rules are checked from top to bottom. Put the most specific rules first. Click **Test your rules** to see where a scan would go before you save. ## A/B tests [#ab-tests] An A/B split sends each visitor to one version. The same visitor sees the same version for the rest of the day. The code's page shows how scans split between versions. ## After a downgrade [#after-a-downgrade] Saved rules keep working. ## Related [#related] - [Smart routing guide](https://qrdx.dev/docs/smart-routing) - [A code opens the wrong page](https://qrdx.dev/docs/help/troubleshooting/wrong-destination) --- # Archive, delete and restore codes (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/archive-and-delete Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/archive-and-delete.mdx What happens to a printed code when you archive it, move it to the trash or delete it forever. Archiving, trashing or deleting a code **stops it from working**. To retire a printed code gently, [pause it](https://qrdx.dev/docs/help/dynamic-codes/pause-schedule-limit) and add a fallback URL instead. ## Archive [#archive] Choose **Archive** from the code's menu in **Codes**. - Scanners see "This QR code is no longer active". - Archived codes don't count toward your plan's code limit. - Choose **Unarchive** to turn the code back on. On Free, you need a free code slot. ## Move to the trash [#move-to-the-trash] Choose **Move to trash** from the code's menu. - Scanners see "This QR code is no longer active". - Open **Trash** in the Codes sidebar and choose **Restore** to bring it back. - QRdx never empties the trash on its own. ## Delete forever [#delete-forever] In **Trash**, choose **Delete forever**. - Scanners see "QR code not found". - The code's scan history is deleted. - This can't be undone. --- # Bring codes from another service (Help) URL: https://qrdx.dev/docs/help/dynamic-codes/import Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/dynamic-codes/import.mdx Import links from a CSV file, or move your codes from Bitly, Dub and other QR code services. ## From a CSV file [#from-a-csv-file] 1. Go to **Codes** and click **Import CSV**. 2. Upload a CSV file with one code per row. The only required column is the destination. You can also include a title, folder, tags and a custom back-half. A file can have up to 1,000 rows and be up to 1 MB. On Free you can import one code at a time. Pro and Business can import many at once. ## From another service [#from-another-service] Click **Import** on the **Codes** page and pick where your codes are now. - **Dub** and **Bitly**: sign in to that service and pick the links. - **Rebrandly** and **Short.io**: paste an API key from that service. - **QR Code Generator PRO, Uniqode, Flowcode, QR TIGER, ME-QR, TinyURL, Hovercode, Scanova** and others: upload the export file from that service. ## Keep printed codes working [#keep-printed-codes-working] Printed codes still point to the old service. The migration guide explains how to keep them working, and how to keep your original back-halves. See [Move from Bitly, Dub, Rebrandly and others](https://qrdx.dev/docs/migrate). --- # Create a mobile page (Help) URL: https://qrdx.dev/docs/help/pages-and-forms/create-a-page Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/pages-and-forms/create-a-page.mdx Build a page for a QR code, like a link list, menu, contact card or coupon. No website needed. QRdx hosts a mobile page for your QR code. You build it from blocks, and you can edit it any time without reprinting the code. ## Create a page [#create-a-page] 1. Go to **Pages** in your dashboard and click **New page**. 2. Pick a type: Link list, Business, vCard Plus, Menu, Coupon, Event, App download, Feedback & forms, PDF, Images, MP3 / Audio, Video or Custom page. 3. Pick a template. You can change every block, color and text later. 4. Name the page if you like, then click **Create page**. QRdx creates a dynamic QR code for the page at the same time. You can also start from the [playground](https://qrdx.dev/playground): click **Change** next to **QR code type** and pick one of the **Hosted pages**. ## Edit and publish [#edit-and-publish] The page editor has three tabs: **Blocks**, **Design** and **Settings**. Nothing changes for visitors until you click **Publish**, or press ⌘/Ctrl + S. The header shows "Unpublished changes" until then. Unpublished edits are kept in your browser, so you can close the tab and come back. ## Get the QR code [#get-the-qr-code] Click **QR code** in the page editor header to open the page's code and download it. **View live** opens the published page. If a page has no code, open its menu (**⋯**) in **Pages** and choose **Create a QR code**. ## Plans [#plans] - **Free**: 3 pages, with a small "Made with QRdx" footer. - **Pro**: unlimited pages, no footer. - **Business**: unlimited white-label pages and custom CSS. ## Related [#related] - [Collect answers with a form](https://qrdx.dev/docs/help/pages-and-forms/forms) - [Landing pages guide](https://qrdx.dev/docs/landing-pages) - [Live page templates](https://qrdx.dev/templates) --- # Collect answers with a form (Help) URL: https://qrdx.dev/docs/help/pages-and-forms/forms Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/pages-and-forms/forms.mdx Create a feedback form, survey or sign-up form, and read the responses in your dashboard. ## Create a form [#create-a-form] 1. Go to **Pages** and click **New page**. 2. Choose **Feedback & forms**. 3. Pick a template: **Guest feedback**, **NPS survey** or **Sign-up form**. 4. Click **Create page**, edit the questions, then click **Publish**. You can also add a **Form** block to any other page. ## Read the responses [#read-the-responses] In **Pages**, open the page's menu (**⋯**) and choose **Responses**. Click **Export CSV** to download the responses as a spreadsheet. ## Get an email for each response [#get-an-email-for-each-response] On Pro and Business, go to **Settings** → **Notifications** and turn on **Form submissions**. ## Monthly limits [#monthly-limits] Each workspace can collect a number of responses each calendar month (UTC): - **Free**: 50. - **Pro**: 1,000. - **Business**: 10,000. When the limit is reached, visitors see "This form isn't accepting responses right now." The form takes responses again next month, or right away after you upgrade. ## Spam [#spam] Forms have built-in spam protection. You don't need to set anything up. ## Related [#related] - [Forms guide](https://qrdx.dev/docs/forms-and-pages#forms) --- # Plans and what they include (Help) URL: https://qrdx.dev/docs/help/account-and-billing/plans Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/plans.mdx What you get on Free, Pro and Business, and where to compare prices. QRdx has three plans: **Free**, **Pro** and **Business**. Compare prices, monthly or yearly, on the [pricing page](https://qrdx.dev/pricing). ## Without an account [#without-an-account] Static QR codes are free and unlimited. Design and download them in the [playground](https://qrdx.dev/playground) with no account and no watermark. ## Free [#free] A free account adds: - 10 dynamic QR codes. - 3 mobile pages, with a small "Made with QRdx" footer. - 90 days of scan history. - 50 form responses a month. - 10 saved themes. ## Pro [#pro] Everything in Free, plus: - Unlimited dynamic codes and pages, with no footer. - 3 years of scan history. - Teams of up to 3 people. - Your own short link endings (custom back-halves). - Smart routing, A/B tests and UTM tags. - Passwords, start and end dates, and scan limits. - Retargeting pixels and email alerts. - 1,000 form responses a month. ## Business [#business] Everything in Pro, plus: - White-label pages with custom CSS. - Unlimited scan history. - Unlimited team members. - Conversion tracking. - Webhooks, Zapier, Slack and Discord. - 10,000 form responses a month. ## On every plan [#on-every-plan] Scans are unlimited. No plan pauses a code because of traffic. ## Team workspaces [#team-workspaces] A team uses the best plan among its owners. One owner on Business gives the whole team Business. Members don't need their own subscription. ## Related [#related] - [Upgrade, downgrade or switch billing](https://qrdx.dev/docs/help/account-and-billing/change-plan) - [Full table of plans and limits](https://qrdx.dev/docs/plans-and-limits) --- # Upgrade, downgrade or switch billing (Help) URL: https://qrdx.dev/docs/help/account-and-billing/change-plan Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/change-plan.mdx Move between Free, Pro and Business, or between monthly and yearly billing. ## Upgrade from Free [#upgrade-from-free] 1. Go to the [pricing page](https://qrdx.dev/pricing). 2. Choose **Monthly** or **Yearly**. 3. Click **Upgrade to Pro** or **Upgrade to Business**. 4. Sign in if you're asked to, then pay at checkout. Your new plan starts once the payment goes through. Payments are handled by [Polar](https://polar.sh). Visitors from India see prices in rupees. ## Change plan when you already pay [#change-plan-when-you-already-pay] Plan changes happen in the billing portal, run by Polar. Open it from any of these: - Your avatar menu → **Manage billing**. - **Settings** → **Usage & billing** → **Manage subscription**. - The [pricing page](https://qrdx.dev/pricing) → **Switch to Pro** or **Switch to Business**. In the portal you can move between Pro and Business, switch between monthly and yearly, and update your card. ## Downgrade to Free [#downgrade-to-free] To go back to Free, [cancel your subscription](https://qrdx.dev/docs/help/account-and-billing/cancel). Your plan stays active until the end of the period you paid for. ## What happens when you downgrade [#what-happens-when-you-downgrade] Your codes keep working. Only new things are limited. See [What happens to your codes if you stop paying](https://qrdx.dev/docs/help/account-and-billing/never-hostage). --- # Find invoices and receipts (Help) URL: https://qrdx.dev/docs/help/account-and-billing/invoices Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/invoices.mdx Download invoices and payment receipts, and add your company details, in the billing portal. Invoices come from [Polar](https://polar.sh), which sells QRdx subscriptions as merchant of record. ## Download an invoice or receipt [#download-an-invoice-or-receipt] 1. Open the billing portal: your avatar menu → **Manage billing**, or **Settings** → **Usage & billing** → **Manage subscription**. 2. Find the order in your purchase history. 3. Download the invoice or the receipt. In the portal you can also edit an invoice to add a company name, a VAT number or a billing address. These links only appear once you have a paid subscription. ## Taxes [#taxes] Polar adds sales tax or VAT where it applies. ## Refunds [#refunds] We look at refund requests case by case. Email [support@qrdx.dev](mailto:support@qrdx.dev) from your account's email address with the date of the charge and the reason. Read the full [refund policy](https://qrdx.dev/legal/refunds). --- # Cancel your subscription (Help) URL: https://qrdx.dev/docs/help/account-and-billing/cancel Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/cancel.mdx Cancel Pro or Business at any time. Your codes keep working afterwards. ## Cancel [#cancel] 1. Open the billing portal: your avatar menu → **Manage billing**, or **Settings** → **Usage & billing** → **Manage subscription**. 2. Cancel the subscription there. ## What happens next [#what-happens-next] - Your plan stays active until the end of the period you paid for. You aren't charged again. - Then your account moves to Free. - Every dynamic code keeps redirecting, and every scan is still recorded. - If you're over a Free limit, you can't create more until you're under it. You can still change where codes point, archive them and export them. See [What happens to your codes if you stop paying](https://qrdx.dev/docs/help/account-and-billing/never-hostage). ## Deleting your account cancels too [#deleting-your-account-cancels-too] If you [delete your account](https://qrdx.dev/docs/help/account-and-billing/delete-your-account), QRdx cancels your subscription straight away. ## Refunds [#refunds] Email [support@qrdx.dev](mailto:support@qrdx.dev). See the [refund policy](https://qrdx.dev/legal/refunds). --- # What happens to your codes if you stop paying (Help) URL: https://qrdx.dev/docs/help/account-and-billing/never-hostage Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/never-hostage.mdx The Never-Hostage Guarantee. Codes keep redirecting after a downgrade, a failed payment or a cancellation. Your printed codes are safe. That's the [Never-Hostage Guarantee](https://qrdx.dev/guarantee), and it applies on every plan, Free included. ## What we promise [#what-we-promise] - **Static codes are free forever.** They hold your content directly and never expire. - **Dynamic codes keep redirecting** after a downgrade, a failed payment or a cancellation. - **No pausing and no ads.** We never pause your codes over billing, and never send your scanners through a QRdx page. - **Limits only stop new things.** Existing codes and pages keep working, and you can still change where codes point. - **Export everything.** Download your codes, scans and responses on every plan. - **12 months' notice** if QRdx ever shuts down, with a path to run the open-source app yourself. ## What keeps working after a downgrade [#what-keeps-working-after-a-downgrade] - Every dynamic code and every page. - Routing rules, passwords, schedules and pixels you already saved. - Custom domains you already verified. ## What changes [#what-changes] - You can't create new codes or pages beyond the new plan's limits. - Scan history shows only what your new plan includes. Nothing is deleted. - Free pages show the "Made with QRdx" footer again. - Forms stop taking responses once the monthly limit is reached. - Uploads over your storage limit are blocked. ## What does stop a code [#what-does-stop-a-code] Only things you choose: pausing, an end date, a scan limit, archiving or deleting it. QRdx can also turn off a code that's used for abuse. --- # Sign-in methods and email address (Help) URL: https://qrdx.dev/docs/help/account-and-billing/sign-in Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/sign-in.mdx Sign in with Google, GitHub or an email code, link sign-in methods and change your email address. ## Ways to sign in [#ways-to-sign-in] - **Google** or **GitHub**. - **Email code**: enter your email, then type the 6-digit code we send. A code works for 5 minutes. The first time you sign in, QRdx creates your account. There's no password to remember. ## Link Google or GitHub [#link-google-or-github] Go to **Settings** → **Account** → **Sign-in methods**. - **Email code** is always on. - Link Google or GitHub to sign in with one click. The account must use the same email address as your QRdx account. - Click **Unlink** to remove one. ## Change your email address [#change-your-email-address] 1. Go to **Settings** → **Account**. 2. Next to **Email**, click **Change**. 3. Enter the new address and type the code we send to it. For your safety, you need to have signed in within the last 24 hours. ## Sign out of other devices [#sign-out-of-other-devices] Under **Sessions** in **Settings** → **Account**, sign out of one device, or click **Sign out everywhere else**. ## Trouble signing in [#trouble-signing-in] See [Sign-in codes or emails don't arrive](https://qrdx.dev/docs/help/troubleshooting/emails). --- # Export your data (Help) URL: https://qrdx.dev/docs/help/account-and-billing/export-your-data Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/export-your-data.mdx Download everything in your account, or export scans and form responses as CSV. ## Everything at once [#everything-at-once] 1. Go to **Settings** → **Account**. 2. Under **Your data**, click **Download my data**. You get a ZIP file with your codes, scans, pages, form responses, themes and account details, as JSON and CSV files, plus a README that explains them. You can download it up to 3 times an hour. ## Scans of one code [#scans-of-one-code] Open the code from **Codes** and click **Export CSV**. You get up to 50,000 scans for the dates you picked. ## Form responses [#form-responses] In **Pages**, open the page's menu (**⋯**), choose **Responses**, then click **Export CSV**. ## QR code images [#qr-code-images] In **Codes**, select the codes you want, then click **Download**. You get a ZIP of PNG or SVG files, with a CSV list of the codes. --- # Delete your account (Help) URL: https://qrdx.dev/docs/help/account-and-billing/delete-your-account Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/account-and-billing/delete-your-account.mdx Permanently delete your QRdx account and what's in it. Your dynamic codes stop working. Deleting your account **can't be undone**. Your personal dynamic codes stop working: scanners see "QR code not found". [Export your data](https://qrdx.dev/docs/help/account-and-billing/export-your-data) first. ## Delete [#delete] 1. Go to **Settings** → **Account**. 2. Under **Danger zone**, click **Delete account…**. 3. Type `delete` to confirm. 4. Click **Email me a code** and type the 6-digit code we send you. 5. Click **Delete account**. ## What gets deleted [#what-gets-deleted] Your personal workspace: its codes, scans, pages, files and domains, plus your themes, webhooks and integrations. You're signed out on every device. ## Your subscription [#your-subscription] If you pay for Pro or Business, QRdx cancels the subscription straight away. ## Your teams [#your-teams] - In a team where you're the only member, the team is deleted. - In a team with other members, another member becomes owner and keeps the team's codes and pages. ## Just want to stop paying? [#just-want-to-stop-paying] You don't need to delete your account. [Cancel your subscription](https://qrdx.dev/docs/help/account-and-billing/cancel) and your codes keep working on Free. --- # Create a team and invite people (Help) URL: https://qrdx.dev/docs/help/teams/create-and-invite Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/teams/create-and-invite.mdx Share codes, pages and themes with your team. Teams need Pro or Business. A team is a shared workspace. Everyone in it works on the same codes, pages, files, domains and themes. ## Create a team [#create-a-team] 1. Open the workspace switcher at the top of the dashboard sidebar. It shows **Personal** when you're in your own workspace. 2. Click **Create team**. 3. Fill in a **Team name** and a **Team URL** (3 to 48 lowercase letters, numbers and dashes). 4. Create the team. QRdx switches you into it. Creating a team needs Pro or Business. ## Invite someone [#invite-someone] 1. Go to **Team** in the dashboard sidebar. 2. Enter their email address and choose a role: **Member** or **Admin**. See [Team roles](https://qrdx.dev/docs/help/teams/roles). 3. Click **Create invite link**. QRdx emails the invitation and shows a link you can share yourself. The confirmation tells you whether the email was sent. ## Accepting an invitation [#accepting-an-invitation] - The invited person opens the link and signs in with the **same email address** the invite was sent to. - They click **Accept invitation**, or **Decline**. - Invite links expire after 48 hours. ## Switch between workspaces [#switch-between-workspaces] Use the workspace switcher to move between **Personal** and your teams. Codes you create go into the workspace you're in. ## Related [#related] - [Seats and team limits](https://qrdx.dev/docs/help/teams/seats) - [Teams guide](https://qrdx.dev/docs/teams) --- # Team roles (Help) URL: https://qrdx.dev/docs/help/teams/roles Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/teams/roles.mdx What owners, admins and members can do in a team. Every team member has one role. ## Member [#member] - Create and edit the team's codes and pages, and upload files. - Delete their own codes, files and form responses. - Use the team's themes and verified domains. ## Admin [#admin] Everything a member can do, plus: - Delete any code, file or form response. - Invite and remove members, and change their roles. - Add themes to the team. - Add, verify and remove custom domains. - Manage webhooks. ## Owner [#owner] Everything an admin can do, plus: - Make other members owners. - Delete the team. ## Change someone's role [#change-someones-role] On the **Team** page, change the role next to their name. Only admins and owners can change roles. ## Hand over a team [#hand-over-a-team] There's no separate transfer button. Make the new person an **Owner**. Once there's another owner, you can leave the team. --- # Seats and team limits (Help) URL: https://qrdx.dev/docs/help/teams/seats Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/teams/seats.mdx How many people a team can have, and how to leave, remove someone or delete a team. ## How many seats you get [#how-many-seats-you-get] - **Pro**: 3 seats, including the owner. - **Business**: unlimited seats. - **Free**: no teams. A team uses the best plan among its owners. Members don't need their own subscription. ## Pending invitations use a seat [#pending-invitations-use-a-seat] An invitation takes a seat until it's accepted or revoked. To free the seat, find the invitation on the **Team** page and click **Revoke**. ## If the owners' plan changes [#if-the-owners-plan-changes] People already in the team stay. Only new invitations are blocked until there's a free seat. ## Remove someone [#remove-someone] On the **Team** page, click **Remove** next to their name, then **Remove member**. Only admins and owners can remove members. The codes and pages they created stay with the team. ## Leave a team [#leave-a-team] On the **Team** page, click **Leave team**. If you're the only owner, make someone else an owner first. See [Team roles](https://qrdx.dev/docs/help/teams/roles). ## Delete a team [#delete-a-team] Owners can click **Delete team** on the **Team** page. To confirm, type the team's URL. Nothing stops working. Each code, page, file and domain moves to the personal workspace of the person who created it, and printed codes keep redirecting. --- # A code won't scan (Help) URL: https://qrdx.dev/docs/help/troubleshooting/wont-scan Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/troubleshooting/wont-scan.mdx Why a QR code isn't scanning, and how to fix each cause. If a phone camera doesn't react to your code, or reads it only sometimes, work through these checks. Start with the **Scan score** under the preview in the [playground](https://qrdx.dev/playground). Click it to see which checks fail. See [Check a design with the scan score](https://qrdx.dev/docs/help/designing/scan-score). For a code that's already printed, take a photo and drop it into the [scan checker](https://qrdx.dev/tools/scan-checker). It shows what the code says and tests how well it reads. ## Common causes [#common-causes] ### Low contrast or light on dark [#low-contrast-or-light-on-dark] Scanners look for dark squares on a light background. Make the code darker or the background lighter, and avoid light codes on dark backgrounds. See [Choose colors that scan](https://qrdx.dev/docs/help/designing/colors-and-contrast). ### A logo covers too much [#a-logo-covers-too-much] Set **Error correction** to **High** in the **Design** tab. See [Add a logo](https://qrdx.dev/docs/help/designing/logos). ### Unusual eye shapes [#unusual-eye-shapes] Circle and curly corner eyes fail in some scanner apps. Square, rounded and gear eyes read everywhere. ### No clear border [#no-clear-border] Scanners need empty space around the code. Set **Padding** to **Standard** or **Large**, and don't let other artwork touch the code. ### Printed too small [#printed-too-small] Make the code about one tenth of the scanning distance: 2 cm for a card read at 20 cm, 10 cm for a poster read from 1 m. The [print size calculator](https://qrdx.dev/tools/print-size) works it out for you. ### Too much content [#too-much-content] A static code that holds a lot of text becomes dense and hard to read when small. Use a dynamic code instead: it holds only a short link. See [Static or dynamic](https://qrdx.dev/docs/help/getting-started/static-vs-dynamic). ### The print itself [#the-print-itself] Blurry printing, glossy surfaces that reflect light, and curved or folded surfaces all make codes harder to read. Print as a vector file (SVG, PDF or EPS) and test one copy before printing many. ## It scans, but shows an error [#it-scans-but-shows-an-error] If the camera reads the code but you see a page like "QR code not found", see [A scan shows an error page](https://qrdx.dev/docs/help/troubleshooting/error-pages). --- # A code opens the wrong page (Help) URL: https://qrdx.dev/docs/help/troubleshooting/wrong-destination Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/troubleshooting/wrong-destination.mdx Why a QR code opens an old or unexpected link, and how to find out what it really holds. ## First, see what the code holds [#first-see-what-the-code-holds] Take a photo of the printed code and drop it into the [scan checker](https://qrdx.dev/tools/scan-checker). It shows exactly what the code contains. - If it shows your website address, the code is **static**. - If it shows a short link like `qrdx.dev/q/…`, the code is **dynamic**. ## A static code [#a-static-code] A static code stores the link inside the code. It can't be changed after printing. This happens when a code is downloaded before it was made dynamic. To fix it, print the dynamic version. See [Create a dynamic QR code](https://qrdx.dev/docs/help/dynamic-codes/create). ## A dynamic code [#a-dynamic-code] Open the code in **Codes** and check each of these. ### The new destination wasn't saved [#the-new-destination-wasnt-saved] In the playground, changing the address isn't enough: click **Point it to the new address**. In the dashboard, use **Edit link** and **Save changes**. See [Change where a code points](https://qrdx.dev/docs/help/dynamic-codes/change-the-link). ### A smart routing rule matched [#a-smart-routing-rule-matched] Rules can send some scans elsewhere, for example by device or country. The first matching rule wins. Open **Smart routing** and click **Test your rules**. ### An A/B test is running [#an-ab-test-is-running] Each visitor sees one version for the day. Another person may see a different page. ### The code is paused or ended [#the-code-is-paused-or-ended] A paused or ended code opens its **Fallback URL**, if you set one. Check **Access & schedule**. ### UTM parameters were added [#utm-parameters-were-added] If you set UTM parameters, scans open your link with `utm_` tags added. That's expected, and the page is the same. ## Still seeing the old page? [#still-seeing-the-old-page] QRdx doesn't cache redirects, so the next scan uses the new destination. If your phone shows an old version of the page itself, reload it in the browser. --- # A scan shows an error page (Help) URL: https://qrdx.dev/docs/help/troubleshooting/error-pages Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/troubleshooting/error-pages.mdx What each QR code error page means, from "QR code not found" to "This campaign has ended", and how to fix it. When a dynamic code can't open its destination, scanners see a short page. Here's what each one means. ## "QR code not found" [#qr-code-not-found] This is a 404 page: the short link doesn't belong to any code. - The code was **deleted forever**. - The code's **back-half** or **domain** was changed after it was printed. - The account that owned it was deleted. - The link was typed by hand with a mistake. Short links are case-sensitive. A deleted code can't come back. If you changed the back-half, change it back with **Edit link**. ## "This QR code is no longer active" [#this-qr-code-is-no-longer-active] The code is **archived** or in the **trash**. Choose **Unarchive** or **Restore** in **Codes**. See [Archive, delete and restore codes](https://qrdx.dev/docs/help/dynamic-codes/archive-and-delete). QRdx also turns off codes that link to harmful sites. ## "This campaign is paused", "hasn't started yet" or "has ended" [#this-campaign-is-paused-hasnt-started-yet-or-has-ended] These come from the code's own settings: a pause, a start or end date, or a scan limit. Change them in **Access & schedule**. See [Pause, schedule or limit a code](https://qrdx.dev/docs/help/dynamic-codes/pause-schedule-limit). ## "This QR code is password protected" [#this-qr-code-is-password-protected] The code has a password. Enter it and click **Continue**. See [Protect a code with a password](https://qrdx.dev/docs/help/dynamic-codes/password). ## "Too many requests" [#too-many-requests] One network opened a lot of QR codes in a few seconds. Wait a moment and scan again. ## "This domain isn't connected yet" [#this-domain-isnt-connected-yet] The code uses a custom domain whose DNS setup isn't finished. Check the domain on the **Domains** page in your dashboard. ## "This page could not be found" on a page [#this-page-could-not-be-found-on-a-page] A mobile page at `/p/…` shows this when the page was deleted or the address was mistyped. ## "This form isn't accepting responses right now" [#this-form-isnt-accepting-responses-right-now] The workspace reached its monthly form response limit. See [Collect answers with a form](https://qrdx.dev/docs/help/pages-and-forms/forms#monthly-limits). --- # Sign-in codes or emails don't arrive (Help) URL: https://qrdx.dev/docs/help/troubleshooting/emails Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/troubleshooting/emails.mdx What to do when the sign-in code, a team invitation or an alert email doesn't show up. ## The sign-in code [#the-sign-in-code] The email's subject is "Your QRdx sign-in code". The code has 6 digits and works for 5 minutes. If it doesn't arrive: 1. **Check your spam, junk or promotions folder.** Search your inbox for "QRdx sign-in code". 2. **Check the address.** Click **Use a different email** if you made a typo. 3. **Wait 30 seconds, then click Resend.** Each new code replaces the old one, so use the newest email. 4. **Use Google or GitHub** instead, if your QRdx account uses the same email address. If you see "Too many codes requested", wait a minute. An address can request 5 codes every 15 minutes and 30 a day. ### Work email [#work-email] Some company mail filters hold emails from new senders. Ask your IT team to let QRdx emails through, or sign in with another method. ## A team invitation [#a-team-invitation] Ask the person who invited you to copy the invite link from the **Team** page and send it to you directly. Links expire after 48 hours. See [Create a team and invite people](https://qrdx.dev/docs/help/teams/create-and-invite). ## Alerts and the weekly digest [#alerts-and-the-weekly-digest] - Email alerts and the digest are on Pro and Business. - They're all off until you turn them on in **Settings** → **Notifications**. Turn them off there to stop them. - The weekly digest goes out on Mondays at 09:00 UTC. It's skipped when there were no scans in the last two weeks and no form responses. ## Still nothing [#still-nothing] [Contact support](https://qrdx.dev/docs/help/contact) and tell us the email address you're using. --- # What QRdx records when someone scans (Help) URL: https://qrdx.dev/docs/help/privacy-and-security/scan-data Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/privacy-and-security/scan-data.mdx The data a scan of a dynamic code records, what it never stores, and how long it's kept. Only dynamic codes record scans. A static code holds your content directly and never passes through QRdx. ## What a scan records [#what-a-scan-records] - Country, region and city, worked out from the network. - Device type, operating system and browser. - The referring site, if there is one. - The browser's language. - Which version the visitor saw, if an A/B test is running. - A daily, salted hash of the network address and browser, so a visitor counts once per day. ## What it never stores [#what-it-never-stores] QRdx never stores the scanner's IP address. The hash above can't be turned back into it, and it changes every day. Bots and link previews aren't recorded. ## How long scans are kept [#how-long-scans-are-kept] Scans are kept as long as the code exists. Your plan decides how far back you can see them: 90 days on Free, 3 years on Pro and unlimited on Business. Deleting a code forever deletes its scans. ## Cookies for people who scan [#cookies-for-people-who-scan] Pages your scanners see, like short links and mobile pages, don't show a cookie banner and don't load QRdx's analytics or support widget. The only QRdx cookie there remembers a correct code password for 6 hours. Page owners can add their own analytics pixels to their pages on paid plans. ## The QR code scanner [#the-qr-code-scanner] The [QRdx scanner](https://qrdx.dev/scan) keeps your scan history on your device only. ## More detail [#more-detail] - [Privacy policy](https://qrdx.dev/legal/privacy-policy) - [Cookie policy](https://qrdx.dev/legal/cookie-policy) - [Subprocessors](https://qrdx.dev/legal/subprocessors) - [Data processing agreement](https://qrdx.dev/legal/dpa) --- # Keep your account secure (Help) URL: https://qrdx.dev/docs/help/privacy-and-security/account-security Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/privacy-and-security/account-security.mdx How sign-in works, how to sign out other devices, and how to report a security problem. ## No password to leak [#no-password-to-leak] QRdx has no passwords. You sign in with Google, GitHub or a one-time code sent to your email. That makes your email account, and your Google or GitHub account, the keys to QRdx. Turn on two-step verification for them. QRdx doesn't have its own two-step verification (2FA) or passkeys. ## Sign out other devices [#sign-out-other-devices] Go to **Settings** → **Account**. Under **Sessions**, sign out of one device, or click **Sign out everywhere else**. ## Remove apps you connected [#remove-apps-you-connected] If you let another app use your QRdx account, it's listed under **Settings** → **Authorized apps**. Remove any you don't use. ## Sensitive actions need a code [#sensitive-actions-need-a-code] Deleting your account asks for a code sent to your email. Changing your email address needs a recent sign-in and a code sent to the new address. ## Report a security problem [#report-a-security-problem] Email [support@qrdx.dev](mailto:support@qrdx.dev) with a subject starting `[security]`. Please don't post it in public GitHub issues. See the [security policy](https://qrdx.dev/legal/security). A harmful QR code isn't a security bug: [report it](https://qrdx.dev/docs/help/privacy-and-security/report-abuse) instead. --- # Report a harmful QR code (Help) URL: https://qrdx.dev/docs/help/privacy-and-security/report-abuse Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/privacy-and-security/report-abuse.mdx Report a QRdx code, short link or page that leads to phishing, malware, scams or other harmful content. ## Report it [#report-it] 1. Open the [report form](https://qrdx.dev/report-abuse). You don't need an account. 2. Paste the code's short link, the page link or the 7-character short code. 3. Choose a reason, add any details, and send. Leave your email address if you'd like to hear what we did. It's optional. Every page a dynamic QRdx code opens, including password and "campaign ended" pages, links to the form. ## What happens next [#what-happens-next] A person reviews every report within 24 to 48 hours. Codes that break our rules are switched off for everyone, and their owner can't switch them back on. ## Stay safe [#stay-safe] Don't enter passwords, card numbers or one-time codes on a page you reached from a QR code you don't trust. ## Static codes [#static-codes] A static code holds its content directly and never passes through QRdx, so we can't switch it off. Report the harmful website to its host or to [Google Safe Browsing](https://safebrowsing.google.com/safebrowsing/report_phish/). ## If your code was switched off [#if-your-code-was-switched-off] If you think we got it wrong, email [support@qrdx.dev](mailto:support@qrdx.dev) with "[appeal]" in the subject and the code's short link. Read the full [abuse policy](https://qrdx.dev/legal/report-abuse). --- # Contact support (Help) URL: https://qrdx.dev/docs/help/contact Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/contact.mdx Email support, use the chat widget, or open a GitHub issue. ## Email [#email] Write to [support@qrdx.dev](mailto:support@qrdx.dev). To get an answer faster, include: - the email address of your QRdx account; - the short link or page link, if it's about a code or a page; - what you expected and what happened, with a screenshot if you can. ## The support widget [#the-support-widget] On qrdx.dev, the support and feedback widget sits in a corner of the page. It appears after you allow **Functionality** cookies. Choose **Cookie settings** in the site footer to change your choice. ## Bugs and feature ideas [#bugs-and-feature-ideas] QRdx is open source. Report a bug or suggest a feature in [GitHub issues](https://github.com/bucharitesh/qrdx/issues), or start a conversation in [GitHub Discussions](https://github.com/bucharitesh/qrdx/discussions). ## Special requests [#special-requests] - **Security problems:** email support@qrdx.dev with a subject starting `[security]`. See the [security policy](https://qrdx.dev/legal/security). - **A harmful QR code:** use the [report form](https://qrdx.dev/report-abuse). - **Refunds:** email support@qrdx.dev with the date of the charge. See the [refund policy](https://qrdx.dev/legal/refunds). --- # What's new and what's next (Help) URL: https://qrdx.dev/docs/help/whats-new Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/help/whats-new.mdx Where to find recent changes, how we plan new work, and what QRdx doesn't do today. ## What's new [#whats-new] Every release is in the [changelog](https://qrdx.dev/changelogs), newest first. The biggest recent release, [0.1.0](https://qrdx.dev/changelogs) on 31 October 2026, made QRdx a platform: dynamic QR codes, mobile pages and forms, smart routing, teams, notifications, webhooks and a REST API. ## What's next [#whats-next] We don't publish a roadmap with dates, and we don't promise features before they're built. When something ships, it goes in the changelog. What people ask for shapes what we build. To ask for something: - use the support widget on qrdx.dev, or email [support@qrdx.dev](mailto:support@qrdx.dev); - or open an idea in [GitHub Discussions](https://github.com/bucharitesh/qrdx/discussions). ## What QRdx doesn't do today [#what-qrdx-doesnt-do-today] Some things people ask about that QRdx doesn't have right now: - **Two-step verification (2FA) or passkeys.** Sign-in uses Google, GitHub or email codes. See [Keep your account secure](https://qrdx.dev/docs/help/privacy-and-security/account-security). - **Dynamic codes for content other than links**, straight from the playground. Use a [mobile page](https://qrdx.dev/docs/help/pages-and-forms/create-a-page) for a contact card, menu, PDF or other content you want to change later. - **App store listings for every plugin.** The [plugins page](https://qrdx.dev/plugins) says how to install each one today. ## Follow along [#follow-along] The source code and every change are public on [GitHub](https://github.com/bucharitesh/qrdx). --- # Dynamic QR codes URL: https://qrdx.dev/docs/dynamic-qr-codes Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/dynamic-qr-codes.mdx 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 [#create-a-dynamic-code] - **Dashboard:** open [Dashboard](https://qrdx.dev/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. ## Custom back-halves [#custom-back-halves] Every code gets a random 7-character back-half, the part after `/q/`. On Pro and Business you can choose your own instead, such as `https://qrdx.dev/q/spring-menu`: fill in **Custom back-half** when you create or edit a code, and the dialog tells you as you type whether it's free. - 3 to 64 characters: letters, digits, `-` and `_`, not starting or ending with `-`. - Back-halves are case-sensitive, like generated ones: `Spring-Menu` and `spring-menu` are two different links. - On `qrdx.dev` a back-half is unique across all of QRdx, and words that read as QRdx pages (`admin`, `api`, `docs`, `login`, `pricing`, `settings`, ...) are reserved. - On your own [custom domain](https://qrdx.dev/docs/custom-domains) it only has to be unique on that domain, and only `api`, `p` and `q` are reserved, so `go.yourbrand.com/pricing` works. Such a back-half belongs to the domain: it has no `qrdx.dev/q/` link. - Changing the back-half of a code changes its link: QR codes printed with the old one stop working, so download the code again. ## Manage codes [#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; the short link then shows a "QR code not found" page. Deleting through the API (`DELETE /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 [#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 back-halves (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 custom back-half in the `slug` column). Importing more than one row, and custom back-halves, need 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 [#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](https://qrdx.dev/docs/plans-and-limits) for everything else each plan includes. ## Scan analytics [#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 [#rest-api] Manage codes programmatically under `https://api.qrdx.dev/v1/codes`. Requests are authenticated with an OAuth access token (`Authorization: Bearer `) that has the `codes:read` or `codes:write` scope; see [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api) for how to get one. 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` | `/v1/codes` | List codes. Query: `status` (`active`, `archived`, `all`), `q`, `limit` (≤ 100), `offset` | | `POST` | `/v1/codes` | Create one code, or an array of up to 100 (arrays need Pro). Body: `destinationUrl`, optional `title`, `style`, `domainId` and `slug` | | `GET` | `/v1/codes/{id}` | Get one code | | `PATCH` | `/v1/codes/{id}` | Update `destinationUrl`, `title`, `style`, `domainId` ([custom domains](https://qrdx.dev/docs/custom-domains)), `slug` or `archived` | | `DELETE` | `/v1/codes/{id}` | Delete a code and its scans | | `GET` | `/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: ```bash curl https://api.qrdx.dev/v1/codes \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://example.com/menu", "title": "Spring menu"}' ``` ```json { "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 } } ``` Pass `slug` to choose the back-half (Pro and Business; see [custom back-halves](#custom-back-halves) for the rules). With a `domainId` it's checked on that domain, otherwise on `qrdx.dev`: ```bash curl https://api.qrdx.dev/v1/codes \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://example.com/menu", "slug": "spring-menu"}' ``` The response then has `"slug": "spring-menu"` and `"shortLink": "https://qrdx.dev/q/spring-menu"`. A back-half chosen on a custom domain has `"defaultShortLink": null`, because it only exists on that domain. In an array, every `slug` must be different. Change the destination: ```bash curl -X PATCH https://api.qrdx.dev/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](https://qrdx.dev/docs/getting-started). Errors use one JSON shape, `{ "code": "...", "message": "...", "status": 400 }`, plus a `docs` link when a page explains the fix: | Status | Code | When | | --- | --- | --- | | 400 | `VALIDATION_ERROR`, `INVALID_SLUG` | Invalid body or query (details in `data`), or a `slug` that breaks the rules or is reserved | | 401 | `UNAUTHORIZED`, `INVALID_TOKEN` | No `Authorization: Bearer` token, or an invalid, expired or revoked one ([get a token](https://qrdx.dev/docs/developers/rest-api#get-an-access-token)) | | 402 | `LIMIT_REACHED`, `PLAN_REQUIRED` | Over your plan's active code limit, or bulk creation or a custom `slug` 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, or there's no such endpoint | | 409 | `SLUG_TAKEN` | Another code already uses that `slug` (on `qrdx.dev`, or on the custom domain) | | 429 | `RATE_LIMITED` | Too many requests; retry after the `Retry-After` header | ## Self-hosting [#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. --- # Code controls URL: https://qrdx.dev/docs/code-controls Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/code-controls.mdx Pause a dynamic QR code, protect it with a password, schedule it, cap its scans and choose where ended codes send people. Every [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes) has an **Access & schedule** panel on its page in the [dashboard](https://qrdx.dev/dashboard) (open a code, then scroll to **Settings**). Changes apply to the next scan; you never need to reprint the code. | Control | What scanners see | Plan | | --- | --- | --- | | Pause | Your fallback URL, or a neutral "this campaign is paused" page | Every plan | | Password | A password screen; the right password opens the code | Pro and Business | | Starts | Before the start: a "hasn't started yet" page | Pro and Business | | Ends | After the end: your fallback URL, or a neutral "campaign has ended" page | Pro and Business | | Scan limit | Once the limit is reached: your fallback URL, or the "ended" page | Pro and Business | | Fallback URL | Where paused, ended and over-limit codes send people | Pro and Business | ## Pause [#pause] Turn on **Pause code** to stop a code temporarily, for example between two events, and turn it off to resume. Scans while it is paused go to your fallback URL when you set one, otherwise to a plain page that says the campaign is paused. Pausing is free on every plan. ## Password [#password] Set a password (at least 4 characters) and anyone who scans the code first sees a password screen. After the right password, that browser stays unlocked for 6 hours, on the short link and on the hosted page it opens. Changing or removing the password locks out every browser that unlocked the old one. Passwords are stored as bcrypt hashes; QRdx can't show you a password later, only replace it. Repeated wrong guesses from one visitor are slowed down. ## Start and end dates [#start-and-end-dates] - **Starts:** until then, scans show "This campaign hasn't started yet. Please scan this code again later." - **Ends:** from then on, scans go to your fallback URL, or show "This campaign has ended". Dates are entered in your own time zone and stored as exact moments, so a code ends at the same instant everywhere. The end must be after the start. ## Scan limit [#scan-limit] The limit counts scans the same way your analytics do: bots, link previews and rapid repeat scans from one device aren't counted. Once the code has reached the limit, the next scans go to your fallback URL or the "ended" page. Raise or clear the limit to reopen the code. ## Fallback URL [#fallback-url] The address paused, expired and over-limit codes send people to, such as your homepage or the next campaign. Without one, scanners see a small neutral page. That page never shows ads, a QRdx promotion or a sign-up prompt, only the campaign's status and a "Report abuse" link. The fallback URL goes through the same safety checks as a code's destination. ## Plans and downgrades [#plans-and-downgrades] - Pausing works on every plan. - Setting or changing a password, dates, a scan limit or a fallback URL needs Pro or Business. - **A downgrade never ends or locks a code.** Controls you set while on Pro keep working exactly as configured, and you can remove any of them on any plan. ## Safety checks on links [#safety-checks-on-links] QRdx checks every destination and fallback URL when you save it: - links listed by [Google Web Risk](https://cloud.google.com/web-risk) as malware, phishing or unwanted software are rejected (when the server has a Web Risk key); - `javascript:`, `data:` and other non-web links are rejected; - during an account's first 7 days, codes that point to URL shorteners, bare IP addresses or lookalike (homoglyph) domains are saved switched off and **held for review**. The panel says so; nothing else is needed from you, and a reviewer turns the code on once it checks out. New accounts can also create up to 30 dynamic codes per hour. Anyone can report a code at [qrdx.dev/report-abuse](https://qrdx.dev/report-abuse). Codes switched off after a review stop working for everyone and can't be turned back on by their owner. ## Self-hosting [#self-hosting] - `WEB_RISK_API_KEY`: a Google Cloud API key with the Web Risk API enabled. Without it, links are only checked by the offline rules above. - `NEXT_PUBLIC_TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY`: Cloudflare Turnstile on the public report form (optional). - Unlock cookies are signed with `BETTER_AUTH_SECRET`. --- # Smart routing URL: https://qrdx.dev/docs/smart-routing Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/smart-routing.mdx Send each scan of a dynamic QR code to a different URL by device, country, language, time or scan count, run A/B tests and add UTM parameters. A [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes) normally sends every scan to one destination. With smart routing, one printed code can open the App Store on iPhones and Google Play on Android, a Spanish page for Spanish-speaking visitors, or a different offer outside business hours. You set it up in the **Smart routing** panel on the code's page in the [dashboard](https://qrdx.dev/dashboard) (open a code, then scroll to **Settings**). Smart routing and UTM parameters are part of Pro and Business. They apply to codes that open a website link; [hosted pages](https://qrdx.dev/docs/landing-pages) don't redirect, so they have no routing. ## How rules work [#how-rules-work] - A code has up to 20 **rules**. Each rule has conditions ("when") and an action ("then"). - Rules are checked **top to bottom** for every scan. The **first rule whose conditions all match** decides where the scan goes. Use the arrows to reorder rules. - When no rule matches, the scan goes to the code's main destination. - A rule without conditions matches every scan, so rules below it never run. - Turn a rule **off** to keep it without using it. - Changes apply to the next scan; you never need to reprint the code. If a rule can't be evaluated for some reason, the scan still goes to the main destination: routing never stops a code from working. ## Conditions [#conditions] Each condition can be inverted with **is not** (for example "country is not US"). | Condition | Matches when | Notes | | --- | --- | --- | | Device | The scanner uses a phone, tablet, desktop or other device | From the browser's user agent | | Operating system | iOS, Android, Windows, macOS, Linux, ChromeOS, HarmonyOS, … | iPhone and iPad both count as iOS | | Country | The scan comes from one of the listed countries | 2-letter codes like `US, CA, GB` | | Region | The scan comes from one of the listed regions | Codes like `US-CA, US-NY` (country + subdivision) | | Language | The visitor's preferred browser language is one of the listed ones | `es` matches every Spanish variant (`es-MX`, `es-ES`); `pt-BR` only Brazilian Portuguese | | Time of day | The scan happens on the chosen weekdays between two times in a time zone you pick | A window like 22:00–02:00 runs past midnight and belongs to the day it starts | | Date range | The scan happens between two dates | Leave one side empty for "until" or "from"; dates are in your browser's time zone | | Scan count | It's one of the first N scans, or any scan after N | Counts recorded scans (bots aren't counted), so it is approximate when many people scan at once | Country and region come from the scanner's IP address, which QRdx never stores. When a location is unknown, location conditions don't match (and "is not" conditions do). ## Smart app links [#smart-app-links] Choose **Add rule → Smart app link** to create two rules at once: iOS goes to your App Store link, Android to your Google Play link. Everyone else (desktop, other phones) goes to the main destination, for example your website. ## A/B tests [#ab-tests] Set a rule's action to **Split between URLs (A/B)** and add 2 to 5 variants, each with a URL and a weight. A split of 70 and 30 sends about 70% of visitors to the first URL. - Assignment is **sticky**: a visitor who scans again the same day gets the same variant. It is based on an anonymous daily visitor hash, never a cookie. - The code's analytics show **scans per variant** since the split's variants or weights last changed. Each scan stores the variant it was served. Scans recorded before QRdx stored variants are replayed with your current rules and the card is marked as an estimate. - A split can have conditions too, for example "A/B test for visitors in Germany only". ## Test your rules [#test-your-rules] **Test your rules** at the bottom of the panel shows where a scan would go, for any device, operating system, country, region, language, date and time, scan number and visitor, using your unsaved changes. It lists which rule matched and why the others didn't, and shows the final URL including UTM parameters. ## UTM parameters [#utm-parameters] The **UTM parameters** panel adds `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` and up to 10 parameters of your own to the URL each scan opens, so analytics tools can tell QR traffic apart. - Parameters are added after routing, to whichever URL the scan goes to. - A parameter the URL already has is never replaced, and the rest of the URL is kept exactly as you entered it. - Names starting with `qrdx_` are reserved for [conversion tracking](https://qrdx.dev/docs/tracking-and-conversions). ## Plans and downgrades [#plans-and-downgrades] Rules and UTM parameters saved on Pro keep working if you downgrade: limits only block creating new things. Without Pro you can still turn rules off and delete them or remove UTM parameters; changing them needs Pro again. --- # Tracking and conversions URL: https://qrdx.dev/docs/tracking-and-conversions Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/tracking-and-conversions.mdx Add your analytics and retargeting pixels to hosted pages, see when and how people scan, and attribute sales and sign-ups to QR code scans. Every [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes) records scans without cookies. On top of that you can load your own pixels on hosted pages (Pro) and attribute conversions such as purchases to the scan that brought the visitor (Business). Both are set up on the code's page in the [dashboard](https://qrdx.dev/dashboard), under **Settings**. ## Analytics on every code [#analytics-on-every-code] Besides scans over time, countries, cities, devices, operating systems, browsers and referrers, each code's page shows: - **When people scan:** scans by hour of day and by weekday, in UTC or in your own time zone. - **Languages:** each visitor's preferred browser language ("Unknown" when the browser doesn't send one, and for scans from before languages were recorded). - **A/B splits:** scans per variant for each [A/B split rule](https://qrdx.dev/docs/smart-routing#ab-tests). - **Page activity** (hosted pages): page views, unique viewers, taps and the most tapped links and buttons. - **Conversions**, when the code has any. [Dashboard → Analytics](https://qrdx.dev/dashboard/analytics) adds up every code in the workspace: totals, scans over time, top codes, countries, devices, languages, scan times, and a comparison of up to 4 codes side by side. Bots and link previews aren't counted, and a visitor counts once per day for "unique". IP addresses are never stored. ## Tracking pixels on hosted pages (Pro) [#tracking-pixels-on-hosted-pages-pro] For codes that open a hosted page, the **Tracking pixels** panel loads your own tags on that page: | Provider | ID format | | --- | --- | | Google Analytics 4 | `G-XXXXXXXXXX` | | Google Tag Manager | `GTM-XXXXXXX` | | Meta (Facebook) pixel | 6–20 digits | | TikTok pixel | 10–30 letters and digits | | LinkedIn Insight Tag | partner ID, 3–12 digits | Pixels only ever load when you set them up; QRdx adds no third-party scripts of its own to hosted pages. Google Tag Manager loads only when the page opens on one of your [custom domains](https://qrdx.dev/docs/custom-domains). A container can run any script, so it never runs on qrdx.dev, where visitors may be signed in to QRdx. **Consent.** Pixels never load before the visitor agrees: - **Where required** (default): visitors in the EU/EEA, the UK and Switzerland, and visitors whose location is unknown, see a short banner naming your providers and choose **Accept** or **Decline**. The choice is remembered for that page, and **Cookie settings** lets them change it. Elsewhere pixels load right away, except for browsers that send the Global Privacy Control signal. - **For every visitor:** everyone sees the banner first. You remain responsible for your own privacy policy and for how you use the data your pixels collect. Codes that open your own website go there directly, so add your pixels to your site instead. ## Conversion tracking (Business) [#conversion-tracking-business] Conversion tracking tells you which scans led to a sale, sign-up or any other goal, with revenue. 1. On a code that opens your website, turn on **Conversion tracking**. Every scan then opens your URL with a signed `qrdx_sid` parameter added, for example `https://shop.example/?qrdx_sid=…`. 2. Add the snippet to the pages people land on and the page where they convert: ```html ``` The script remembers `qrdx_sid` in a first-party cookie on your domain for 30 days (add `data-storage="session"` to the script tag to keep it only for the browser session) and removes it from the address bar. Conversions are only sent for visitors who arrived from a scan, so other visitors are never tracked. | Field | Meaning | | --- | --- | | `name` | What happened, e.g. `purchase` or `signup` (default `conversion`) | | `value` | Amount, up to 2 decimals (optional) | | `currency` | ISO 4217 code like `USD`; defaults to `USD` when a value is given | | `id` | Your order or event id: sending the same id twice records it once | | `metadata` | Up to 20 extra key/value pairs (optional) | ### From your server [#from-your-server] To report conversions from your backend (more reliable than the browser), keep `qrdx_sid` with the order and call the API with an [OAuth access token](https://qrdx.dev/docs/developers/rest-api#get-an-access-token) that has the `codes:write` scope: ```bash curl -X POST https://api.qrdx.dev/v1/conversions \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"sid": "", "name": "purchase", "value": 49.99, "currency": "USD", "id": "ORDER-1234"}' ``` With a token you can also send `"code": ""` instead of `sid` when the scan isn't known. The API answers `201` for a new conversion and `200` with `"duplicate": true` when the `id` was already recorded. ### Limits [#limits] - A scan can be attributed for 30 days after it happened. - `qrdx_sid` is signed, so it can't be guessed or changed to point at another code. - Browser reports need conversion tracking to be on for the code, and each scan can report a limited number of conversions per hour. - Conversions stop being recorded if the workspace leaves Business; existing ones stay in your analytics, and turning tracking off is always possible. --- # Notifications URL: https://qrdx.dev/docs/notifications Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/notifications.mdx Email alerts, a weekly digest, and Slack or Discord messages when your QR codes get scanned or your forms get responses. Choose what QRdx tells you about in [Settings → Notifications](https://qrdx.dev/settings/notifications). Settings are per person and per workspace: your personal workspace and every team you belong to have their own switches, and in a team every member picks their own. | Channel | What you can get | Plan | | --- | --- | --- | | Email | Weekly digest, scan milestones, form submissions, new codes, code changes | Pro and Business | | Slack, Discord | Scan milestones, form submissions, new codes, code changes | Business | | [Webhooks](https://qrdx.dev/docs/webhooks), Zapier, Make | Every event, including each scan | Business | Team workspaces follow the plan of the team's owners. Everything is off until you turn it on. ## Alerts [#alerts] - **Scan milestones:** a code's first scan (with when, where and on which device), then 10, 100, 500, 1,000, 5,000, 10,000 scans and beyond. - **Form submissions:** the answers from a form on one of your [hosted pages](https://qrdx.dev/docs/landing-pages), with a link to all responses. - **New codes** and **code changes:** who created a code, or changed its destination, title, design or domain. Most useful in teams. To keep a busy form or a bulk import from flooding your inbox or channel, each target gets at most 30 alerts of one kind per hour; the rest are skipped (the weekly digest still counts them, and webhooks still receive every event). ## Weekly digest [#weekly-digest] Every Monday (UTC) you get last week's numbers for the workspace: scans compared with the week before, unique visitors, your top 5 codes, the top countries, new codes and form responses. It's skipped when there were no scans for two weeks and no form responses. ## Email [#email] Alert emails go to your account's email address. Each one has an **Unsubscribe** link that turns off that kind of email without signing in (mail apps that support one-click unsubscribe show it too). Turn emails back on, or off, in notification settings at any time. Team invitations and other account emails are not affected by these settings. ## Slack and Discord [#slack-and-discord] 1. Create an incoming webhook: - **Slack:** add the [Incoming WebHooks](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks) app, or your own Slack app with incoming webhooks, to a channel and copy the URL (`https://hooks.slack.com/services/...`). - **Discord:** channel settings → **Integrations** → **Webhooks** → **New Webhook** → **Copy Webhook URL** (`https://discord.com/api/webhooks/...`). 2. In [Settings → Notifications](https://qrdx.dev/settings/notifications), click **+ Slack** or **+ Discord** in the workspace, paste the URL and pick the events. 3. Use the send button to post a test message. Only Slack and Discord webhook URLs are accepted. After saving, QRdx shows only the start of the URL, because anyone who has it can post to the channel. Messages never mention `@everyone`, `@here` or roles, whatever visitors type into a form. ## Plans and downgrades [#plans-and-downgrades] If a workspace's plan no longer includes a channel, its alerts stop, but your settings are kept and you can still turn them off or remove targets. Your QR codes keep working on every plan. ## Self-hosting [#self-hosting] Emails are sent through [Resend](https://resend.com): set `RESEND_API_KEY` and `EMAIL_FROM` (an address on a domain verified in Resend). Without them, emails are skipped and logged, and team invites fall back to copyable links. The weekly digest runs from a scheduled job on Monday mornings (`apps/docs/vercel.json` on Vercel calls `/api/cron/digest?shard=all`, which sends every subscriber's digest in one run). With many subscribers you can instead call it hourly on Mondays without `shard=all`, so each run handles a twenty-fourth of them. Elsewhere, call it with the `CRON_SECRET` from your environment: ```bash curl -H "Authorization: Bearer $CRON_SECRET" https://your-host/api/cron/digest ``` Unsubscribe links are signed with `BETTER_AUTH_SECRET`; changing it invalidates links in emails already sent. --- # Landing pages URL: https://qrdx.dev/docs/landing-pages Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/landing-pages.mdx Build a mobile page for a QR code from blocks, with no website needed. Link lists, business pages, vCard Plus and custom pages. A hosted landing page is a mobile page that QRdx serves for you at `https://qrdx.dev/p/` (or on your [custom domain](https://qrdx.dev/docs/custom-domains)). Every page comes with a [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes), so you can keep editing the page after the code is printed and see every scan. ## Create a page [#create-a-page] 1. Open [Dashboard → Pages](https://qrdx.dev/dashboard/pages) and click **New page**, or click **New code** on the codes list and pick a page type instead of **Website link**. 2. Pick a page type and a template from its gallery: - **Link list**: your links, socials and latest work on one page (Creator, Musician, Minimal, Podcast). - **Business**: opening hours, location, contact details and a call to action (Local shop, Salon & studio, Professional services). - **vCard Plus**: a digital business card with a **Save contact** button (Classic, Midnight, Minimal). - **Custom page**: start blank or from an announcement layout. Other page types (menus, coupons, events, app links, forms, PDFs, images, audio and video) appear in the same picker. 3. Click **Create page**. The editor opens with the template's blocks and the QR code is ready to download from **QR code**. ## Edit and publish [#edit-and-publish] The editor has three tabs and a live phone preview. Tap a block in the preview to jump to its settings. - **Blocks**: add blocks with **Add block**, reorder them by dragging (or with the arrow buttons), hide a block without deleting it, duplicate or remove it. Each block has its own form; problems (for example a link without `https://`) are flagged on the block and must be fixed before publishing. - **Design**: use the colors of one of your [saved QR themes](https://qrdx.dev/dashboard/themes) so the page matches the printed code, pick a preset palette or set each color yourself. Choose a font, corner radius, a solid, gradient or image background and an optional logo. **Match colors** derives readable text and card colors from your background and accent, and a warning appears when text contrast is low. - **Settings**: the share preview description and image, whether search engines may index the page (off by default), and custom CSS on Business. Click **Publish** (or press ⌘/Ctrl + S) to put your changes live. Nothing changes for visitors until you publish; unpublished edits are kept in your browser so you can restore them after closing the tab. Each publish is stored as a version. If a teammate publishes the same page while you are editing, QRdx asks you to reload instead of overwriting their changes. ## Core blocks [#core-blocks] | Block | What it shows | | --- | --- | | Profile header | Photo or logo, name, bio and an optional cover image | | Text | A heading and a paragraph (line breaks are kept) | | Button | One call to action: a web link, email address or phone number | | Link list | A stack of link buttons, with site icons for Instagram, YouTube, WhatsApp and more | | Social icons | Profiles as round icons or buttons | | Image | A photo or banner, optionally linked | | Video | A YouTube, Vimeo or Loom video. It loads from the video site only after the visitor taps play | | Contact card | Name, job title, phones, emails, website, address, quick actions (call, WhatsApp, email, directions), **Save contact** and **Share** | | Address & map | Your address with Google Maps and Apple Maps directions, and an optional embedded map | | Opening hours | Hours per day (split shifts and past-midnight hours included) with an **Open now** badge in your time zone | | Divider, Spacer | Layout helpers | ### vCard Plus and Save contact [#vcard-plus-and-save-contact] **Save contact** downloads a vCard 3.0 file (`.vcf`) that phones open in their contacts app. It contains the name, company, job title, phones, emails, website, address, bio, the page's social profiles and the photo. Photos uploaded to QRdx storage are embedded in the file; photos linked from other sites are included as a link (QRdx never downloads images from other sites). **Share** opens the phone's share sheet, or copies the page link on computers. ## Plans [#plans] | | Free | Pro | Business | | --- | --- | --- | --- | | Hosted pages | 3 | Unlimited | Unlimited | | "Made with QRdx" footer | Shown | Removed | Removed | | White-label (your logo as the browser icon, no QRdx branding) | — | — | ✓ | | Custom CSS | — | — | ✓ | Limits only stop you from creating new pages. Pages you already have keep working on every plan, including after a downgrade; on a downgrade from Business, custom CSS simply stops being applied. Each page also uses one of your plan's dynamic codes. See [Plans and limits](https://qrdx.dev/docs/plans-and-limits). ### Custom CSS (Business) [#custom-css-business] Write plain CSS in **Settings → Custom CSS**. Rules only apply inside your page: every selector is scoped to the page content, and each block can be targeted with `.lp-block-`, for example `.lp-block-links a` or `.lp-block-contact h1`. For safety, CSS can't load files (`url()`, `@import`, `@font-face`), can't use backslash escapes, and supports the `@media`, `@supports`, `@container` and `@keyframes` at-rules only. ## What visitors get [#what-visitors-get] - Fast, server-rendered pages built for phones, with no QRdx analytics or chat scripts. Tracking pixels load only if you add them to the code yourself. - Hidden from search engines unless you turn on **Show in search engines**. - Share previews (title, description, image) for chat apps and social networks. Password-protected codes never reveal page details in previews. - A small **Report abuse** link on every page, including white-label pages. Password protection, start and end dates, scan limits and pausing from [code controls](https://qrdx.dev/docs/code-controls) apply to the page and its **Save contact** file too, also when someone opens the page link directly. --- # Menus, coupons, events and forms URL: https://qrdx.dev/docs/forms-and-pages Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/forms-and-pages.mdx Hosted page types for restaurants, offers, events and app downloads, plus forms that collect feedback into a response inbox. A dynamic QR code can open a hosted page instead of a website. Besides link lists, business pages and vCards, QRdx has five page types for common print jobs. Each starts from a template you can change block by block in the page editor, with a live phone preview. | Page type | Made for | Blocks it starts with | | --- | --- | --- | | **Menu** | Cafés, restaurants, bars | Menu, Wi-Fi, opening hours, map | | **Coupon** | Discounts and freebies | Coupon with barcode and Redeem button | | **Event** | Parties, conferences, webinars | Event with add-to-calendar | | **App download** | One code for every app store | App download | | **Feedback & forms** | Reviews, NPS surveys, sign-ups | Form, review links | Every block below can also be added to any other page. ## Menu [#menu] The **Menu** block lists sections (Starters, Mains, ...) with items. Each item can have a description, a price, a note after the price ("per glass", "Market price"), a photo, a short badge ("Chef's pick") and: - **Dietary tags:** vegetarian, vegan, gluten-free, dairy-free, halal, kosher, spicy, organic. - **Allergens:** the 14 allergens food businesses must declare in the EU and UK (gluten, crustaceans, eggs, fish, peanuts, soy, milk, tree nuts, celery, mustard, sesame, sulphites, lupin, molluscs), shown as "Contains: …". - **Sold out:** the item stays on the menu, dimmed and marked as sold out. All prices on one menu use the currency you pick. With two or more sections, shortcut chips at the top jump to each section. A menu holds up to 30 sections and 300 items. The **Wi-Fi** block shows your network name and password with copy buttons. Phones can't join a network from a web page, so guests copy the password and paste it in their Wi-Fi settings. Anyone who opens the page can see the password. ## Coupon [#coupon] The **Coupon** block shows a headline, the code with a copy button and, optionally, a barcode your till can scan: - **Code 128** encodes up to 48 letters, digits and symbols (no accents or emoji). - **EAN-13** takes 12 digits (QRdx adds the check digit) or all 13. Numbers starting with 20–29 are reserved for in-store use, which suits coupon codes. By default the barcode encodes the coupon code; set **Barcode value** to encode something else, such as your point-of-sale number. **Valid from** and **Valid until** are calendar days. A coupon counts as started once its first day has begun anywhere in the world and as expired only after its last day has ended everywhere, so nobody sees "expired" early because of their time zone. Expired coupons stay visible, marked as expired. Turn on **Redeem button** to count redemptions: the visitor shows the screen to your staff and taps **Redeem now**. The page then shows "Redeemed" with the time on that phone, and the count appears on the page's [Responses](#responses) screen. It can't stop the same code being shown on another phone, so treat it as a counter, not a lock. ## Event [#event] The **Event** block shows the date and time, venue and address (with an **Open in Maps** link), an online link, the organizer and a button such as **Get tickets**. Times are entered in the event's time zone and shown to visitors in that time zone, for example "7:00 – 10:00 PM GMT+1". All-day and multi-day events are supported; without an end time an event lasts an hour. **Add to calendar** offers Google Calendar, Apple Calendar (an `.ics` file), Outlook.com and Yahoo Calendar. The `.ics` download is served from your page's address, so it also works on a [custom domain](https://qrdx.dev/docs/custom-domains); in the editor preview it's disabled until the page is published. Once the event is over, the block says so and hides the calendar button. ## App download [#app-download] The **App download** block takes your App Store link (`apps.apple.com`), Google Play link (`play.google.com`) and a website. The visitor's store comes first: iPhone and iPad users see the App Store button on top, Android users Google Play, everyone else your website. The other links stay underneath, so nobody is stuck. ## Forms [#forms] The **Form** block collects answers. Questions can be: | Type | Visitors answer with | | --- | --- | | Text | A short or long answer (you set the maximum length) | | Email | An email address | | Phone | A phone number | | Star rating | Stars, out of 3 to 10 (you choose; 5 by default) | | NPS | A 0–10 "how likely are you to recommend us" score | | Choice | One option, or several when **Allow several answers** is on | | Checkbox | A tick, for example consent to be contacted | Mark questions as **Required** to make them mandatory. Visitors see errors next to the question, and a thank-you message you write after sending. Ask only for what you need, and say how you'll use contact details. Spam protection is built in: a hidden field that only bots fill in, and limits on how often one device can send a form. When the server has [Cloudflare Turnstile](#cloudflare-turnstile) keys, forms also run Turnstile's invisible check. ### Review links [#review-links] The **Review links** block shows "Review us on Google" (and Tripadvisor, Yelp, Facebook, Trustpilot or any other site) buttons. It's shown to every visitor, whatever they answered in a form. Review sites, Google included, don't allow showing review links only to people who rated you highly, so QRdx never hides them based on a rating. In Google Business Profile, choose **Ask for reviews** to copy your review link. ## Responses [#responses] Open a page in **Dashboard → Pages** and choose **Responses** (or go to `/dashboard/pages//responses`). Responses are listed newest first, 25 per screen, with the time (UTC), the code the visitor scanned and every answer. Email addresses and phone numbers are links. - **Several forms on one page:** tabs filter by form. - **Export CSV:** one row per response and one column per question. Columns follow the current questions, then questions you removed that still have answers. Renaming a question keeps its column. - **Delete:** select responses, or use the trash button on one. Deleting can't be undone. In a team, members can delete responses on pages they created; owners and admins on any page. Responses belong to the page. Deleting the page deletes its responses. ### Monthly limits [#monthly-limits] | Plan | Responses per month | | --- | --- | | Free | 50 | | Pro | 1,000 | | Business | 10,000 | The limit counts responses received by all pages in the personal workspace or team, per calendar month (UTC), and starts over on the 1st. The Responses screen shows how many you've used. When the limit is reached, forms show "This form isn't accepting responses right now" and keep what the visitor typed. Responses you already have are never deleted or hidden, on any plan, including after a downgrade. ## Access rules [#access-rules] Forms and calendar files follow the page's [code controls](https://qrdx.dev/docs/code-controls): while a code is paused, scheduled for later, expired or over its scan limit, its form stops accepting responses and the `.ics` file isn't served. On a password-protected page they work once the visitor has entered the password. ## Notifications and webhooks [#notifications-and-webhooks] Every response sends a `form.submitted` event with the answers. Use it for [email or Slack/Discord notifications](https://qrdx.dev/docs/notifications) and [webhooks](https://qrdx.dev/docs/webhooks) (Zapier, Make, your own server). ## Privacy [#privacy] Answers are stored with the page in your workspace. QRdx doesn't store visitors' IP addresses with responses: each response keeps only a salted hash of the IP address and browser for that day, which can't be turned back into either. ## Self-hosting [#self-hosting] ### Cloudflare Turnstile [#cloudflare-turnstile] Set both variables to protect forms with [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/): ```bash NEXT_PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAA... TURNSTILE_SECRET_KEY=0x4AAAAAAA... ``` With only one of them set, Turnstile stays off. The Turnstile script loads when a visitor first touches a form, not with the page, and runs invisibly unless Cloudflare needs the visitor to click. If Cloudflare can't be reached, responses are still accepted so you never lose feedback; the other spam protections still apply. ### Custom domains [#custom-domains] On a verified [custom domain](https://qrdx.dev/docs/custom-domains), the form endpoint (`/api/forms/...`) and calendar files (`/api/calendar/...`) are served next to your pages, so forms and add-to-calendar work there without extra setup. --- # Files URL: https://qrdx.dev/docs/files Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/files.mdx Upload PDFs, images, audio and video for your hosted pages, and set up file storage when you self-host. Hosted pages can show files you upload to QRdx: a menu or brochure as a PDF, a photo gallery, music or a podcast, or a video. Uploaded files live in **Dashboard → Files**, belong to your personal workspace or to the active team, and each one has a stable link that keeps working when you replace the file. ## What you can upload [#what-you-can-upload] | Kind | Formats | | --- | --- | | PDF | PDF | | Images | JPEG, PNG, WebP, GIF, AVIF | | Audio | MP3, M4A, AAC, Ogg, WAV | | Video | MP4, WebM, MOV | MP4 with H.264 video plays in every browser. MOV files from phones usually play too, but convert them to MP4 if a browser can't play one. SVG, HTML, archives and office documents can't be uploaded, so nothing served from storage can run code in a visitor's browser. Every upload is checked before it's used: its size must match what was announced, and its first bytes must really be a file of its type (an HTML page renamed to `.pdf` is rejected). Rejected uploads are deleted from storage and listed as **Blocked**. ## Limits per plan [#limits-per-plan] | Plan | Storage per workspace | Largest file | | --- | --- | --- | | Free | 50 MB | 10 MB | | Pro | 5 GB | 100 MB | | Business | 50 GB | 250 MB | Team workspaces use the plan of the team's owner. Limits only stop new uploads: after a downgrade, or when a workspace is over its storage, every file you already uploaded keeps being served on every page that uses it. Delete files you no longer need (or upgrade) to upload new ones. ## Use files on a page [#use-files-on-a-page] Five blocks show files. Each one can use an uploaded file or a link to a file hosted somewhere else: - **PDF** shows the document inline on tablets and computers, with **Open PDF** (full-screen viewer on phones) and **Download** buttons. - **Gallery** shows photos in a grid of 1–3 columns. Tapping a photo opens it full screen; swipe or use the arrow keys to browse. - **Audio** plays tracks with cover art and a playlist; the next track starts when one ends. A download button is optional. - **Video file** plays an MP4 or WebM you upload, optionally muted on a loop like a GIF. For YouTube, Vimeo or Loom, use the **Video** block instead: it embeds the video without loading anything from YouTube until someone taps play. - **Downloads** lists files people can save, each with its own label. The **PDF**, **Images**, **MP3 / Audio** and **Video** page types in **New code** start you with these blocks already in place. In a block, choose **Upload** to add a file from your device, **From your files** to reuse one you uploaded before, or paste a link. ## Replace or delete a file [#replace-or-delete-a-file] Open **Dashboard → Files** and use the menu next to a file: - **Copy link** copies the file's own link (`https://qrdx.dev/api/files/`). You can also use it as the destination of a dynamic QR code. - **Replace file** uploads new content behind the same link, for example next season's menu. Pages and codes that use the file show the new version within a minute. A file can only be replaced by one of the same kind (a PDF by a PDF). - **Delete** removes the file for good. If pages or codes still use it, QRdx lists them first; after deleting, those pages show "File unavailable" where the file was. In a team, members can replace and delete the files they uploaded; owners and admins can manage every team file. ## Reported files [#reported-files] Every hosted page has a **Report abuse** link. If a file breaks the rules or a copyright owner asks for it to be removed, QRdx can block it. A blocked file stops being served on every page and through its QRdx link within a minute (pages show "File unavailable"), and identical copies of it (up to 25 MB) can't be uploaded again. ## Set up storage (self-hosting) [#set-up-storage-self-hosting] File uploads use [Cloudflare R2](https://developers.cloudflare.com/r2/). Without it, the Files page says "Storage not configured on this server", the upload API answers `503`, and blocks can still use links to files hosted elsewhere. 1. In the Cloudflare dashboard, open **R2** and create a bucket, for example `qrdx-files`. 2. Under **R2 → Manage API tokens**, create a token with **Object Read & Write** on that bucket. 3. Set these environment variables and restart: ```bash R2_ACCOUNT_ID=your-cloudflare-account-id R2_ACCESS_KEY_ID=token-access-key-id R2_SECRET_ACCESS_KEY=token-secret-access-key R2_BUCKET=qrdx-files # Optional: serve files from a public bucket URL or custom domain R2_PUBLIC_BASE_URL=https://files.example.com ``` 4. Add a CORS policy to the bucket (**Settings → CORS policy**). Browsers upload straight to R2, so the bucket must accept `PUT` requests with a `Content-Type` header from your app's origin: ```json [ { "AllowedOrigins": ["https://qrdx.example.com", "http://localhost:3000"], "AllowedMethods": ["PUT", "GET", "HEAD"], "AllowedHeaders": ["Content-Type"], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3600 } ] ``` Replace `https://qrdx.example.com` with the origin people use to open your dashboard (scheme and host, no trailing slash). Keep `http://localhost:3000` only if you upload from a local development server. ### Public files or presigned links [#public-files-or-presigned-links] Visitors always open files through `/api/files/`, which redirects to the stored object: - With `R2_PUBLIC_BASE_URL` set (a custom domain connected to the bucket, or its `r2.dev` URL, which Cloudflare rate-limits and recommends for testing only), it redirects to the public URL. Object keys contain a random id, so files can't be listed or guessed. - Without it, the bucket stays private and every redirect is a presigned link that expires after an hour. Either way, a replaced, deleted or blocked file stops being served through its QRdx link within a minute. A blocked file also leaves its public URL at once: its object moves to a `quarantine/` key with a random part (kept so an admin can restore it), and its QRdx link answers `410 Gone`. Browsers upload to a `staging/` key. Once the upload is checked, QRdx copies exactly the checked version to the file's own key, so an upload link that is still valid can't replace a checked file. An upload that never finishes stops counting toward storage after two hours and is cleaned up the next time someone uploads to that workspace. Add an object lifecycle rule to the bucket (**Settings → Object lifecycle rules**) that deletes objects with the prefix `staging/` after one day, so leftovers from abandoned uploads go away too. --- # Teams URL: https://qrdx.dev/docs/teams Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/teams.mdx Share dynamic QR codes, pages, files, domains and themes with your team, invite people by email and choose owner, admin or member roles. A team is a shared workspace. Everything created while a team is open belongs to the team instead of to you: dynamic codes and [hosted pages](https://qrdx.dev/docs/landing-pages), [files](https://qrdx.dev/docs/files), [custom domains](https://qrdx.dev/docs/custom-domains), folders and tags, form responses and [webhooks](https://qrdx.dev/docs/webhooks). Owners and admins can also copy saved themes to the team so every member designs with the same colors. Your personal workspace stays separate, and you switch between the two with the workspace switcher at the top of the dashboard sidebar. Creating a team needs Pro or Business. A Pro team has up to 3 members, a Business team has no member limit. Anyone can join a team they're invited to, including people on the Free plan. ## Create a team [#create-a-team] 1. Open the workspace switcher and click **Create team**, or go to [Dashboard → Team](https://qrdx.dev/dashboard/team). 2. Enter a name and a team URL (3 to 48 lowercase letters, numbers and dashes). 3. The new team opens as your active workspace, with you as its owner. ## Invite people [#invite-people] Owners and admins invite people from [Dashboard → Team](https://qrdx.dev/dashboard/team): 1. Enter the person's email address and pick a role, **Member** or **Admin**. 2. QRdx emails them an invitation. Every invitation also has a link you can copy and send yourself. 3. They open the link, sign in with the invited email address and click **Accept invitation**. The team then becomes their active workspace. Invitations expire after 48 hours and only work for the email address they were sent to, so a forwarded link can't be used by someone else. Pending invitations take up a seat until they are accepted, so revoke the ones you no longer need. ## Roles [#roles] | | Member | Admin | Owner | | --- | --- | --- | --- | | Create and edit codes and pages, upload files | Yes | Yes | Yes | | Delete codes, files and form responses | Their own | Any | Any | | Add saved themes to the team | No | Yes | Yes | | Invite and remove people, change roles | No | Yes | Yes | | Add, verify and remove custom domains | No | Yes | Yes | | Manage webhook endpoints and see signing secrets | No | Yes | Yes | | Make someone an owner | No | No | Yes | | Delete the team | No | No | Yes | Members can see webhook endpoints and their delivery logs, and use the team's verified domains for their codes. [Notification settings](https://qrdx.dev/docs/notifications) are personal: every member chooses their own alerts for each team. ## Plans and limits [#plans-and-limits] A team uses the best plan among its owners, so one owner on Business gives the whole team Business limits and members don't need their own subscription. Limits such as dynamic codes, pages, storage and form responses are counted per team, separately from each member's personal workspace. See [Plans and limits](https://qrdx.dev/docs/plans-and-limits). If the owners' plan changes, current members stay and every code keeps working; only new invitations and new things over the limit are blocked. ## Leave or delete a team [#leave-or-delete-a-team] - **Leave:** click **Leave team** in the danger zone. The last owner can't leave: make another member an owner first, or delete the team. - **Remove a member:** the codes and pages they created stay with the team. - **Delete a team:** owners only. You confirm by typing the team URL. Nothing stops working: codes, pages, files, domains and other team items move back to the personal workspace of whoever created them. ## API access [#api-access] [REST API](https://qrdx.dev/docs/developers/rest-api) requests made with an OAuth access token act on the token owner's personal workspace. Requests made from a signed-in browser session act on the team that is open. --- # Plans and limits URL: https://qrdx.dev/docs/plans-and-limits Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/plans-and-limits.mdx What Free, Pro and Business include, how limits apply to personal and team workspaces, and what happens when you downgrade or cancel. QRdx has three plans. Static QR codes from the playground and the `qrdx` library are free and unlimited on all of them, with no account. Compare prices on the [pricing page](https://qrdx.dev/pricing). | | Free | Pro | Business | | --- | --- | --- | --- | | Price (US$) | $0 | $5/month or $48/year | $15/month or $144/year | | Price (India) | ₹0 | ₹199/month or ₹1,499/year | ₹599/month or ₹4,999/year | Visitors from India see prices in rupees. Checkout is handled by [Polar](https://polar.sh), which charges customers in India in ₹ and everyone else in US$, and adds local taxes where they apply. ## Limits [#limits] | | Free | Pro | Business | | --- | --- | --- | --- | | Dynamic QR codes | 10 | Unlimited | Unlimited | | Scans | Unlimited | Unlimited | Unlimited | | Hosted landing pages | 3, with a "Made with QRdx" footer | Unlimited, no footer | Unlimited, white-label, custom CSS | | Analytics history | 90 days | 3 years | Unlimited | | Custom domains | None | 5 | 25 | | Custom back-halves (`qrdx.dev/q/spring-menu`) | No | Yes | Yes | | Team members | 1 (no teams) | 3 | Unlimited | | Smart routing, A/B tests, UTM tags | No | Yes | Yes | | Passwords, schedules, expiry, scan limits | No | Yes | Yes | | Retargeting pixels | No | Yes | Yes | | Conversion tracking | No | No | Yes | | Email digests and alerts | No | Yes | Yes | | Webhooks, Zapier and Slack | No | No | Yes | | OAuth apps ([build an integration](https://qrdx.dev/docs/developers/build-an-integration)) | 1 | 5 | 25 | | File storage (largest file) | 50 MB (10 MB) | 5 GB (100 MB) | 50 GB (250 MB) | | Form responses per month | 50 | 1,000 | 10,000 | | API requests per day | 1,000 | 10,000, plus bulk creation | 100,000, plus bulk creation | | AI art images per month | 3 | 30 | 150, pooled per team | | AI theme generations | 5 in total | Unlimited | Unlimited | | Saved themes | 10 | Unlimited, shared with your team | Unlimited, shared with your team | Only active dynamic codes count toward the code limit: archived codes don't. Scans never count toward anything, and no plan pauses a code because of traffic. The only scan caps are the ones you set yourself on a code. The API allowance is per workspace (personal or team) and resets at 00:00 UTC. Each request that counts returns `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` so you can watch it. See the [REST API](https://qrdx.dev/docs/developers/rest-api) for the per-minute limits. ## Personal and team workspaces [#personal-and-team-workspaces] Your personal workspace uses your own plan. A team uses the best plan among its owners, so one owner on Business gives the whole team Business limits. Members don't need their own subscription. Free accounts can't create teams. A Pro team has up to 3 seats, a Business team has no limit. Seats are members, owner included, plus pending invitations, so revoke an invitation you no longer need to free its seat. If the owners' plan changes, current members stay; only new invitations are blocked. ## Going over a limit, downgrading and cancelling [#going-over-a-limit-downgrading-and-cancelling] Limits only stop new things. Existing codes keep redirecting on every plan, including after a downgrade, a failed payment or a cancellation. That's the [Never-Hostage Guarantee](https://qrdx.dev/guarantee). - **Over a limit:** you can't create more until you're under it. You can still edit, archive and export what you have. - **Over the daily API allowance:** API calls get `429` until 00:00 UTC. Your codes keep redirecting and the website keeps working. - **Downgrading:** codes, landing pages and verified custom domains keep working. Analytics show the history your new plan includes. - **Cancelling:** your plan stays active until the end of the period you paid for, then your account moves to Free. - **A failed payment:** once the subscription lapses, it's the same as cancelling. Nothing is paused. Codes that end for a reason you chose, such as an expiry date, a pause or a scan limit, send scanners to your fallback URL or to a neutral "This campaign has ended" page, never to a QRdx page. ## Billing [#billing] Upgrade from the [pricing page](https://qrdx.dev/pricing). To change plans, switch between monthly and yearly, update your card or cancel, open **Manage subscription** in your settings, which takes you to the Polar billing portal. One subscription covers your account, so while you have one, plan changes go through the portal instead of a new checkout. ## Self-hosting [#self-hosting] A [self-hosted](https://qrdx.dev/docs/self-hosting) instance uses the same plans. The limits are defined in one file, `apps/docs/lib/plans.ts`, which you can change for your instance. Without Polar configured, upgrades are unavailable and every account uses the Free limits. --- # Move from Bitly, Dub, Rebrandly and others URL: https://qrdx.dev/docs/migrate Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/migrate.mdx Import your short links and dynamic QR codes into QRdx, keep printed codes working, and keep your branded back-halves. QRdx imports links and dynamic codes from other providers in one go. Open [Dashboard → Codes](https://qrdx.dev/dashboard/codes) and click **Import**. Every import has the same three steps: pick what to bring, preview it, import it. - **Connected accounts:** Dub, Bitly, Rebrandly and Short.io. QRdx lists your links through the provider's API. - **Export files:** QR Code Generator PRO, Uniqode (Beaconstac), Flowcode, QR TIGER, ME-QR, TinyURL, Hovercode, Scanova, or any CSV with destination URLs. Nothing changes in your old account during the import itself. ## What an import creates [#what-an-import-creates] Each link or code becomes a [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes) with: - its destination and title; - its tags (optional), and a folder such as "Imported from Bitly" (you can rename it or turn it off); - notes like `Imported from Bitly bit.ly/3AbCdEf on 2026-11-01.` plus the link's original creation date. QRdx sets its own creation date for the new code. The preview shows how many codes will be created, which tags are new, and what is skipped: - **Already imported:** links imported before (from any earlier run) are always skipped, so running an import twice never creates duplicates. - **Same destination:** links whose destination already has a QRdx code are skipped by default. Tick the option to import them anyway. - **Can't import:** missing or invalid destinations, and links that already point to a QRdx short link. Imports run in batches of 50 with a progress bar. Closing the dialog is safe: open **Import** again and the import continues from **Recent imports**. The summary lists created, skipped and failed codes with the reasons. Plan limits apply as for any new code. Free includes 10 active dynamic codes; if an import needs more, the preview says how many fit and offers to import the first ones, or to upgrade. Pro and Business have no limit. ## From a connected account [#from-a-connected-account] 1. Click **Import** and pick the provider. If it isn't connected yet, connect it right there (sign in, or paste an API key or token). Tokens are stored encrypted, and you can disconnect in Settings → Integrations. 2. Search, filter by domain or tag, and select links. **Select all matching links** takes up to 1,000 links per import; run it again for the rest. 3. Check the preview and click **Import**. | Provider | Connect with | List links | Keep printed codes working | |---|---|---|---| | Dub | Sign in (OAuth) | Yes | Yes, on every Dub plan | | Bitly | Sign in, or a generic access token | Yes | Paid Bitly plans only: 5 link redirects a month on Core, 20 on Growth, 110 on Premium | | Rebrandly | API key | Yes | Within Rebrandly's monthly destination edits: 10 on Essentials, 50 on Professional, 150 on Growth (none on Free) | | Short.io | API key | Yes, one domain at a time | Yes, on every plan | Plan limits are the providers' own and change over time. Check their pricing pages. ## Keep printed codes working [#keep-printed-codes-working] QR codes you printed with another provider encode that provider's short link. After a connected-account import, the summary offers **Keep my printed codes working**: 1. A **dry run** lists every old link and the QRdx link it would point to. Nothing changes yet. 2. Confirm, and QRdx changes each old link's destination to its new QRdx code. Visitors still end up on the same page, and every scan now shows in QRdx analytics. The old provider still sees a click first. 3. **Undo** puts back the original destinations, which QRdx saved with the import. If the provider refuses (for example a Bitly plan without link redirects left), QRdx stops, says why, and leaves the remaining links unchanged. Links skipped because their destination already had a QRdx code point at that existing code. ## Keep the original back-halves [#keep-the-original-back-halves] On Pro and Business, tick **Keep the original back-halves** in the import preview. Each code then gets its old back-half instead of a random one when QRdx can keep it, so `bit.ly/spring-menu` becomes `qrdx.dev/q/spring-menu`: - A back-half is kept when it's 3 to 64 letters, digits, `-` or `_` (see [custom back-halves](https://qrdx.dev/docs/dynamic-qr-codes#custom-back-halves)), isn't reserved, and isn't used yet on `qrdx.dev` by any QRdx code. - Otherwise the code gets a generated back-half, and the preview shows the change with the reason, for example `spring-menu → Ab3xK9q · Another QRdx code already uses this back-half on qrdx.dev`. The import uses exactly the back-half the preview shows. - Back-halves are case-sensitive, as at Bitly, Dub and Short.io. Keep the provider connected until you're sure: undo needs the connection. The old account also has to stay active for its links to keep redirecting. Back-halves that can't be kept get a generated one; those links need a reprint or a redirect at the old provider. Custom back-halves are part of Pro and Business. ## From an export file [#from-an-export-file] 1. Get the file (steps below). QRdx reads CSV; for an Excel file, save or download it as CSV first. 2. Click **Import**, pick the provider and upload the file. QRdx recognizes the provider's columns where it can, and you can change the mapping. Only the destination is required; the old short link (or code ID) lets a re-upload skip codes already imported. 3. Preview and import, then use **Copy all new links** or **Download mapping CSV** (old link → new QRdx link). Codes from these providers live on the provider's domain, so QRdx can't change them. To keep printed codes working, open each old dynamic code in the old provider and set its destination to the new QRdx link from the mapping. The old account has to stay active for those codes to redirect; reprint with the QRdx codes before you cancel. ### QR Code Generator PRO [#qr-code-generator-pro] There's no documented export of the code list. Make a spreadsheet with **Name**, **Short URL** and **Destination URL** for each code. To re-point codes: open the code → **Details** → **Edit**, replace the URL and save. For many codes, its bulk update takes a CSV of new URLs for up to 500 codes. ### Uniqode (Beaconstac) [#uniqode-beaconstac] **QR Codes → Dynamic**, select all, **⋯ → Export**, per organization or folder. The export's columns (QR Code Name, QR Code URL, Destination URL, Labels, …) are recognized. To re-point: open the code → **Edit** → new URL → **Next** → **Save**. ### Flowcode [#flowcode] There's no documented export of the code list. Reuse your bulk-create sheet (Flowcode Name, Flowcode Scan URL) or list Name, Scan URL and Destination. To re-point: **Assets** → the code → **Overview** → **Destination** → **Save**. ### QR TIGER [#qr-tiger] **My Account** → pick a category (for example URL) → **Download QR List**, once per category. To re-point: **My Account → Dashboard → URL** → the campaign → **Edit** → **Save**. ### ME-QR [#me-qr] There's no export. List Name, QR link and Destination URL for each code. To re-point: edit the code in your ME-QR codes list. ### TinyURL [#tinyurl] TinyURL lists CSV export on its paid plans. Export your links as CSV, or list Alias and Long URL. Editing a TinyURL destination needs a paid TinyURL plan. ### Hovercode, Scanova and others [#hovercode-scanova-and-others] Export your codes as CSV from the provider (Hovercode lists CSV exports on its Business plans; Scanova emails a download link), then check the column mapping. Any CSV with a destination column works with **Another provider (CSV)**. Static QR codes (from goQR.me, QR Code Monkey, Adobe Express and similar tools) encode the destination itself, so they keep working without the tool. Import their destinations from a CSV only if you want editable, tracked codes for new prints. ## From a QR code library [#from-a-qr-code-library] Libraries render static codes, so nothing needs migrating: codes already printed keep working. To switch the rendering in your app, install `qrdx` and swap the component. ### From qrcode.react [#from-qrcode-react] ```tsx // Before import { QRCodeSVG } from "qrcode.react"; ; // After import { QRCodeSVG } from "qrdx"; ; ``` `value`, `size`, `level`, `fgColor`, `bgColor`, `marginSize` (as `margin`) and `imageSettings` carry over; qrdx adds patterns, gradients and templates. See the [API reference](https://qrdx.dev/docs/api-reference). ### From qr-code-styling [#from-qr-code-styling] ```tsx // Before import QRCodeStyling from "qr-code-styling"; const qr = new QRCodeStyling({ data: "https://example.com", width: 256, dotsOptions: { type: "rounded", color: "#111827" }, image: "/logo.png", }); qr.append(ref.current); // After import { QRCodeSVG } from "qrdx"; ; ``` qrdx is a React component, so there's no instance to append: render it like any element. Downloads use the helpers in the [API reference](https://qrdx.dev/docs/api-reference). --- # Short links and file pickers URL: https://qrdx.dev/docs/short-links-and-file-pickers Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/short-links-and-file-pickers.mdx Shorten a QR code's link with your own Bitly, Rebrandly or Short.io account, or encode a Dropbox, OneDrive, Box or Notion share link. Connect an account in [Settings → Integrations](https://qrdx.dev/settings/integrations) and the **Website** type in the QR editor gets two buttons under the address field: - **Shorten**: creates a link in your Bitly, Rebrandly or Short.io account (or picks one you already have). The QR code encodes that short link, so you can change where it points later in that service, and it counts the clicks. - **Pick a file**: browse or search Dropbox, OneDrive, Box or Notion. QRdx creates the item's share link and the QR code encodes it. Hosted pages can use cloud files too: PDF, audio, video and download blocks get **From Dropbox**, **From OneDrive** or **From Box** next to the upload buttons. Integrations belong to your user account. Tokens and API keys are stored encrypted, and you can disconnect at any time. ## Short links [#short-links] | Service | How you connect | Domains | Back-half | | --- | --- | --- | --- | | Bitly | Sign in with Bitly, or paste a generic access token (Settings → Developer settings → API) | bit.ly and your branded domains | Picked by Bitly | | Rebrandly | Paste an API key (Account → API keys) | rebrand.ly and your branded domains | Your own slashtag, or random | | Short.io | Paste a secret API key (Integrations & API → API) | Your Short.io domains | Your own path, or random | Each integration's settings page shows your domains and lets you pick the default one for new links. You can still pick another domain per link. **Changing a destination later** QRdx creates the link; editing where it points happens in Bitly, Rebrandly or Short.io. Bitly only lets paid plans change a link's destination. If you'd rather keep the destination editable in QRdx itself, with scan analytics, use a [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes) instead. ## File pickers [#file-pickers] | Service | Link QRdx encodes | Who can open it | | --- | --- | --- | | Dropbox | A shared link (created, or the existing one reused) | Anyone with the link, unless your team only allows team links | | OneDrive | A view-only sharing link | Anyone with the link; organization-only where your admin turned "Anyone" links off | | Box | The item's shared link, set to "People with the link" | Anyone with the link; company-only where your admin requires it | | Notion | The page's "Publish to web" link | Anyone, once the page is published | When the link can't be opened by everyone, QRdx says so right after you pick, so a printed code doesn't send people to a sign-in page. ### Notion pages [#notion-pages] QRdx only sees the pages you share with it: Notion asks which ones when you connect. To add more later, open the page in Notion, click **•••** → **Connections** and add QRdx (or reconnect and pick them). Notion's API can't publish a page. Publish it yourself (**Share** → **Publish** → **Publish to web**), then pick it again so QRdx uses the public link. ### Files on hosted pages [#files-on-hosted-pages] For Dropbox and Box, page blocks get a direct link to the file, so audio and video players and the PDF viewer can load it. OneDrive sharing links open OneDrive's viewer, so a block shows them as a link. ## Disconnecting [#disconnecting] Disconnecting deletes the stored token. For Dropbox, Box and Notion, QRdx also revokes it with the service. For Bitly, Rebrandly and Short.io, delete the key in that service too if you no longer use it; for OneDrive, remove QRdx from your Microsoft account's app permissions. QR codes you already made keep working: they hold the short link or share link, not your token. ## Self-hosting [#self-hosting] Rebrandly and Short.io need nothing on the server. The others need an app in each service, with its client ID and secret in the environment; without them the integration shows "Not configured". Bitly still works without them, through pasted access tokens. | Integration | Variables | Redirect URI | | --- | --- | --- | | Bitly (sign-in) | `BITLY_CLIENT_ID`, `BITLY_CLIENT_SECRET` | `/api/integrations/bitly/callback` | | Dropbox | `DROPBOX_CLIENT_ID`, `DROPBOX_CLIENT_SECRET` | `/api/integrations/dropbox/callback` | | OneDrive | `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET` | `/api/integrations/onedrive/callback` | | Box | `BOX_CLIENT_ID`, `BOX_CLIENT_SECRET` | `/api/integrations/box/callback` | | Notion | `NOTION_CLIENT_ID`, `NOTION_CLIENT_SECRET` | `/api/integrations/notion/callback` | `` is `http://localhost:3000` in development and your production URL otherwise. Every integration also needs `INTEGRATION_ENCRYPTION_KEY` in production, which encrypts stored tokens and keys. Scopes to request: - **Dropbox:** `account_info.read`, `files.metadata.read`, `sharing.read`, `sharing.write` - **OneDrive:** `offline_access`, `Files.ReadWrite` (Microsoft Graph needs write access to create sharing links) - **Box:** Read and write all files and folders (`root_readwrite`) - **Bitly, Notion:** no scopes; Notion asks the user which pages to share --- # Developers URL: https://qrdx.dev/docs/developers Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/index.mdx Every way to build with QRdx, from a keyless image URL to the REST API, webhooks, MCP and the CLI. Pick the surface that fits what you're building. The first three need no account and no key. ## No key needed [#no-key-needed] - [QR code image API](https://qrdx.dev/docs/developers/image-api): A QR code image from a URL. Paste it into Markdown, HTML or an email. - [MCP server](https://qrdx.dev/docs/developers/mcp): Let Claude, Cursor, VS Code and other AI tools make styled QR codes. - [CLI](https://qrdx.dev/docs/developers/cli): SVG, PNG or EPS QR codes from your terminal or CI. ## With an account [#with-an-account] - [Quickstart](https://qrdx.dev/docs/developers/quickstart): Your first API request in under two minutes, then dynamic codes with a token. - [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api): Create and edit dynamic codes, read scans and report conversions over HTTP. - [API reference](https://qrdx.dev/docs/developers/api): Every endpoint, with samples in curl, JavaScript and Python, and a Try it panel. - [Webhooks](https://qrdx.dev/docs/webhooks): Signed events for scans, code changes and form responses. Works with Zapier, Make and n8n. - [Build an integration](https://qrdx.dev/docs/developers/build-an-integration): Let people connect their QRdx account to your app with OAuth 2.0 and PKCE. - [Errors](https://qrdx.dev/docs/developers/errors): Every error the API returns, what it means and how to fix it. ## In your own code [#in-your-own-code] - [React library](https://qrdx.dev/docs/getting-started): Render and export styled QR codes with the qrdx package. - [Self-hosting](https://qrdx.dev/docs/self-hosting): Run the whole app, dynamic codes included, on your own servers. ## Base URL and authentication [#base-url-and-authentication] The REST API lives at `https://api.qrdx.dev/v1`. The same routes also answer at `https://qrdx.dev/api/v1`. Requests authenticate with an OAuth 2.0 access token. There are no API keys. The [quickstart](https://qrdx.dev/docs/developers/quickstart#get-an-access-token) shows how to get a token. ## Stay up to date [#stay-up-to-date] Library, CLI and API changes are in the [changelog](https://qrdx.dev/changelogs). The source is on [GitHub](https://github.com/bucharitesh/qrdx). --- # Quickstart (Developers) URL: https://qrdx.dev/docs/developers/quickstart Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/quickstart.mdx Your first QRdx API request in under two minutes, with no key. Then dynamic codes with an access token. The QRdx API has two halves. Images need nothing: no account, no key. Dynamic codes, scans and webhooks belong to an account, so they need an access token. ## 1. Make a QR code (no key) [#1-make-a-qr-code-no-key] Run this. It saves a PNG of a QR code that opens qrdx.dev: **curl** ```bash curl "https://api.qrdx.dev/v1/qr?data=https://qrdx.dev&format=png" -o qr.png ``` **JavaScript** ```js // Node 18 or later (save as qr.mjs) import { writeFile } from "node:fs/promises"; const url = new URL("https://api.qrdx.dev/v1/qr"); url.searchParams.set("data", "https://qrdx.dev"); url.searchParams.set("format", "png"); const response = await fetch(url); await writeFile("qr.png", Buffer.from(await response.arrayBuffer())); ``` **Python** ```python # pip install requests import requests response = requests.get( "https://api.qrdx.dev/v1/qr", params={"data": "https://qrdx.dev", "format": "png"}, ) response.raise_for_status() open("qr.png", "wb").write(response.content) ``` That's a working API call. The URL itself is the image, so it also works straight in an `` tag: ```html QR code for qrdx.dev ``` Change the colors, shapes, size and format with query parameters, or try them in the browser on the [Render a QR code image](https://qrdx.dev/docs/developers/api/get-qr-image) page: its **Try it** panel sends the request and shows the image. Images don't count against any quota; the limit is 300 requests a minute per IP address. ## 2. Get an access token [#get-an-access-token] Everything else in the API acts on an account: create dynamic codes, change where they point, read their scans. Those requests carry an access token in the `Authorization` header. **API keys aren't available yet** Today, the only way to get a token is to register an OAuth app and sign in through it once. It takes about five minutes the first time. When API keys ship, this whole step becomes "create a key in Settings → Developers", and every request below works unchanged with the key in place of the token. [API keys](#api-keys) below will say when. 1. **Register an app.** In [Settings → Developers](https://qrdx.dev/settings/developers), choose **New app**. Give it a name, add the redirect URI `http://localhost:8787/callback`, and select the scopes `codes:read`, `codes:write` and `profile:read`. Copy the client ID and the client secret (the secret is shown once). 2. **Sign in through it.** Save this script as `get-token.mjs`. It runs the OAuth flow with PKCE against your own account and prints your tokens. It needs Node 18 or later and nothing else: ```js title="get-token.mjs" import { createHash, randomBytes } from "node:crypto"; import { createServer } from "node:http"; const clientId = process.env.QRDX_CLIENT_ID; const clientSecret = process.env.QRDX_CLIENT_SECRET; const redirectUri = "http://localhost:8787/callback"; const verifier = randomBytes(32).toString("base64url"); const challenge = createHash("sha256").update(verifier).digest("base64url"); const state = randomBytes(16).toString("hex"); const authorize = new URL("https://qrdx.dev/api/oauth/authorize"); authorize.search = new URLSearchParams({ response_type: "code", client_id: clientId, redirect_uri: redirectUri, scope: "codes:read codes:write profile:read", state, code_challenge: challenge, code_challenge_method: "S256", }); console.log(`Open this URL and approve your app:\n\n${authorize}\n`); const server = createServer(async (req, res) => { const url = new URL(req.url, redirectUri); if (url.pathname !== "/callback") return res.writeHead(404).end(); const code = url.searchParams.get("code"); if (!code || url.searchParams.get("state") !== state) { res.writeHead(400).end(url.searchParams.get("error") ?? "Bad callback"); return; } const response = await fetch("https://qrdx.dev/api/oauth/token", { method: "POST", body: new URLSearchParams({ grant_type: "authorization_code", client_id: clientId, client_secret: clientSecret, code, redirect_uri: redirectUri, code_verifier: verifier, }), }); const tokens = await response.json(); res.end(response.ok ? "Done: back to your terminal." : "Failed: see your terminal."); server.close(); console.log(tokens); }); server.listen(8787); ``` ```bash QRDX_CLIENT_ID=... QRDX_CLIENT_SECRET=... node get-token.mjs ``` Open the URL it prints, approve your app, and the terminal shows `access_token` and `refresh_token`. 3. **Keep it handy.** Export the access token for the next step: ```bash export QRDX_TOKEN=... ``` Access tokens last one hour; the refresh token gets you a new one for 30 days ([refreshing tokens](https://qrdx.dev/docs/developers/rest-api#4-refresh-and-revoke)). Requests made with a token act on your personal workspace and count against its daily quota (1,000 requests on Free). ## 3. Create a dynamic code [#3-create-a-dynamic-code] A dynamic code is a short link you can point somewhere else after it's printed: **curl** ```bash curl -X POST https://api.qrdx.dev/v1/codes \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://example.com", "title": "My first code"}' ``` **JavaScript** ```js const response = await fetch("https://api.qrdx.dev/v1/codes", { method: "POST", headers: { Authorization: `Bearer ${process.env.QRDX_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ destinationUrl: "https://example.com", title: "My first code", }), }); const { data: code } = await response.json(); console.log(code.shortLink); ``` **Python** ```python import os import requests response = requests.post( "https://api.qrdx.dev/v1/codes", headers={"Authorization": f"Bearer {os.environ['QRDX_TOKEN']}"}, json={"destinationUrl": "https://example.com", "title": "My first code"}, ) response.raise_for_status() code = response.json()["data"] print(code["shortLink"]) ``` The response is `201 Created` with the new code: ```json { "data": { "id": "cm5code0spring0menu00001", "slug": "aB3dE5f", "shortLink": "https://qrdx.dev/q/aB3dE5f", "destinationUrl": "https://example.com/", "title": "My first code", "status": "active", "scanCount": 0 } } ``` (Shortened: [Create codes](https://qrdx.dev/docs/developers/api/create-codes) shows every field.) The QR code encodes `shortLink`. Render it with the image API from step 1, `https://api.qrdx.dev/v1/qr?data=https://qrdx.dev/q/aB3dE5f`, and print it. ## 4. Point it somewhere else [#4-point-it-somewhere-else] Change the destination at any time; the printed code keeps working: ```bash curl -X PATCH https://api.qrdx.dev/v1/codes/$CODE_ID \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://example.com/new-page"}' ``` Scan the code once, then read its analytics with `GET /v1/codes/$CODE_ID/scans` ([Get scan analytics](https://qrdx.dev/docs/developers/api/get-code-scans)). ## When something goes wrong [#when-something-goes-wrong] Every error is JSON with a stable `code` and a message that says what to do: ```json { "code": "UNAUTHORIZED", "message": "Missing access token. Send an OAuth access token as \"Authorization: Bearer \". …", "status": 401 } ``` The [errors page](https://qrdx.dev/docs/developers/errors) lists every code with its fix. ## API keys [#api-keys] API keys don't exist yet. Until they do, use an OAuth access token as in [step 2](#get-an-access-token); they authenticate the same requests. When keys ship, this section will show how to create one, and the `Authorization: Bearer` header will take either. ## Next steps [#next-steps] - [API reference](https://qrdx.dev/docs/developers/api): Every endpoint, with samples in curl, JavaScript and Python and a Try it panel. - [Errors](https://qrdx.dev/docs/developers/errors): Every error code, what it means and how to fix it. - [Webhooks](https://qrdx.dev/docs/webhooks): Get scans and new codes pushed to your server (Business). - [Rate limits](https://qrdx.dev/docs/developers/rest-api#rate-limits): Daily quotas per plan and the `X-RateLimit-*` headers. --- # REST API and OAuth (Developers) URL: https://qrdx.dev/docs/developers/rest-api Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/rest-api.mdx Manage dynamic QR codes, read scan analytics, report conversions and subscribe to webhooks over HTTP with OAuth 2.0 access tokens. The QRdx REST API lives under `https://api.qrdx.dev/v1`. It uses JSON and, apart from the keyless [image API](https://qrdx.dev/docs/developers/image-api), authenticates requests with an OAuth 2.0 access token: ```bash curl https://api.qrdx.dev/v1/codes -H "Authorization: Bearer $QRDX_TOKEN" ``` Requests made with a token act on the token owner's personal workspace. The codes, conversions and hooks endpoints also accept the browser session of someone signed in to QRdx; those requests act on the open [team](https://qrdx.dev/docs/teams). The same endpoints also answer under `https://qrdx.dev/api/v1`. Browser-session requests use that path, since the sign-in cookie belongs to qrdx.dev, and a [self-hosted](https://qrdx.dev/docs/self-hosting) instance serves the API at `/api/v1` on its own domain. New here? The [quickstart](https://qrdx.dev/docs/developers/quickstart) takes you from nothing to a first request. Every endpoint has its own page, with request and response schemas, samples in curl, JavaScript and Python, and a Try it panel, in the [API reference](https://qrdx.dev/docs/developers/api), generated from the [OpenAPI document](https://qrdx.dev/openapi.json). ## Endpoints [#endpoints] | Endpoint | Scope | What it does | | --- | --- | --- | | `GET /v1/qr` | None | QR code image as SVG, PNG or EPS. See [Image API](https://qrdx.dev/docs/developers/image-api) | | `/v1/codes` | `codes:read`, `codes:write` | List, create (one or up to 100 at a time, with an optional custom back-half in `slug`), update and delete dynamic codes. See [Dynamic QR codes](https://qrdx.dev/docs/dynamic-qr-codes#rest-api) | | `GET /v1/codes/{id}/scans` | `codes:read` | Scan analytics for a code, or individual scans page by page | | `POST /v1/conversions` | `codes:write` | Attribute a sale or sign-up to the scan that led to it (Business). See [Tracking and conversions](https://qrdx.dev/docs/tracking-and-conversions) | | `/v1/hooks` | `codes:read`, `codes:write` | Subscribe a URL to events, REST Hooks style, for Zapier, Make and n8n. See [Webhooks](https://qrdx.dev/docs/webhooks) | | `GET /v1/themes`, `GET /v1/themes/{id}` | `themes:read` | Your saved QR themes (name and style) | | `GET /v1/me` | `profile:read` | The signed-in user's id, name, email and avatar | `codes:write` also allows everything `codes:read` does. ## Get an access token [#get-an-access-token] QRdx is an OAuth 2.0 provider. Your app sends people to QRdx to sign in, and gets back an access token that can only do what the granted scopes allow. | Scope | Allows | | --- | --- | | `profile:read` | Read the user's name, email and avatar | | `themes:read` | Read the user's saved QR themes | | `codes:read` | Read dynamic codes and their scan analytics | | `codes:write` | Create, edit and delete dynamic codes, report conversions, manage webhook subscriptions | ### 1. Register an app [#1-register-an-app] Create an OAuth app yourself in [Settings → Developers](https://qrdx.dev/settings/developers): give it a name, its redirect URIs and the scopes it needs, and you get a client ID and a client secret. The secret is shown once; keep it on your server. Free workspaces can have 1 app, Pro 5 and Business 25. [Build an integration](https://qrdx.dev/docs/developers/build-an-integration) walks through the whole flow, from creating the app to getting it listed in the integrations directory. On a [self-hosted](https://qrdx.dev/docs/self-hosting) instance you can also create a first-party app from `apps/docs`: ```bash pnpm exec tsx scripts/create-oauth-app.ts \ --name "My app" \ --redirect-uris "https://myapp.example/callback" \ --scopes "codes:read,codes:write" ``` ### 2. Send the user to QRdx [#2-send-the-user-to-qrdx] ```txt https://qrdx.dev/api/oauth/authorize ?response_type=code &client_id= &redirect_uri=https://myapp.example/callback &scope=codes:read codes:write &state= &code_challenge= &code_challenge_method=S256 ``` `redirect_uri` must exactly match a registered URL, and `scope` (space or comma separated) may only ask for the app's scopes. PKCE is recommended, and required for apps that turn on **Require PKCE**. `S256` is the only method: a `plain` challenge gets `invalid_request`. People sign in if needed, then approve your app on the QRdx consent screen, which shows its name, logo, website, who built it and the requested permissions. QRdx then redirects to `redirect_uri` with `code` and your `state`, or with `error=access_denied` if they cancel. The code is valid for 10 minutes and can be used once. ### 3. Exchange the code for tokens [#3-exchange-the-code-for-tokens] ```bash curl -X POST https://qrdx.dev/api/oauth/token \ -d grant_type=authorization_code \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET \ -d code=$CODE \ -d redirect_uri=https://myapp.example/callback \ -d code_verifier=$CODE_VERIFIER ``` ```json { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "scope": "codes:read codes:write" } ``` The token endpoint takes a form-encoded body. Access tokens last 1 hour and refresh tokens 30 days. ### 4. Refresh and revoke [#4-refresh-and-revoke] ```bash # New access and refresh tokens; the old refresh token stops working curl -X POST https://qrdx.dev/api/oauth/token \ -d grant_type=refresh_token \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET \ -d refresh_token=$REFRESH_TOKEN # Revoke an access or refresh token curl -X POST https://qrdx.dev/api/oauth/revoke -d token=$TOKEN ``` `GET /api/oauth/userinfo` returns the user as `sub`, `name`, `email` and `picture` for tokens with `profile:read`. People can revoke an app's access themselves in **Settings → Authorized apps**. Its tokens for that account then fail with `invalid_token` (API calls) and `invalid_grant` (refresh), and the app has to ask again. ## Errors and limits [#errors-and-limits] Every `/v1` endpoint returns errors as `{ "code": "...", "message": "...", "status": 400 }`, with a `docs` link when a page explains the fix; the [errors page](https://qrdx.dev/docs/developers/errors) lists every code with its fix. An unknown path gets `404 NOT_FOUND`. `/v1/me` and `/v1/themes` also include the OAuth fields they have always sent, `"error": "invalid_token"` and `"error_description"`, and the OAuth endpoints (`/api/oauth/*`) use only the OAuth shape. Creating several codes in one request, choosing a code's `slug` and moving codes onto a custom domain need Pro or Business (`402 PLAN_REQUIRED` otherwise). Scans are never limited. ### Rate limits [#rate-limits] Two kinds of limit apply. Both answer `429 RATE_LIMITED` with a `Retry-After` header, the number of seconds to wait. - **Daily quota.** A workspace can make 1,000 API requests a day on Free, 10,000 on Pro and 100,000 on Business (see [Plans and limits](https://qrdx.dev/docs/plans-and-limits)). The day runs from 00:00 to 24:00 UTC. Every authenticated request counts. Requests made with an access token count against the token owner's personal workspace and its plan. Requests made with a browser session count against the open team, or the personal workspace. - **Per-minute limits.** The codes, conversions and hooks endpoints allow 120 reads and 30 writes a minute per user, and 300 requests a minute per IP address. Requests turned away before the daily check don't count: an invalid token (`401`), a token without the needed scope (`403`) or a per-minute `429`. Responses to requests that count carry the daily quota in three headers, on successes and errors alike: | Header | Value | | --- | --- | | `X-RateLimit-Limit` | Requests a day on the workspace's plan | | `X-RateLimit-Remaining` | Requests left today | | `X-RateLimit-Reset` | When the count starts over (the next 00:00 UTC), in Unix seconds | The `X-RateLimit-*` headers always describe the daily quota. A per-minute `429` has a `Retry-After` header only. Over the daily quota you get: ```http HTTP/1.1 429 Too Many Requests Retry-After: 43200 X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1793577600 ``` ```json { "code": "RATE_LIMITED", "message": "Daily API limit reached: the Free plan allows 1,000 requests a day. It resets at 00:00 UTC. Upgrade your plan for a higher limit.", "status": 429 } ``` `/v1/me` and `/v1/themes` send the same headers and body, plus `"error": "rate_limited"` and `"error_description"`. The [image API](https://qrdx.dev/docs/developers/image-api), the [MCP server](https://qrdx.dev/docs/developers/mcp) and conversions reported from the browser with a `qrdx_sid` need no token, so they don't use the daily quota. They have per-IP limits of their own. A [self-hosted](https://qrdx.dev/docs/self-hosting) instance enforces rate limits only when Upstash Redis is configured. --- # API reference (Developers) URL: https://qrdx.dev/docs/developers/api Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/index.mdx Every QRdx REST API endpoint, with code samples in curl, JavaScript and Python and a Try it panel that sends real requests. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Every endpoint of the QRdx REST API, generated from its [OpenAPI 3.1 document](https://qrdx.dev/openapi.json), which is built from the same schemas the API validates requests with. Each page has code samples in curl, JavaScript and Python, and a **Try it** panel that sends real requests. New to the API? The [quickstart](https://qrdx.dev/docs/developers/quickstart) gets you from nothing to a first request in under two minutes. ## Base URL [#base-url] ```txt https://api.qrdx.dev/v1 ``` The same routes also answer at `https://qrdx.dev/api/v1`. Requests and responses are JSON, except the image API, which returns images. ## Try it [#try-it] Try it sends real requests from your browser to this site's `/api/v1` (the same API as `api.qrdx.dev/v1`), and never with your QRdx sign-in cookie: - **No token needed:** [Render a QR code image](https://qrdx.dev/docs/developers/api/get-qr-image) works straight away. - **Everything else** needs an OAuth access token. Paste it into the **Authorization** field as `Bearer `. [How to get a token](https://qrdx.dev/docs/developers/quickstart#get-an-access-token). Requests made with a token act on real data in the token owner's personal workspace and count against its daily quota, exactly as they would from your own code. ## Authentication [#authentication] Send an OAuth 2.0 access token in the `Authorization` header. Each endpoint below lists the scope it needs; `codes:write` also allows everything `codes:read` does. API keys aren't available yet, so for now you get a token by registering an OAuth app: see [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api#get-an-access-token). ## Errors and limits [#errors-and-limits] Errors are JSON: `{ "code", "message", "status" }`, plus `docs` when a page explains the fix. The [errors page](https://qrdx.dev/docs/developers/errors) lists every code with its fix. Each workspace has a daily request quota, reported in the `X-RateLimit-*` headers; see [rate limits](https://qrdx.dev/docs/developers/rest-api#rate-limits). ## Endpoints [#endpoints] ### QR images [#qr-images] Keyless QR code images for `` tags, emails and scripts. | Endpoint | Request | Scope | | --- | --- | --- | | [Render a QR code image](https://qrdx.dev/docs/developers/api/get-qr-image) | `GET /qr` | None | ### Codes [#codes] Dynamic QR codes: a short link you can point anywhere after printing. | Endpoint | Request | Scope | | --- | --- | --- | | [List codes](https://qrdx.dev/docs/developers/api/list-codes) | `GET /codes` | `codes:read` | | [Create codes](https://qrdx.dev/docs/developers/api/create-codes) | `POST /codes` | `codes:write` | | [Get a code](https://qrdx.dev/docs/developers/api/get-code) | `GET /codes/{id}` | `codes:read` | | [Update a code](https://qrdx.dev/docs/developers/api/update-code) | `PATCH /codes/{id}` | `codes:write` | | [Delete a code](https://qrdx.dev/docs/developers/api/delete-code) | `DELETE /codes/{id}` | `codes:write` | ### Scans [#scans] Scan analytics of a code. | Endpoint | Request | Scope | | --- | --- | --- | | [Get scan analytics](https://qrdx.dev/docs/developers/api/get-code-scans) | `GET /codes/{id}/scans` | `codes:read` | ### Conversions [#conversions] Sales and sign-ups attributed to the scan behind them. | Endpoint | Request | Scope | | --- | --- | --- | | [Report a conversion](https://qrdx.dev/docs/developers/api/create-conversion) | `POST /conversions` | `codes:write` | ### Hooks [#hooks] REST Hooks subscriptions for Zapier, Make and n8n. | Endpoint | Request | Scope | | --- | --- | --- | | [List hooks](https://qrdx.dev/docs/developers/api/list-hooks) | `GET /hooks` | `codes:read` | | [Subscribe to events](https://qrdx.dev/docs/developers/api/create-hook) | `POST /hooks` | `codes:write` | | [Get a hook](https://qrdx.dev/docs/developers/api/get-hook) | `GET /hooks/{id}` | `codes:read` | | [Unsubscribe](https://qrdx.dev/docs/developers/api/delete-hook) | `DELETE /hooks/{id}` | `codes:write` | | [Get sample events](https://qrdx.dev/docs/developers/api/get-hook-samples) | `GET /hooks/sample` | `codes:read` | ### Themes [#themes] The user's saved QR designs. | Endpoint | Request | Scope | | --- | --- | --- | | [List themes](https://qrdx.dev/docs/developers/api/list-themes) | `GET /themes` | `themes:read` | | [Get a theme](https://qrdx.dev/docs/developers/api/get-theme) | `GET /themes/{themeId}` | `themes:read` | ### Profile [#profile] The user behind the token. | Endpoint | Request | Scope | | --- | --- | --- | | [Get the token's user](https://qrdx.dev/docs/developers/api/get-profile) | `GET /me` | `profile:read` | ## OpenAPI document [#openapi-document] Download the document at [`https://qrdx.dev/openapi.json`](https://qrdx.dev/openapi.json) (also served at `https://api.qrdx.dev/openapi.json`) to generate a client, import the API into Postman or Insomnia, or give an AI agent the full contract. --- # Render a QR code image (Developers) URL: https://qrdx.dev/docs/developers/api/get-qr-image Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-qr-image.mdx A QR code image as SVG (default), PNG or EPS, with no token. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} **No token needed**, and it doesn't use your daily quota: put the URL straight into an `` tag. The same query always returns the same bytes, so responses are cached for a year. Colors are hex without `#` (or with `#` encoded as `%23`). Limit: 300 requests a minute per IP address. More in the [Image API guide](https://qrdx.dev/docs/developers/image-api). --- # List codes (Developers) URL: https://qrdx.dev/docs/developers/api/list-codes Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/list-codes.mdx The dynamic codes of the token owner's personal workspace, newest first. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Page through them with `limit` and `offset`; `pagination.total` counts every match. --- # Create codes (Developers) URL: https://qrdx.dev/docs/developers/api/create-codes Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/create-codes.mdx Creates one dynamic code, or up to 100 at once. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Send an object for one code, or an array for several (Pro). The response's `shortLink` is what the QR code encodes: point it somewhere else later with PATCH and the printed code keeps working. Render it with the image API, for example `https://api.qrdx.dev/v1/qr?data=`. Free workspaces have 10 active codes. Create requests are limited to 20 a minute. --- # Get a code (Developers) URL: https://qrdx.dev/docs/developers/api/get-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-code.mdx One dynamic code of the token owner's personal workspace. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} --- # Update a code (Developers) URL: https://qrdx.dev/docs/developers/api/update-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/update-code.mdx Changes a code's destination, title, design, domain or back-half, or archives it. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Send only the fields to change. `archived: true` stops redirects and frees a plan slot; `false` restores the code. The destination, title and design stay editable on every plan. --- # Delete a code (Developers) URL: https://qrdx.dev/docs/developers/api/delete-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/delete-code.mdx Permanently deletes a code and its scan history. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Its short link stops working, and this can't be undone. To pause a code and keep its scans, archive it with PATCH instead. --- # Get scan analytics (Developers) URL: https://qrdx.dev/docs/developers/api/get-code-scans Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-code-scans.mdx Scan analytics of a code for a range of days. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Totals, a daily series and the top countries, cities, devices, systems, browsers and referrers. The range is clamped to how long the plan keeps analytics. With `limit` above 0 the response also lists individual scans, newest first: pass the response's `nextBefore` as `before` for the next page. --- # Report a conversion (Developers) URL: https://qrdx.dev/docs/developers/api/create-conversion Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/create-conversion.mdx Attributes a sale, sign-up or other conversion to the scan that brought the visitor (Business). {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} From your server, send a token and the scan's `sid`, or the code id as `code`. From the browser, `conversions.js` sends `sid` with no token, for codes with conversion tracking turned on. An `id`, such as your order id, makes retries safe: a repeat returns 200 with `duplicate: true`. More in [Tracking and conversions](https://qrdx.dev/docs/tracking-and-conversions). --- # List hooks (Developers) URL: https://qrdx.dev/docs/developers/api/list-hooks Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/list-hooks.mdx The workspace's hooks: REST Hooks subscriptions and webhook endpoints. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} `target_url` shows only the origin unless the token has `codes:write`. Listing works on every plan; subscribing needs Business. --- # Subscribe to events (Developers) URL: https://qrdx.dev/docs/developers/api/create-hook Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/create-hook.mdx Subscribes a URL to events, the REST Hooks way Zapier, Make and n8n call it (Business). {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} QRdx posts each event to the URL, signed with the `secret` in the response, which is shown only once ([verifying signatures](https://qrdx.dev/docs/webhooks)). Send the URL as `target_url` (or `targetUrl`, `hookUrl`, `hook_url` or `url`) and one `event` or several `events`. --- # Get a hook (Developers) URL: https://qrdx.dev/docs/developers/api/get-hook Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-hook.mdx One hook of the workspace. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} --- # Unsubscribe (Developers) URL: https://qrdx.dev/docs/developers/api/delete-hook Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/delete-hook.mdx Unsubscribes a hook: events stop at once. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} This is the REST Hooks unsubscribe Zapier calls when a Zap is turned off. It works on every plan. --- # Get sample events (Developers) URL: https://qrdx.dev/docs/developers/api/get-hook-samples Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-hook-samples.mdx Sample payloads of an event, shaped exactly like real deliveries. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Zapier's “perform list”: up to three payloads built from the workspace's recent data, newest first, or a fixed example when it has none. Use it to map fields while building a Zap. --- # List themes (Developers) URL: https://qrdx.dev/docs/developers/api/list-themes Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/list-themes.mdx The user's saved QR themes: each one's name and design. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Tokens only: a browser session gets `401`. --- # Get a theme (Developers) URL: https://qrdx.dev/docs/developers/api/get-theme Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-theme.mdx One of the user's saved themes. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} --- # Get the token's user (Developers) URL: https://qrdx.dev/docs/developers/api/get-profile Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/api/get-profile.mdx The id, name, email and avatar of the user who authorized the token. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi, then run it again. */} Tokens only: a browser session gets `401`. --- # Errors (Developers) URL: https://qrdx.dev/docs/developers/errors Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/errors.mdx Every error the QRdx API returns, with its HTTP status, what it means and how to fix it. {/* Generated by pnpm openapi:generate from public\/openapi.json. Edit lib\/openapi\/errors.ts, then run it again. */} Every REST API error is JSON with the same shape, whichever endpoint returns it: ```json { "code": "INSUFFICIENT_SCOPE", "message": "This token needs the 'codes:write' scope. Add it to your app and ask for it when the user authorizes.", "status": 403, "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints" } ``` | Field | What it is | | --- | --- | | `code` | A stable, machine-readable code from the table below. Branch on this. | | `message` | What went wrong and how to fix it, for people. The wording may change. | | `status` | The HTTP status, repeated. | | `docs` | A page that explains the fix, when there is one. | | `data` | Extra detail, on some errors. `VALIDATION_ERROR` lists each problem in `data.details`. | A validation error names every field that failed: ```json { "code": "VALIDATION_ERROR", "message": "Invalid request body", "status": 400, "data": { "details": [ { "path": "destinationUrl", "message": "Enter a full URL, starting with https://" } ] } } ``` The image API reports query parameters as `{ "param", "message" }` instead of `{ "path", "message" }`. ## Error codes [#error-codes] | Status | Code | Meaning | Fix | | --- | --- | --- | --- | | 400 | `VALIDATION_ERROR` | The body, query or path failed validation. `data.details` lists each problem as `{ path, message }` (`{ param, message }` on the image API). | Correct the fields listed in `data.details` and send the request again. | | 400 | `INVALID_SLUG` | The custom back-half (`slug`) breaks a rule: a reserved word, or the same back-half twice in one request. | Choose another back-half. The message names the rule. | | 400 | `TOO_MANY_CODES` | More than 100 codes in one request. The body schema normally rejects this first, as `VALIDATION_ERROR`. | Send at most 100 codes per request. | | 400 | `DOMAIN_NOT_VERIFIED` | `domainId` points to a custom domain that isn't verified yet. | Finish verifying the domain on the Domains page, or leave `domainId` out. | | 400 | `SID_REQUIRED` | A conversion without the `qrdx_sid` of the scan (from the browser), or without `sid` or `code` (from a server). | Send the `qrdx_sid` the short link added to your page URL, or, with a token, the code id as `code`. | | 401 | `UNAUTHORIZED` | No credentials: the request has no `Authorization: Bearer` header (and no QRdx browser session). | Send an OAuth access token as `Authorization: Bearer `. | | 401 | `INVALID_TOKEN` | The access token is unknown, expired (tokens last one hour) or revoked. | Get a new access token with your refresh token, or send the user through authorization again. | | 402 | `PLAN_REQUIRED` | The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business. | Upgrade the workspace, or leave the feature out of the request. | | 402 | `LIMIT_REACHED` | The workspace has as many active dynamic codes as its plan includes (10 on Free). | Archive a code (archived codes don't count) or upgrade to Pro for unlimited codes. | | 403 | `INSUFFICIENT_SCOPE` | The token is valid but lacks the scope this endpoint needs. | Add the scope named in the message to your app and ask for it when the user authorizes. | | 403 | `FORBIDDEN` | The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site. | Ask a team owner or admin, or call the API with an access token from your server. | | 403 | `CONVERSIONS_OFF` | A browser conversion for a code that doesn't have conversion tracking turned on. | Turn on conversion tracking for the code, or report the conversion from your server with a token. | | 403 | `LIMIT_REACHED` | The workspace already has 20 webhook endpoints and hooks. | Delete an endpoint or hook you no longer use. | | 404 | `NOT_FOUND` | No such endpoint, or the code, hook, theme or custom domain doesn't exist in the token's workspace. | Check the path and the id. Tokens act on the token owner's personal workspace, not on teams. | | 404 | `INVALID_SID` | The `qrdx_sid` is unknown or older than 30 days, the attribution window. | Send the `qrdx_sid` of a recent scan, or report the conversion by code id from your server. | | 409 | `SLUG_TAKEN` | Another code already uses that back-half on that domain. | Choose another back-half. | | 413 | `PAYLOAD_TOO_LARGE` | The body is over the endpoint's limit: 4 MB for codes, 16 KB for hooks, 8 KB for conversions. | Send a smaller body. For codes, a large inline logo is the usual cause. | | 415 | `UNSUPPORTED_MEDIA_TYPE` | A write without `Content-Type: application/json`. | Send a JSON body with `Content-Type: application/json`. | | 422 | `UNSAFE_DESTINATION` | Google Web Risk flags the destination URL (malware, phishing or unwanted software). | Use another destination. If the flag is wrong, ask Google to review the site. | | 429 | `RATE_LIMITED` | Too many requests: a per-minute limit (only `Retry-After` is set) or the workspace's daily quota (`Retry-After` plus `X-RateLimit-*`). | Wait `Retry-After` seconds. For the daily quota, wait for 00:00 UTC or upgrade the plan. | | 500 | `INTERNAL_ERROR` | Something failed on QRdx's side. It has been logged. | Try again in a moment. If it keeps happening, report it with the time of the request. | | 503 | `SLUG_UNAVAILABLE` | No free short-link slug could be reserved in time. | Send the same request again. | | 503 | `UNAVAILABLE` | Custom domains aren't set up on this deployment (a self-hosted instance without them). | Leave `domainId` out, or configure custom domains on your instance. | ## Retrying [#retrying] - **`429`:** wait the number of seconds in `Retry-After`, then send the same request. When the daily quota runs out, the `X-RateLimit-*` headers say how many requests the plan allows and when the count resets (00:00 UTC). See [rate limits](https://qrdx.dev/docs/developers/rest-api#rate-limits). - **`500` and `503`:** retry with exponential backoff, starting at about a second. - **Other `4xx`:** the same request fails the same way. Fix it first. Creating codes isn't idempotent: if a `POST /v1/codes` times out, list your codes before sending it again, or you may create the code twice. Conversions are: send your own `id` and a repeat returns `200` with `duplicate: true`. ## `/v1/me` and `/v1/themes` [#v1me-and-v1themes] These two endpoints send the same body plus the OAuth fields they have always sent, `error` and `error_description` (the same text as `message`), so older clients keep working: | `error` | `code` | | --- | --- | | `invalid_token` | UNAUTHORIZED or INVALID_TOKEN | | `insufficient_scope` | INSUFFICIENT_SCOPE | | `rate_limited` | RATE_LIMITED (daily quota) | | `not_found` | NOT_FOUND (themes) | ## OAuth endpoints [#oauth-endpoints] The OAuth endpoints under `https://qrdx.dev/api/oauth` (authorize, token, revoke, userinfo) follow the OAuth 2.0 standard instead: `{ "error", "error_description" }`, with `error` one of `invalid_request`, `invalid_client`, `invalid_grant`, `unsupported_grant_type`, `invalid_token` or `access_denied`. Getting `invalid_grant` on a refresh means the refresh token was already used (each refresh returns a new one), expired after 30 days, or the user revoked your app: send the user through authorization again. See [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api#get-an-access-token). ## Unknown endpoints [#unknown-endpoints] A path under `/v1` that doesn't exist returns `404 NOT_FOUND` as JSON, never an HTML page. The [API reference](https://qrdx.dev/docs/developers/api) lists every endpoint. --- # Webhooks URL: https://qrdx.dev/docs/webhooks Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/webhooks.mdx Get a signed HTTPS request when your QR codes are scanned, created or changed, or when someone submits a form. Works with Zapier, Make and n8n. Webhooks send events from a workspace to a URL you control, as they happen. Use them to update a CRM when someone fills in a form, post to an internal tool when a code passes 1,000 scans, or keep your own copy of scan data. Webhooks, Zapier/Make hooks and Slack/Discord alerts are part of the **Business** plan. Email alerts and the weekly digest are part of Pro; see [Notifications](https://qrdx.dev/docs/notifications). ## Add an endpoint [#add-an-endpoint] 1. Open [Dashboard → Webhooks](https://qrdx.dev/dashboard/webhooks) in the workspace whose events you want (your personal workspace or a team). 2. Click **Add endpoint**, enter an `https://` URL and pick the events. 3. Copy the **signing secret** shown after saving. Owners and admins can show it again, or rotate it, later. 4. Click **Send test**. QRdx sends a signed `ping` event and shows the response status. In a team, owners and admins manage endpoints; members can see the endpoints and their delivery logs. A workspace can have up to 20 endpoints. Endpoint URLs must be public `https://` host names. IP addresses, `localhost` and names that resolve to private networks are refused, and redirects are not followed. ## Events [#events] | Event | When | Notes | | --- | --- | --- | | `scan.created` | A dynamic code is scanned | Batched: at most one request per minute per endpoint, sent within a few minutes, with up to 100 scans and the exact `count` | | `scan.milestone` | A code's 1st scan, then 10, 100, 500, 1,000, 5,000, 10,000, 50,000, 100,000, 500,000 and 1,000,000 scans, then every million | Fires once per milestone | | `code.created` | A dynamic code is created (dashboard or API) | One event per code, also for bulk creation | | `code.updated` | A code's destination, title, design or short-link domain changes | `changes` lists the fields | | `form.submitted` | Someone submits a form on a hosted page | Includes the answers | | `ping` | You click **Send test** | | Scans by bots, link previews and prefetchers, and rapid repeats from one visitor, are not counted and don't trigger events, exactly as in [scan analytics](https://qrdx.dev/docs/dynamic-qr-codes). ## Payloads [#payloads] Every request is a `POST` with a JSON body and these headers: | Header | Value | | --- | --- | | `Content-Type` | `application/json` | | `User-Agent` | `QRdx-Webhooks/1.0 (+https://qrdx.dev/docs/webhooks)` | | `X-QRdx-Event` | The event type, e.g. `code.created` | | `X-QRdx-Delivery` | Delivery id: the same on every retry, so you can ignore duplicates | | `X-QRdx-Timestamp` | Unix seconds when this attempt was signed | | `X-QRdx-Signature` | `t=,v1=` | The body is always an envelope: ```json { "id": "evt_cm3x8c1a20001", "type": "code.created", "created_at": "2026-10-31T09:30:00.000Z", "data": { "code": { "id": "cm3x7q2l40000", "slug": "aB3dE5f", "title": "Spring menu", "short_link": "https://qrdx.dev/q/aB3dE5f", "destination_url": "https://example.com/menu", "status": "active", "scan_count": 0, "created_at": "2026-10-31T09:30:00.000Z", "updated_at": "2026-10-31T09:30:00.000Z" }, "actor_id": "user_123" } } ``` A `scan.created` batch: ```json { "id": "evt_cm3x9d0b10002", "type": "scan.created", "created_at": "2026-10-31T09:31:00.000Z", "data": { "window_started_at": "2026-10-31T09:31:00.000Z", "window_seconds": 60, "count": 2, "scans": [ { "scan_id": "cm3x9d0b00001", "code": { "id": "cm3x7q2l40000", "slug": "aB3dE5f", "title": "Spring menu" }, "scanned_at": "2026-10-31T09:31:00.000Z", "country": "DE", "region": "BE", "city": "Berlin", "device_type": "mobile", "os": "iOS", "browser": "Safari", "referer": null } ] } } ``` When more than 100 scans arrive in one minute, `scans` holds the first 100 and `count` the exact total. Scan payloads never include IP addresses or visitor hashes. Other events carry: - `scan.milestone`: `milestone`, `code` (`id`, `slug`, `title`, `scan_count`) and the `scan` that reached it. - `code.updated`: like `code.created`, plus `changes`, e.g. `["destination_url"]`. - `form.submitted`: `response_id`, `page` (`id`, `title`), `code` (`id`, `slug`, or `null`), `fields` (`name`, `label`, `value` as text) and `submitted_at`. ## Verify signatures [#verify-signatures] Compute an HMAC-SHA256 of `.` with your signing secret and compare it with `v1` in `X-QRdx-Signature`. Use the raw request body, before any JSON parsing, and reject timestamps older than five minutes. ```ts import { createHmac, timingSafeEqual } from "node:crypto"; export function isFromQRdx(rawBody: string, header: string, secret: string) { const parts = Object.fromEntries( header.split(",").map((part) => part.trim().split("=", 2)), ); const timestamp = Number(parts.t); if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false; const expected = createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex"); const received = Buffer.from(parts.v1 ?? ""); return ( received.length === expected.length && timingSafeEqual(received, Buffer.from(expected)) ); } ``` Rotating the secret takes effect immediately: the old secret stops verifying, so update your server right after rotating. ## Retries and the delivery log [#retries-and-the-delivery-log] Respond with any `2xx` status within 10 seconds. Anything else (an error status, a redirect, a timeout, an unreachable host) counts as a failure, and QRdx retries with growing waits of at least 1 minute, 4 minutes, 16 minutes, 1 hour, 4 hours, 17 hours and 24 hours. Each retry goes out within a few minutes of its wait ending. That's 8 attempts over roughly two days, after which the delivery is marked **Failed**. Each endpoint's **Deliveries** page lists the last 50 deliveries with their status, attempts, last error and payload. **Retry** re-sends one immediately (also for delivered ones). Deliveries are kept for 30 days. Deliveries can arrive more than once (a retry after a slow response, a manual retry) and out of order. Use `X-QRdx-Delivery` or the envelope `id` to ignore duplicates. Disabling an endpoint stops new deliveries; deleting it also deletes its log. If a workspace's plan no longer includes webhooks, endpoints are kept but nothing is sent until it is on Business again. Your QR codes keep working either way. ## Zapier, Make and n8n (REST Hooks) [#zapier-make-and-n8n-rest-hooks] Automation tools can subscribe and unsubscribe on their own through the REST Hooks API. Authenticate with an OAuth access token, like the [codes API](https://qrdx.dev/docs/dynamic-qr-codes#rest-api): `codes:write` to subscribe or unsubscribe, `codes:read` to list. Hook URLs are often secret, so with `codes:read` alone `target_url` shows only the host (`https://hooks.zapier.com/…`). Token requests act on your personal workspace. Subscriptions are ordinary endpoints: they appear in Dashboard → Webhooks, where you can also remove them. | Method | Path | Description | | --- | --- | --- | | `POST` | `/v1/hooks` | Subscribe. Body: `target_url` (or Zapier's `hookUrl`) and `event` (or an `events` array). Returns `201` with the subscription `id` and its `secret` | | `DELETE` | `/v1/hooks/{id}` | Unsubscribe (`204`) | | `GET` | `/v1/hooks` | List subscriptions and endpoints | | `GET` | `/v1/hooks/{id}` | One subscription | | `GET` | `/v1/hooks/sample?event=` | Recent example payloads for `event`, as an array (Zapier's "perform list") | ```bash curl https://api.qrdx.dev/v1/hooks \ -H "Authorization: Bearer $QRDX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_url": "https://hooks.zapier.com/hooks/standard/123/abc/", "event": "form.submitted"}' ``` ```json { "id": "cm3xa1b2c0003", "target_url": "https://hooks.zapier.com/hooks/standard/123/abc/", "events": ["form.submitted"], "event": "form.submitted", "active": true, "created_at": "2026-10-31T09:40:00.000Z", "secret": "whsec_..." } ``` Without Business the subscribe call returns `402 PLAN_REQUIRED`. A `scan.created` delivery is one batch, so in Zapier use the `data.scans` line items to act on each scan. ## Self-hosting [#self-hosting] Deliveries are sent right away; retries and scan batches whose minute has ended are sent by a scheduled job. QRdx runs it every 5 minutes from a GitHub Actions workflow, which works on Vercel's free Hobby plan, and `apps/docs/vercel.json` also runs it in full once a day. GitHub can start scheduled runs late, so expect retries within a few minutes rather than to the minute. To do the same, copy `.github/workflows/webhook-worker.yml`, add your `CRON_SECRET` as an Actions secret and your job URL as the `WEBHOOK_WORKER_URL` variable. Or call the job from any scheduler with the `CRON_SECRET` you set in the environment: ```bash # every 1-5 minutes curl -H "Authorization: Bearer $CRON_SECRET" https://your-host/api/cron/webhooks ``` With `?source=github` added to the URL, the job first checks a small marker in Upstash Redis and returns `{"skipped": true}` without querying the database when nothing is due, so a database that scales to zero can stay asleep (without Upstash, it always runs the full job). Keep one call a day without it: that run always does the full job and repairs the marker. --- # Build an integration (Developers) URL: https://qrdx.dev/docs/developers/build-an-integration Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/build-an-integration.mdx Create an OAuth app, let people connect their QRdx account with OAuth 2.0 and PKCE, call the REST API on their behalf and get listed in the integrations directory. An integration is an OAuth app. People click **Connect QRdx** in your product, approve your app on the QRdx consent screen, and your server gets an access token that calls `https://api.qrdx.dev/v1` for them, limited to the scopes they approved. This guide covers the whole flow: create an app, send people to QRdx, exchange the code for tokens, call the API, receive events, and get listed. ## 1. Create an OAuth app [#1-create-an-oauth-app] Open [Settings → Developers](https://qrdx.dev/settings/developers) and click **New app**. The app belongs to the workspace that's open: your personal workspace, or the team picked in the sidebar. In a team, only owners can create and change apps; admins and members can see them. | Field | What it's for | | --- | --- | | Name, description, logo, website | Shown on the consent screen. Upload a square logo or paste an `https://` link to one | | Redirect URIs | Where QRdx sends people back to, up to 10. `https://` only, plus `http://localhost` (or `127.0.0.1`) for development. No wildcards or `#fragments`: every request must use one of them exactly | | Scopes | The most your app may request. Each authorization asks for some or all of them | | Require PKCE | Every authorization must send an `S256` code challenge. Recommended, and on by default | After you create the app, QRdx shows its **client ID** and **client secret**. Copy the secret then: QRdx keeps only a hash of it and can't show it again. If you lose it or it leaks, click **Regenerate secret** on the app's page. The old secret stops working at once; people who already connected stay connected. | Plan | OAuth apps per workspace | | --- | --- | | Free | 1 | | Pro | 5 | | Business | 25 | A team uses the best plan among its owners, as for every other [limit](https://qrdx.dev/docs/plans-and-limits). ## 2. Send people to QRdx [#2-send-people-to-qrdx] Generate a random `state` and a PKCE `code_verifier`, keep both in the user's session, and redirect to the authorize URL: ```txt https://qrdx.dev/api/oauth/authorize ?response_type=code &client_id= &redirect_uri=https://acme.example/oauth/qrdx/callback &scope=profile:read codes:read codes:write &state= &code_challenge= &code_challenge_method=S256 ``` | Parameter | Required | Value | | --- | --- | --- | | `response_type` | Yes | `code` | | `client_id` | Yes | Your app's client ID | | `redirect_uri` | Yes | One of the app's redirect URIs, character for character | | `scope` | Yes | Space- or comma-separated; only scopes the app has | | `state` | Recommended | Any value; QRdx sends it back unchanged. Check it to stop CSRF | | `code_challenge` | If the app requires PKCE | 43–128 characters: the base64url SHA-256 of your `code_verifier` | | `code_challenge_method` | No | `S256`, the default and the only method accepted. `plain` is refused with `invalid_request` | | `prompt` | No | `consent` shows the consent screen even if the person approved these scopes before | ```ts title="lib/qrdx-oauth.ts" import { createHash, randomBytes } from "node:crypto"; export const QRDX_AUTHORIZE_URL = "https://qrdx.dev/api/oauth/authorize"; export function createPkce() { const verifier = randomBytes(32).toString("base64url"); // 43 characters const challenge = createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } export function authorizeUrl({ state, challenge, }: { state: string; challenge: string; }) { const url = new URL(QRDX_AUTHORIZE_URL); url.search = new URLSearchParams({ response_type: "code", client_id: process.env.QRDX_CLIENT_ID!, redirect_uri: "https://acme.example/oauth/qrdx/callback", scope: "profile:read codes:read codes:write", state, code_challenge: challenge, code_challenge_method: "S256", }).toString(); return url.toString(); } ``` People who aren't signed in to QRdx sign in first. Then they see the consent screen: your app's logo, name, website and who built it, the permissions you asked for, and the account they're signed in with. Apps that QRdx hasn't verified carry an **Unverified app** note. Someone who already approved the same scopes for your app goes straight back to you. QRdx then redirects to your `redirect_uri`: - Approved: `?code=&state=`. The code works once, within 10 minutes. - Cancelled: `?error=access_denied&error_description=...&state=`. Invalid requests (an unknown `client_id`, an unregistered `redirect_uri`, a scope the app doesn't have, a missing challenge when PKCE is required, `code_challenge_method=plain`) never redirect. They return a JSON error such as `{ "error": "invalid_scope", "error_description": "..." }`. ## 3. Exchange the code for tokens [#3-exchange-the-code-for-tokens] From your server, post the code to the token endpoint. The body is form-encoded, and the `redirect_uri` must be the one you used in step 2. ```bash curl -X POST https://qrdx.dev/api/oauth/token \ -d grant_type=authorization_code \ -d client_id=$QRDX_CLIENT_ID \ -d client_secret=$QRDX_CLIENT_SECRET \ -d code=$CODE \ -d redirect_uri=https://acme.example/oauth/qrdx/callback \ -d code_verifier=$CODE_VERIFIER ``` ```ts title="app/oauth/qrdx/callback/route.ts" export async function GET(req: Request) { const params = new URL(req.url).searchParams; const session = await getSession(req); // your own session store if (params.get("state") !== session.qrdxState) { return new Response("Invalid state", { status: 400 }); } if (params.get("error")) { return Response.redirect("https://acme.example/settings?qrdx=cancelled"); } const response = await fetch("https://qrdx.dev/api/oauth/token", { method: "POST", body: new URLSearchParams({ grant_type: "authorization_code", client_id: process.env.QRDX_CLIENT_ID!, client_secret: process.env.QRDX_CLIENT_SECRET!, code: params.get("code") ?? "", redirect_uri: "https://acme.example/oauth/qrdx/callback", code_verifier: session.qrdxVerifier, }), }); if (!response.ok) { const { error, error_description } = await response.json(); throw new Error(`QRdx token exchange failed: ${error} ${error_description}`); } const tokens: { access_token: string; token_type: "Bearer"; expires_in: number; refresh_token: string; scope: string; } = await response.json(); await saveQrdxTokens(session.userId, tokens); // encrypt at rest return Response.redirect("https://acme.example/settings?qrdx=connected"); } ``` ```json { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "scope": "profile:read codes:read codes:write" } ``` `scope` lists what was granted, space-separated. Access tokens last 1 hour and refresh tokens 30 days. Send `code_verifier` exactly when the authorization had a `code_challenge`; a verifier for a code issued without one is refused. ### Refresh tokens [#refresh-tokens] Before the access token expires, trade the refresh token for a new pair. Refresh tokens rotate: each works once, so store the new one every time. ```bash curl -X POST https://qrdx.dev/api/oauth/token \ -d grant_type=refresh_token \ -d client_id=$QRDX_CLIENT_ID \ -d client_secret=$QRDX_CLIENT_SECRET \ -d refresh_token=$REFRESH_TOKEN ``` ```ts export async function refreshQrdxTokens(refreshToken: string) { const response = await fetch("https://qrdx.dev/api/oauth/token", { method: "POST", body: new URLSearchParams({ grant_type: "refresh_token", client_id: process.env.QRDX_CLIENT_ID!, client_secret: process.env.QRDX_CLIENT_SECRET!, refresh_token: refreshToken, }), }); // invalid_grant: revoked, expired or already used. Ask the person to reconnect. if (!response.ok) throw new Error((await response.json()).error); return response.json(); } ``` The token endpoint allows 30 requests a minute per IP address. ## 4. Scopes [#4-scopes] | Scope | Allows | | --- | --- | | `profile:read` | The user's id, name, email and avatar: `GET /v1/me` and `GET /api/oauth/userinfo` | | `themes:read` | Their saved QR themes: `GET /v1/themes`, `GET /v1/themes/{id}` | | `codes:read` | Their dynamic codes and scan analytics; listing webhook subscriptions | | `codes:write` | Everything `codes:read` allows, plus creating, editing and deleting codes, reporting conversions and subscribing to webhooks | Ask for the least your integration needs: people see each scope on the consent screen. ## 5. Call the API [#5-call-the-api] Send the access token as a Bearer token to `https://api.qrdx.dev/v1`. Token requests act on the personal workspace of the person who authorized your app, with their plan's limits. ```bash curl https://api.qrdx.dev/v1/codes \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```ts const response = await fetch("https://api.qrdx.dev/v1/codes", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ destinationUrl: "https://acme.example/menu", title: "Spring menu", }), }); const { data: code } = await response.json(); // code.shortLink is what the QR encodes ``` A `401` with `invalid_token` means the token expired or was revoked: refresh it, or ask the person to reconnect. A `403` with `insufficient_scope` means the token lacks the scope. Every endpoint, its scope and its errors are listed under [REST API and OAuth](https://qrdx.dev/docs/developers/rest-api). ## 6. Receive events with webhooks [#6-receive-events-with-webhooks] Subscribe on the person's behalf with the REST Hooks API instead of asking them to paste a URL. It needs `codes:write`, and the person's plan must include [webhooks](https://qrdx.dev/docs/webhooks) (Business); otherwise it returns `402 PLAN_REQUIRED`. ```bash curl https://api.qrdx.dev/v1/hooks \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_url": "https://acme.example/webhooks/qrdx", "events": ["scan.created", "form.submitted"]}' ``` The `201` response carries the subscription `id` and a signing `secret`, shown once. Every delivery is signed with it in `X-QRdx-Signature`; see [Verify signatures](https://qrdx.dev/docs/webhooks#verify-signatures). Unsubscribe with `DELETE /v1/hooks/{id}` when the person disconnects. Subscriptions also appear in their Dashboard → Webhooks, where they can remove them. ## 7. Revoke and userinfo [#7-revoke-and-userinfo] When someone disconnects QRdx in your product, revoke their tokens. Revoking either token of a pair revokes the pair; the endpoint answers `200` even for unknown tokens. ```bash curl -X POST https://qrdx.dev/api/oauth/revoke -d token=$REFRESH_TOKEN ``` ```ts await fetch("https://qrdx.dev/api/oauth/revoke", { method: "POST", body: new URLSearchParams({ token: refreshToken }), }); ``` ### Revoking access [#revoking-access] People can take access back themselves at any time, in **Settings → Authorized apps** on QRdx. Every token your app holds for their account stops working at once: API calls answer `401` with `invalid_token` and refreshing answers `invalid_grant`. Treat both as "disconnected" and ask them to connect again; they'll see the consent screen again. To identify who connected, call `GET /api/oauth/userinfo` with a `profile:read` token. It returns OpenID-style claims: ```bash curl https://qrdx.dev/api/oauth/userinfo -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "sub": "user id", "name": "Ada Lovelace", "email": "ada@example.com", "picture": "https://..." } ``` Use `sub` as the stable account id; emails can change. ## 8. Get listed [#8-get-listed] When your integration works end to end, ask to have it added to the [integrations directory](https://qrdx.dev/integrations), where it appears as a **Community** integration built by you. Email [support@qrdx.dev](mailto:support@qrdx.dev?subject=List%20my%20integration) (the **Get listed** button on your app's page fills in the details) with: - your app's name and client ID; - one line on what it does, and who it's for; - setup steps, or a link to them; - a support contact. The QRdx team reviews listed apps and marks them **verified**, which removes the "Unverified app" note from the consent screen. Changing a verified app's name, logo or website removes the badge until it's reviewed again. ## Security checklist [#security-checklist] - Keep the client secret on your server. Browser and mobile code must never see it. - Use PKCE (`S256`) and a fresh `state` for every authorization, and check `state` on return. - Register exact redirect URIs; QRdx compares them character for character. - Store access and refresh tokens encrypted, and save the new refresh token after every refresh. - Revoke tokens when people disconnect. Deleting your app in Settings → Developers revokes every token it issued; pausing it refuses them until you resume it. --- # QR code image API (Developers) URL: https://qrdx.dev/docs/developers/image-api Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/image-api.mdx Turn any URL into a QR code image you can embed in Markdown, HTML or emails. No API key needed. `GET https://api.qrdx.dev/v1/qr` returns a QR code as an SVG or PNG image, or as a vector EPS file for print and design tools. Everything is in the query string, so the URL itself is the image: paste it into a README, an `` tag or an email template. ```md ![QR code](https://api.qrdx.dev/v1/qr?data=https://github.com/you/repo) ``` No account or API key is required. ## Examples [#examples] ```html QR code for qrdx.dev ``` Download a PNG with curl (quote the URL so your shell keeps the `&`): ```bash curl -o qr.png "https://api.qrdx.dev/v1/qr?data=https://qrdx.dev&format=png&size=1024" ``` Get a vector EPS for Illustrator, InDesign or a print shop (`size` is the width in points, 72 per inch, so `144` is 2 inches): ```bash curl -o qr.eps "https://api.qrdx.dev/v1/qr?data=https://qrdx.dev&format=eps&size=144" ``` Stylised dots with a frame: ```txt https://api.qrdx.dev/v1/qr?data=https://qrdx.dev&body=rounded&eye=rounded&dot=rounded&template=Arrow ``` Encode the `data` value when it contains `&`, `#`, `+` or spaces, for example with `encodeURIComponent` in JavaScript. Otherwise the URL is split at those characters. ## Parameters [#parameters] | Parameter | Default | Description | |-----------|---------|-------------| | `data` | required | Text or URL to encode, up to 2048 characters | | `format` | `svg` | `svg`, `png` or `eps` (vector PostScript; `size` is then the width in points) | | `size` | `512` | Width and height in pixels, `64` to `2048` | | `fg` | `000000` | Module color as hex (`155dfc`, `fff`, or 8 digits with alpha) | | `bg` | `ffffff` | Background color as hex. `00000000` gives a transparent background | | `eyeColor` | `fg` | Color of the three corner eyes | | `dotColor` | `fg` | Color of the dot inside each corner eye | | `body` | `square` | Shape of the data modules | | `eye` | `square` | Shape of the corner eye frames | | `dot` | `square` | Shape of the dot inside each corner eye | | `level` | `Q` | Error correction: `L` (~7%), `M` (~15%), `Q` (~25%) or `H` (~30%) | | `margin` | `4` | Quiet zone around the code in modules, `0` to `16` | | `template` | none | Decorative frame template | Write colors without the `#`, or encode it as `%23`: a bare `#` starts the URL fragment and is never sent. Enum values (`level`, `format`, `template`) are case-insensitive. ### Pattern values [#pattern-values] - `body`: `circle`, `circle-large`, `square`, `diamond`, `circle-mixed`, `pacman`, `rounded`, `small-square`, `vertical-line`, `horizontal-line`, `liquid`, `liquid-horizontal`, `liquid-vertical`, `classy`, `classy-rounded`, `leaf`, `plus`, `hexagon`, `star`, `heart`, `random-dots`, `mosaic`, `octagon` - `eye`: `square`, `rounded`, `gear`, `circle`, `diya`, `extra-rounded`, `message`, `pointy`, `leaf`, `drop`, `octagon`, `bevel`, `squircle`, `cushion`, `shield`, `petal`, `leaf-reverse` - `dot`: `square`, `rounded`, `circle`, `message`, `message-reverse`, `diya`, `diya-reverse`, `rounded-triangle`, `leaf`, `drop`, `plus`, `cross-rounded`, `hexagon`, `octagon`, `flower`, `heart`, `squircle`, `dots`, `extra-rounded`, `leaf-reverse`, `drop-reverse` - `template`: `default`, `Arrow`, `StandardBox`, `SquareBorder`, `StrikedBox`, `Halloween` These are the same styles as the [`qrdx` React library](https://qrdx.dev/docs/features/patterns). The [MCP server](https://qrdx.dev/docs/developers/mcp) can also list them with short descriptions. ## Responses [#responses] A successful request returns the image with: - `Content-Type: image/svg+xml`, `image/png` or `application/postscript` (EPS is sent as a `qr-code.eps` download) - `Cache-Control: public, max-age=31536000, immutable` - `Access-Control-Allow-Origin: *`, so you can `fetch` it from any website Rendering is deterministic: the same query string always returns the same bytes, which is why responses can be cached for a year. Invalid parameters return `400` with a JSON body that names each problem: ```json { "code": "VALIDATION_ERROR", "message": "Invalid query parameters", "data": { "details": [ { "param": "size", "message": "Too small: expected number to be >=64" } ] }, "status": 400 } ``` Data that doesn't fit in a QR code at the chosen error correction level also returns `400` with `param: "data"`. Use a lower `level` or shorter data. ## Limits [#limits] - **Rate limit:** 300 requests per minute per IP address. Over the limit you get `429` with a `Retry-After` header. - **No logos or gradients.** The API renders solid colors only. For logos and gradients, use the [web editor](https://qrdx.dev/playground) or the [React library](https://qrdx.dev/docs/features/logos). - **The data is part of the URL.** Like any URL, it can end up in server logs, browser history and caches. Don't put passwords or other secrets in it. ## Scanning tips [#scanning-tips] - Keep `margin` at `2` or more when the image sits on a dark or busy background, such as a README in GitHub's dark mode. - Keep good contrast between `fg` and `bg`. Dark modules on a light background scan most reliably. - The defaults (square modules, 4-module margin) scan on every reader. Stylised shapes such as `circle` dots look softer but some older scanners read them less reliably — check with the [scan checker](https://qrdx.dev/tools/scan-checker). - Test the code with a few phones before printing it. ## Self-hosting [#self-hosting] The endpoint is part of the QRdx web app, so a [self-hosted](https://qrdx.dev/docs/self-hosting) instance serves it at `/api/v1/qr` on your own domain (for example `https://qr.example.com/api/v1/qr?data=…`). The short `/v1/qr` path only works on `api.qrdx.dev`. Rate limiting uses Upstash Redis when it is configured and is off otherwise. --- # MCP server (Developers) URL: https://qrdx.dev/docs/developers/mcp Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/mcp.mdx Let Claude, Cursor, VS Code and other AI tools generate styled QR codes with the QRdx MCP server. QRdx runs a public [Model Context Protocol](https://modelcontextprotocol.io) server at: ```txt https://mcp.qrdx.dev/mcp ``` Use this URL in every client. The same server also answers at `https://qrdx.dev/api/mcp`, and the [connect page](https://qrdx.dev/mcp) shows the public URL on qrdx.dev, on preview deployments and in local development. It uses the Streamable HTTP transport, needs no account or API key, and keeps no session state. Your AI assistant can call it to render QR codes, list the available styles, and build correctly formatted payloads for Wi-Fi, contacts, email, SMS and map locations. ## Setup [#setup] **Claude Code** ```bash claude mcp add --transport http qrdx https://mcp.qrdx.dev/mcp ``` **Cursor** Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "qrdx": { "url": "https://mcp.qrdx.dev/mcp" } } } ``` **VS Code** Add the server to `.vscode/mcp.json` in your workspace: ```json { "servers": { "qrdx": { "type": "http", "url": "https://mcp.qrdx.dev/mcp" } } } ``` **Other clients** Clients that support remote (Streamable HTTP) servers only need the URL `https://mcp.qrdx.dev/mcp`. For clients that only run local stdio servers, bridge it with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote): ```json { "mcpServers": { "qrdx": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.qrdx.dev/mcp"] } } } ``` Then ask for what you need, for example: - "Make a QR code for https://example.com in our brand blue, #155dfc, with rounded modules." - "Create a Wi-Fi QR code for the network Office Guest with password correct-horse-battery." - "Generate a contact QR code for Ada Lovelace, ada@example.com, and add it to the README." ## Tools [#tools] | Tool | What it does | |------|--------------| | `generate_qr_code` | Renders a QR code. Returns a PNG image and public [image API](https://qrdx.dev/docs/developers/image-api) URLs (SVG and PNG) ready for Markdown or HTML, plus the SVG markup on request. | | `list_qr_styles` | Lists body patterns, corner eye and eye dot shapes, frame templates, error correction levels, defaults and limits. | | `qr_payload_wifi` | Builds a `WIFI:` network payload (WPA, WEP or open, optionally hidden) with special characters escaped. | | `qr_payload_vcard` | Builds a vCard 3.0 contact (RFC 2426): name, organization, title, phones, email, website, address and note. | | `qr_payload_email` | Builds a `mailto:` link (RFC 6068) with optional cc, bcc, subject and body. | | `qr_payload_sms` | Builds an `sms:` link (RFC 5724) with an optional message, or the older `SMSTO:` form. | | `qr_payload_geo` | Builds a `geo:` location (RFC 5870) from latitude and longitude. | The payload tools return a plain string. Your assistant then passes it as `data` to `generate_qr_code`. ### generate_qr_code inputs [#generate_qr_code-inputs] The inputs match the [image API parameters](https://qrdx.dev/docs/developers/image-api#parameters): `data` (required), `size`, `fg`, `bg`, `eyeColor`, `dotColor`, `body`, `eye`, `dot`, `level`, `margin` and `template`. Set `include_svg` to `true` to also get the SVG markup as text, for example to save an `.svg` file. It is off by default because the markup is often 50 to 150 KB, which would fill the conversation. Like the image API, the MCP server renders solid colors only. It doesn't support logos or gradients. ## Privacy and limits [#privacy-and-limits] - The tools only render images and format text. They don't read or change any QRdx account data, and the server doesn't save the codes you generate. - The image URLs that `generate_qr_code` returns contain the encoded data. Don't share them if the data is private, such as a Wi-Fi password. - Requests are limited to 120 per minute per IP address. ## Self-hosting and local development [#self-hosting-and-local-development] The MCP server is part of the QRdx web app, so every instance serves it at `/api/mcp`: - **Self-hosted.** When `NEXT_PUBLIC_APP_URL` is your own domain, the connect page, snippets and install links use `https:///api/mcp`. - **Local development.** `pnpm dev` serves it at `http://localhost:3000/api/mcp`. The connect page still shows the public URL and offers the local one as a hint, so you can point a client at your machine to test changes to the server before they ship. Opening `/api/mcp` in a browser redirects to the connect page. MCP clients never ask for HTML, so they reach the server as usual. --- # Use QRdx with AI agents (Developers) URL: https://qrdx.dev/docs/developers/ai-agents Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/ai-agents.mdx Connect Claude, ChatGPT, Cursor, VS Code or Devin Desktop to the QRdx MCP server, give agents keyless QR image URLs, and call the REST API for a user. AI assistants and coding agents can use QRdx in three ways. Pick by what the agent has to do: | The agent needs to | Use | Key or account | | --- | --- | --- | | Make a QR code in a chat or an agent run | [MCP server](#connect-the-mcp-server) | None | | Put a QR code in Markdown, HTML, an email or a build | [Image API](#give-agents-qr-image-urls) | None | | Create or change dynamic codes, read scans | [REST API](#call-the-rest-api-for-a-user) | OAuth access token | | Read the QRdx docs | [Markdown and llms.txt](#read-qrdx-as-markdown) | None | ## Connect the MCP server [#connect-the-mcp-server] The QRdx MCP server lives at: ```txt https://mcp.qrdx.dev/mcp ``` It uses the Streamable HTTP transport and needs no account, sign-in or API key. The [connect page](https://qrdx.dev/mcp) has one-click buttons for Claude, Cursor and VS Code. The tools and their inputs are listed in [MCP server](https://qrdx.dev/docs/developers/mcp). **Claude** Claude on the web and Claude Desktop share the same connectors. 1. Open **Customize → Connectors** and click **Add custom connector**. The **Connect to Claude** button on the [connect page](https://qrdx.dev/mcp) opens this dialog with the name and URL filled in. 2. Name it `QRdx` and enter `https://mcp.qrdx.dev/mcp`. Leave the OAuth fields empty. 3. Click **Add**, then turn QRdx on from the tools menu of a chat. Free plans can add one custom connector. On Team and Enterprise plans, an owner adds the connector for the organization. **Claude Code** ```bash claude mcp add --transport http qrdx https://mcp.qrdx.dev/mcp ``` Add `--scope user` to use it in every project. To share it with a repository instead, commit a `.mcp.json`: ```json { "mcpServers": { "qrdx": { "type": "http", "url": "https://mcp.qrdx.dev/mcp" } } } ``` Run `/mcp` in Claude Code to check that `qrdx` is connected. **Cursor** [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=qrdx&config=eyJ1cmwiOiJodHRwczovL21jcC5xcmR4LmRldi9tY3AifQ%3D%3D) opens Cursor's install prompt. The link follows Cursor's [MCP install link](https://cursor.com/docs/mcp/install-links) format: the server name, and its `mcp.json` entry in base64. Or add the server to `~/.cursor/mcp.json` (every project) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "qrdx": { "url": "https://mcp.qrdx.dev/mcp" } } } ``` **VS Code** [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=qrdx&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcp.qrdx.dev%2Fmcp%22%7D) hands over to VS Code, which asks you to confirm. From a terminal: ```bash code --add-mcp '{"name":"qrdx","type":"http","url":"https://mcp.qrdx.dev/mcp"}' ``` Or add it to `.vscode/mcp.json` in your workspace: ```json { "servers": { "qrdx": { "type": "http", "url": "https://mcp.qrdx.dev/mcp" } } } ``` Then use GitHub Copilot Chat in agent mode and make sure `qrdx` is on in the tools picker. **ChatGPT** 1. Go to [ChatGPT Plugins](https://chatgpt.com/plugins), click **+** and choose **Add custom MCP server**. 2. Name it `QRdx` and enter `https://mcp.qrdx.dev/mcp` as the URL. 3. Choose no authentication, accept the warning and create it. 4. In a new chat, type `@` and pick QRdx. Your ChatGPT plan and your workspace's settings decide whether custom MCP servers are allowed. ChatGPT only connects to remote servers like this one. **Devin Desktop (Windsurf)** Windsurf is now Devin Desktop. Add the server to `~/.config/devin/mcp_config.json` (every project) or `.devin/mcp_config.json` (one project), then refresh the MCP servers: ```json { "mcpServers": { "qrdx": { "url": "https://mcp.qrdx.dev/mcp" } } } ``` Older Windsurf builds read `~/.codeium/windsurf/mcp_config.json` and expect `serverUrl` in place of `url`. **Other apps** Any app that supports remote MCP servers needs only the URL `https://mcp.qrdx.dev/mcp` with no authentication. The [connect page](https://qrdx.dev/mcp) has steps for Gemini CLI, Codex, Zed, Cline, Continue, Goose, LM Studio, Raycast, Warp, JetBrains IDEs, Perplexity, Le Chat and Copilot Studio. Apps that only run local (stdio) servers can bridge the URL with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote), which needs Node.js: ```json { "mcpServers": { "qrdx": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.qrdx.dev/mcp"] } } } ``` Then ask in plain words, for example "Make a QR code for https://example.com in #155dfc with rounded dots" or "Make a Wi-Fi QR code for the network Office Guest". ### Discovery for MCP clients [#discovery-for-mcp-clients] Clients and registries that discover servers on their own can read: - the **MCP Server Card** at `https://mcp.qrdx.dev/mcp/server-card`, in the format of the MCP [Server Card extension](https://modelcontextprotocol.io/seps/2127-mcp-server-cards): name, version, transport and supported protocol versions; - the **AI Catalog** at [`/.well-known/ai-catalog.json`](https://qrdx.dev/.well-known/ai-catalog.json), which points to the server card; - the **API catalog** at [`/.well-known/api-catalog`](https://qrdx.dev/.well-known/api-catalog) ([RFC 9727](https://www.rfc-editor.org/rfc/rfc9727)), which lists the REST API with its [OpenAPI document](https://qrdx.dev/openapi.json) and the MCP server. ## Give agents QR image URLs [#give-agents-qr-image-urls] The [image API](https://qrdx.dev/docs/developers/image-api) turns a URL into a QR code image with no key, so an agent can write the image straight into Markdown, HTML or an email: ```txt https://api.qrdx.dev/v1/qr?data=&format=svg|png|eps ``` ```md ![QR code for qrdx.dev](https://api.qrdx.dev/v1/qr?data=https%3A%2F%2Fqrdx.dev&format=png&size=512) ``` - **Encode `data`** with `encodeURIComponent`, or `&`, `#`, `+` and spaces split it. - **Pick the format for the destination.** PNG works in chat apps and email clients that don't show SVG; SVG is sharpest on the web; EPS is for print shops. - **Style with query parameters** (`fg`, `bg`, `body`, `eye`, `dot`, `level`, `margin`, `template`). The values are listed in [Image API](https://qrdx.dev/docs/developers/image-api#parameters), and the MCP tool `list_qr_styles` returns them too. - **The same URL always returns the same image**, so it's safe to cache and embed. ## Call the REST API for a user [#call-the-rest-api-for-a-user] To create or change [dynamic codes](https://qrdx.dev/docs/dynamic-qr-codes), read scans or subscribe to [webhooks](https://qrdx.dev/docs/webhooks), an agent calls the [REST API](https://qrdx.dev/docs/developers/rest-api) at `https://api.qrdx.dev/v1` with an OAuth 2.0 access token. The [OpenAPI document](https://qrdx.dev/openapi.json) describes every endpoint, schema and error, and the [API reference](https://qrdx.dev/docs/developers/api) has a page for each. QRdx doesn't issue API keys yet, so an agent gets a token the way any OAuth app does: 1. **Register an app** in [Settings → Developers](https://qrdx.dev/settings/developers) with the scopes it needs and a redirect URI it can receive, such as a `http://localhost` callback for a command-line agent. 2. **Run the authorization code flow with PKCE once.** The user approves the app on the QRdx consent screen; the agent exchanges the code for an access token and a refresh token. The steps are in [Get an access token](https://qrdx.dev/docs/developers/rest-api#get-an-access-token). 3. **Keep the refresh token in a secret store**, never in a prompt, a log or the repository. Access tokens last an hour; refreshing returns a new refresh token and the old one stops working. 4. **Ask for the least access.** `codes:read` is enough to report on codes and scans; `codes:write` creates and edits them. 5. **Respect the quota.** Each workspace has a daily request quota by plan (see [Plans and limits](https://qrdx.dev/docs/plans-and-limits)). On `429 RATE_LIMITED`, wait for the seconds in `Retry-After`. Requests made with a token act on the token owner's personal workspace, not on a team. ## Read QRdx as Markdown [#read-qrdx-as-markdown] - [`/llms.txt`](https://qrdx.dev/llms.txt): an index of the whole site for LLMs, with the product, developer tools, docs, help center, pricing and legal pages. - [`/llms-full.txt`](https://qrdx.dev/llms-full.txt): every docs page in one file, in sidebar order. - Add `.md` to any docs URL for that page alone, for example [`/docs/developers/mcp.md`](https://qrdx.dev/docs/developers/mcp.md). [`/pricing.md`](https://qrdx.dev/pricing.md) and [`/mcp.md`](https://qrdx.dev/mcp.md) work too. - Requests to the home page, `/pricing`, `/mcp` or any docs page with `Accept: text/markdown` get the Markdown version at the same URL. ## Limits [#limits] - **The MCP tools make static QR codes.** They render images and build payloads (Wi-Fi, contacts, email, SMS, map locations). They can't create dynamic codes or read anything from a QRdx account; use the REST API for that. - **No logos or gradients** from the MCP server or the image API: they draw solid colors. Use the [playground](https://qrdx.dev/playground) or the [React library](https://qrdx.dev/docs/getting-started) for logos and gradients. - **Image URLs contain the encoded data.** Don't put passwords or other secrets in them, such as a private Wi-Fi password in a public README. - **Rate limits apply per IP address** to the MCP server and the image API; the numbers are on the [MCP server](https://qrdx.dev/docs/developers/mcp#privacy-and-limits) and [Image API](https://qrdx.dev/docs/developers/image-api#limits) pages. - **REST API tokens come from OAuth only.** There are no API keys and no client-credentials grant yet, and tokens can't act on team workspaces. --- # CLI (Developers) URL: https://qrdx.dev/docs/developers/cli Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/cli.mdx Generate styled QR codes as SVG, PNG or vector EPS from your terminal or CI with the @qrdx/cli command-line tool. `@qrdx/cli` renders the same QR codes as the playground and the [`qrdx` library](https://qrdx.dev/docs/getting-started) from the command line. It never prompts in scripts and CI, and when you run `qrdx generate` with no arguments in a terminal it asks for the content and options step by step. > This page describes @qrdx/cli 0.0.5. Run `npm view @qrdx/cli version` to see the version npm installs. ```bash npx @qrdx/cli generate "https://example.com" -o qr.svg ``` Install it globally to get the `qrdx` command: ```bash npm install -g @qrdx/cli qrdx generate "https://example.com" -o qr.png --size 1024 ``` It needs Node.js 22 or later. PNG output uses [sharp](https://sharp.pixelplumbing.com), an optional dependency npm installs where your platform supports it; SVG and EPS work without it. The `qrdx` npm package is the React library: the CLI is only in `@qrdx/cli`. ## Output formats [#output-formats] The file extension picks the format, or use `--format` when the file name has none. | Format | Use it for | `--size` means | | --- | --- | --- | | `.svg` | Web pages, design tools, scaling without loss | Width in pixels (default 512) | | `.png` | Documents, slides, anywhere that needs an image | Width in pixels | | `.eps` | Print shops, Illustrator, InDesign | Width in points (72 per inch, so `144` is 2 inches) | EPS files are vector PostScript. PNG and JPEG logos are embedded as they are; other logo formats are converted with sharp when it's installed, and otherwise left out with a warning. ## Options [#options] | Flag | Default | Description | | --- | --- | --- | | `-o`, `--output` | `qr.svg` | Output file | | `--format` | from the extension | `svg`, `png` or `eps` | | `--size` | `512` | Output size (see above) | | `-l`, `--level` | `Q` | Error correction: `L`, `M`, `Q` or `H` | | `--body` | `square` | Data module [pattern](https://qrdx.dev/docs/features/patterns) | | `--eye` | `square` | Corner eye pattern | | `--dot` | `square` | Corner eye dot pattern | | `--fg`, `--bg` | `#000000`, `#ffffff` | Foreground and background: a hex color or a JSON gradient | | `--eye-color`, `--dot-color` | `--fg` | Corner eye and eye dot colors | | `--logo` | | Center logo image URL (use with `--level H`) | | `--margin` | `0` | Quiet zone, in modules | | `--template` | | `Arrow`, `StandardBox`, `SquareBorder`, `StrikedBox` or `Halloween` | | `--frame` | | Call-to-action [frame](https://qrdx.dev/docs/features/frames): any of the 19 frame styles, such as `banner-bottom`, `rounded-border`, `circle`, `sticker` or `phone` (see [all of them](https://qrdx.dev/docs/styles#frames)) | | `--frame-text` | `Scan me` | Frame label, up to 24 characters | | `--frame-color`, `--frame-text-color` | | Frame and label colors (hex) | `qrdx --help` lists every pattern name. Invalid values exit with code `1` and a message listing the accepted values. ## Examples [#examples] ```bash # High-resolution PNG for print qrdx generate "https://example.com" -o qr.png --size 2048 --level H # Vector EPS, 2 inches wide, for a print shop qrdx generate "https://example.com" -o qr.eps --size 144 # Branded colors and patterns qrdx generate "https://example.com" --body circle --eye rounded --fg "#1a1a2e" -o brand.svg # Gradient foreground qrdx generate "https://example.com" -o gradient.svg \ --fg '{"type":"linear","stops":[{"color":"#6366f1","offset":0},{"color":"#ec4899","offset":100}],"angle":135}' # "Scan for the menu" frame qrdx generate "https://example.com/menu" --frame banner-bottom --frame-text "Scan for the menu" -o menu.png # Wi-Fi network qrdx generate "WIFI:T:WPA;S:MyNetwork;P:MyPassword;;" -o wifi.svg ``` To print a [dynamic QR code](https://qrdx.dev/docs/dynamic-qr-codes), pass its short link (`https://qrdx.dev/q/`) as the data. ## Use it from an AI coding agent [#use-it-from-an-ai-coding-agent] The repository's [`generate-qr` skill](https://github.com/bucharitesh/qrdx/tree/main/skills/generate-qr) teaches agents such as Claude Code to call the CLI. For agents that support MCP, the [MCP server](https://qrdx.dev/docs/developers/mcp) needs no install at all. --- # Install the React library URL: https://qrdx.dev/docs/getting-started Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/getting-started.mdx Install the qrdx React library and render your first QR code in a few lines. Install `qrdx` and create your first QR code in minutes. > This page describes qrdx 0.0.47. Run `npm view qrdx version` to see the version npm installs. ## Install the package [#install-the-package] ```package-install qrdx@latest ``` `react` and `react-dom` (18 or newer) are peer dependencies, so `qrdx` uses the versions your app already has. PDF export is optional: install `jspdf` only if you need it. ## Quick Start [#quick-start] Create your first QR code: ```tsx import { QRCode } from 'qrdx'; export default function App() { return ( ); } ``` That's it. You now have a working QR code. ## Next Steps [#next-steps] - [Learn Usage](https://qrdx.dev/docs/usage): Customize colors, patterns, and more - [Use Templates](https://qrdx.dev/docs/features/templates): Apply pre-built styles instantly - [API Reference](https://qrdx.dev/docs/api-reference): Complete prop documentation --- # Basic usage URL: https://qrdx.dev/docs/usage Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/usage.mdx Render a QR code with the QRCode component, then change its colors, patterns, logo, template and error correction. Create and customize QR codes for your needs. > This page describes qrdx 0.0.47. Run `npm view qrdx version` to see the version npm installs. ## Basic QR Code [#basic-qr-code] ```tsx import { QRCode } from 'qrdx'; ``` ## Customize Colors [#customize-colors] ```tsx ``` ## Add Patterns [#add-patterns] Choose from 23 body patterns, 17 corner eye patterns and 21 corner eye dot patterns: ```tsx ``` There are 23 body patterns, from `square`, `rounded` and `circle` to connected `liquid` and `classy` styles and playful `star` or `heart` dots. See [Patterns](https://qrdx.dev/docs/features/patterns) for the full list and the corner eye and eye dot options, and [Shapes, eyes and frames](https://qrdx.dev/docs/styles) to see them all. ## Add a Logo [#add-a-logo] ```tsx ``` Use higher error correction (`Q` or `H`) when adding logos. ## Use Templates [#use-templates] ```tsx ``` Available templates: `default`, `Arrow`, `StandardBox`, `SquareBorder`, `StrikedBox`, `Halloween` ## Error Correction [#error-correction] Choose based on your use case: - **L (Low)** — ~7% correction, clean environments - **M (Medium)** — ~15% correction, general use - **Q (Quartile)** — ~25% correction, with logos - **H (High)** — ~30% correction, maximum resilience ```tsx ``` ## Component Variants [#component-variants] ### QRCodeCanvas [#qrcodecanvas] For advanced customization with gradients: ```tsx import { QRCodeCanvas } from 'qrdx'; ``` ### QRCodeSVG [#qrcodesvg] For scalable vector graphics: ```tsx import { QRCodeSVG } from 'qrdx'; ``` ## Best Practices [#best-practices] ### Size Guidelines [#size-guidelines] - **128px** — Minimum for scanning - **256-512px** — Recommended for web - **1024px+** — High quality print ### Error Correction [#error-correction-1] - Use `Q` or `H` when adding logos - Use `M` for general applications - Use `L` only in clean environments ### Performance [#performance] - Use solid colors over gradients when possible - Choose simpler patterns for better performance - Cache QR codes server-side when possible ## Next Steps [#next-steps] - [Colors & Gradients](https://qrdx.dev/docs/features/colors-gradients) — Advanced color options - [Templates](https://qrdx.dev/docs/features/templates) — Pre-built styles - [Downloads](https://qrdx.dev/docs/features/downloads) — Export options --- # Shapes, eyes and frames URL: https://qrdx.dev/docs/styles Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/styles.mdx Every body pattern, eye frame, eye centre and call-to-action frame, drawn live by the qrdx renderer and decoded in your browser. Every style below is drawn by the same renderer as your downloads, with its option name and the API value to copy. Each tile is decoded with [jsQR](https://github.com/cozmo/jsQR) right here in your browser, one at a time, so the **Scans** badge is a live result rather than a promise. All of them are also tested in the package (`packages/qrdx/tests/catalog-*.test.ts`): each value decodes through a phone-camera model (contrast loss and a quarter-module blur) at 4, 6 and 9 pixels per module, next to every other body, eye and centre, at error correction L, M, Q and H, at 148 px and 1024 px, light-on-dark, with brand colours and with a centre logo; they were also checked with ZXing and in sharp renders. A few pairings are weak together; see [Pairs to avoid](#pairs-to-avoid). ## Body patterns [#body-patterns] The shape of the data modules (`bodyPattern`). Connected styles (`liquid`, `classy`) merge neighbouring modules into one outline. `random-dots` and `mosaic` vary per module but are deterministic: the same value always draws the same code. - `circle`: Circle - `circle-large`: Circle Large - `square`: Square - `diamond`: Diamond - `circle-mixed`: Mixed - `pacman`: Pacman - `rounded`: Rounded - `small-square`: Small Square - `vertical-line`: Vertical Line - `horizontal-line`: Horizontal Line - `liquid`: Liquid - `liquid-horizontal`: Liquid Rows - `liquid-vertical`: Liquid Columns - `classy`: Classy - `classy-rounded`: Classy Rounded - `leaf`: Leaf - `plus`: Plus - `hexagon`: Hexagon - `star`: Star - `heart`: Heart - `random-dots`: Random Dots - `mosaic`: Mosaic - `octagon`: Octagon ## Eye frames [#eye-frames] The outer ring of the three corner markers (`cornerEyePattern`). `leaf`, `leaf-reverse`, `drop` and `petal` turn each eye so it faces the corner of the code. Each tile pairs the eye with its recommended centre. - `square`: Square - `rounded`: Rounded - `gear`: Gear - `circle`: Circle - `diya`: Diya - `extra-rounded`: Extra Rounded - `message`: Message - `pointy`: Pointy - `leaf`: Leaf - `drop`: Drop - `octagon`: Octagon - `bevel`: Bevel - `squircle`: Squircle - `cushion`: Cushion - `shield`: Shield - `petal`: Petal - `leaf-reverse`: Leaf Reverse ## Eye centres [#eye-centres] The solid centre of each corner marker (`cornerEyeDotPattern`), shown inside square eyes. - `square`: Square - `rounded`: Rounded - `circle`: Circle - `message`: Message - `message-reverse`: Message Reverse - `diya`: Diya - `diya-reverse`: Diya Reverse - `rounded-triangle`: Rounded Triangle - `leaf`: Leaf - `drop`: Drop - `plus`: Plus - `cross-rounded`: Rounded Cross - `hexagon`: Hexagon - `octagon`: Octagon - `flower`: Flower - `heart`: Heart - `squircle`: Squircle - `dots`: Dots - `extra-rounded`: Extra Rounded - `leaf-reverse`: Leaf Reverse - `drop-reverse`: Drop Reverse ## Frames [#frames] Call-to-action frames are drawn around the code, so the code and its quiet zone are never covered and the downloaded image grows to fit. Every frame takes `text` (up to 24 characters), `color`, `textColor` and `fontScale` (0.5 to 1.5). `banner-top-bottom`, `circle` and `menu-card` also show `secondaryText`; `gradient-border` runs from `color` to `secondaryColor`. - `banner-bottom`: Banner - `banner-top`: Top banner - `border`: Border - `rounded-border`: Rounded - `speech-bubble`: Bubble - `banner-top-bottom`: Two banners - `speech-bubble-top`: Bubble top - `chat-bubble`: Chat - `circle`: Circle - `polaroid`: Polaroid - `phone`: Phone - `ticket`: Ticket - `badge`: Ribbon - `envelope`: Envelope - `tag`: Tag - `menu-card`: Menu card - `sticker`: Sticker - `gradient-border`: Gradient - `focus`: Focus ```tsx import { QRCode } from "qrdx"; ; ``` ## Pairs to avoid [#pairs-to-avoid] A few pairings of shapes that scan on their own fail together in our decode tests. The playground won't let you pick them together, and the scan score warns about them (`style-pair`): - `diamond` bodies next to `square` eyes, `pacman` next to `rounded`, and `small-square` next to `square` or `rounded` eyes (missed up close). `circle` eyes read in both decoders we test (jsQR and ZXing) but are marked **caution**: some scanner apps miss them. The pairs ship in the package as `WEAK_BODY_EYES`, and `getStylePairRisk(props)` returns the warning for a style. ## Retired shapes [#retired-shapes] `curly` eyes and `diamond`, `star` and `banner` centres were removed because they don't scan reliably: ZXing (the decoder in most Android scanners) misses curly eyes and diamond and star centres at phone distances, and jsQR misses banner centres in every sharp render. Codes saved with them still load and render exactly as before, and the image API still draws them for existing links, but no picker, preset or AI theme offers them, and their names are kept only as deprecated values (`RETIRED_CORNER_EYE_PATTERNS`, `RETIRED_CORNER_EYE_DOT_PATTERNS`). --- # Patterns (Features) URL: https://qrdx.dev/docs/features/patterns Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/patterns.mdx Change the shape of the data modules, corner eyes and eye dots. Change the shape of QR code elements for unique designs. See every option drawn live (and decoded in your browser) in [Shapes, eyes and frames](https://qrdx.dev/docs/styles). ## Body Patterns [#body-patterns] Control the shape of data dots. There are 23 body patterns: - Classic: `square`, `small-square`, `horizontal-line`, `vertical-line`, `octagon` - Rounded: `circle` (default), `circle-large`, `rounded`, `liquid`, `liquid-horizontal`, `liquid-vertical`, `random-dots` - Decorative: `classy`, `classy-rounded`, `leaf`, `diamond`, `hexagon` - Playful: `mosaic`, `plus`, `star`, `heart`, `pacman`, `circle-mixed` `liquid`, `classy` and `classy-rounded` join neighbouring modules into one smooth outline (`liquid-horizontal` and `liquid-vertical` join them in rows or columns only). `random-dots` and `mosaic` vary per module but always draw the same code for the same value. Alignment patterns are always drawn as solid squares, whatever the pattern, so decoders find them reliably. ```tsx import { QRCode } from 'qrdx'; ``` ### Module scale [#module-scale] `moduleScale` (0.5 to 1, default 1) shrinks each data module around its centre for a lighter, more spaced look; connected patterns get thinner lines instead. Each pattern stops shrinking at the smallest size that still decodes in our tests (for example 0.95 for `circle`, 0.7 for `classy`; see `MODULE_SCALE_FLOOR`), and alignment patterns keep full size. ```tsx ``` ## Corner Eye Patterns [#corner-eye-patterns] Control the shape of corner markers. There are 17 corner eye patterns (the React component defaults to `gear`; the CLI defaults to `square`): `square`, `rounded`, `extra-rounded`, `squircle`, `cushion`, `bevel`, `octagon`, `leaf`, `leaf-reverse`, `drop`, `petal`, `shield`, `gear`, `circle`, `diya`, `message` and `pointy`. `leaf`, `leaf-reverse`, `drop` and `petal` turn each of the three eyes to face its corner of the code. `circle` eyes may be missed by some scanner apps; the [scan score](https://qrdx.dev/docs/utilities) warns about them. ```tsx ``` ## Corner Eye Dot Patterns [#corner-eye-dot-patterns] Control the dot inside each corner marker. There are 21 corner eye dot patterns (the React component defaults to `circle`; the CLI defaults to `square`): `square`, `rounded`, `extra-rounded`, `circle`, `squircle`, `octagon`, `hexagon`, `dots`, `leaf`, `leaf-reverse`, `drop`, `drop-reverse`, `plus`, `cross-rounded`, `flower`, `heart`, `message`, `message-reverse`, `diya`, `diya-reverse` and `rounded-triangle`. ```tsx ``` ### Pairs to avoid and retired shapes [#pairs-to-avoid-and-retired-shapes] `diamond` bodies next to `square` eyes, `pacman` next to `rounded` and `small-square` next to `square` or `rounded` eyes are missed up close; the playground won't combine them, they ship as `WEAK_BODY_EYES`, and `getStylePairRisk(props)` returns the warning the scan score shows. `curly` eyes and `diamond`, `star` and `banner` centres were retired because they don't scan reliably. Designs saved with them still render, but they are no longer offered or listed (see [Retired shapes](https://qrdx.dev/docs/styles#retired-shapes)). ## Per-eye colors [#per-eye-colors] `eyeColor` and `dotColor` color all three eyes. `eyeColors` overrides them per eye (`topLeft`, `topRight`, `bottomLeft`), each with an `outer` (frame) and `inner` (dot) color or gradient: ```tsx ``` Keep every eye dark against the background: decoders find the code by its three eyes. ## Combine Patterns [#combine-patterns] Mix different patterns for unique designs: ```tsx ``` ## Performance [#performance] ### Fast Patterns [#fast-patterns] - `circle` — Excellent performance - `square` — Very fast ### Medium Patterns [#medium-patterns] - `rounded` — Good balance - `diamond` — Moderate ### Complex Patterns [#complex-patterns] - `pacman` — Higher processing - `circle-mixed` — Variable complexity ## Pattern Selection [#pattern-selection] ### For Brand Identity [#for-brand-identity] ```tsx // Minimal, professional // Modern, tech-focused ``` ### For Marketing [#for-marketing] ```tsx // Eye-catching // Elegant ``` ### For Print [#for-print] ```tsx // High contrast // Smooth, professional ``` ## Next Steps [#next-steps] - [Colors & Gradients](https://qrdx.dev/docs/features/colors-gradients) — Add colors to patterns - [Templates](https://qrdx.dev/docs/features/templates) — Pre-configured patterns - [Logos](https://qrdx.dev/docs/features/logos) — Combine patterns with logos --- # Colors & gradients (Features) URL: https://qrdx.dev/docs/features/colors-gradients Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/colors-gradients.mdx Use solid colors or linear and radial gradients, and check contrast so codes stay scannable. Apply colors and gradients to create branded QR codes. ## Solid Colors [#solid-colors] ```tsx import { QRCode } from 'qrdx'; ``` Supported formats: `#hex`, `rgb()`, `rgba()`, `hsl()`, `hsla()`, named colors `eyeColors` sets each eye on its own (`topLeft`, `topRight`, `bottomLeft`, each with an `outer` and `inner` color or gradient); see [Patterns](https://qrdx.dev/docs/features/patterns#per-eye-colors). ### Transparent background [#transparent-background] `bgColor="transparent"` paints no background, so PNG and SVG downloads are see-through and PDF and EPS files have no background fill. Place the code on a light, plain surface: decoders need the light gaps between modules. ## Linear Gradients [#linear-gradients] ```tsx import { QRCodeCanvas } from 'qrdx'; ``` ### Gradient Directions [#gradient-directions] - **Horizontal**: `x1: 0, y1: 0, x2: 1, y2: 0` - **Vertical**: `x1: 0, y1: 0, x2: 0, y2: 1` - **Diagonal**: `x1: 0, y1: 0, x2: 1, y2: 1` ### Multi-Stop Gradients [#multi-stop-gradients] ```tsx ``` ## Radial Gradients [#radial-gradients] ```tsx ``` ## Color Utilities [#color-utilities] ### Check Contrast [#check-contrast] ```tsx import { getContrastRatio, getContrastLevel } from 'qrdx'; const ratio = getContrastRatio('#000000', '#ffffff'); // Returns: 21 const level = getContrastLevel(ratio); // Returns: { level: 'AAA', warning: false, message: 'Excellent contrast' } ``` ### Contrast Guidelines [#contrast-guidelines] - **Minimum**: 3.0:1 ratio - **Recommended**: 4.5:1 or higher - **Excellent**: 7.0:1 or higher ## Next Steps [#next-steps] - [Logos](https://qrdx.dev/docs/features/logos) — Add logos to colored QR codes - [Patterns](https://qrdx.dev/docs/features/patterns) — Combine colors with patterns - [Downloads](https://qrdx.dev/docs/features/downloads) — Export colored QR codes --- # Logos (Features) URL: https://qrdx.dev/docs/features/logos Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/logos.mdx Add a logo to the center of a QR code without breaking scanning. Add brand logos to QR codes while maintaining scannability. ## Basic Logo [#basic-logo] ```tsx import { QRCode } from 'qrdx'; ``` Always use higher error correction (`Q` or `H`) with logos. ## Advanced Control [#advanced-control] ```tsx import { QRCodeCanvas } from 'qrdx'; ``` ## Size Guidelines [#size-guidelines] - **256px QR**: 32-48px logo - **512px QR**: 64-96px logo - **1024px QR**: 128-192px logo Keep logos between 10-30% of QR code size. ## Logo Excavation [#logo-excavation] ### What It Does [#what-it-does] Excavation removes QR modules under the logo for better scanning. ```tsx ``` ### Clear space and error correction [#clear-space-and-error-correction] `padding` (in the same pixels as `width` and `height`) clears extra modules around the logo, so its edges never touch the code. `autoLevel` raises error correction to H whenever the cleared area covers more than 7% of the code, so a bigger logo stays readable; it is off by default, so existing codes keep their level. ```tsx ``` The logo keeps its pixel size whatever the `margin` (releases up to 0.0.46 shrank it as the margin grew). ### When to Use [#when-to-use] - ✅ Logos with solid backgrounds - ✅ Logos that might interfere with scanning - ❌ Transparent logos (optional) - ⚠️ Always test scanning on real devices ## Logo Formats [#logo-formats] - **PNG** — Best for transparency - **JPG** — Good for photos - **SVG** — Scalable but may have loading issues - **WebP** — Modern format, good compression ## Center Calculation [#center-calculation] ```tsx const qrSize = 512; const logoSize = 80; const position = { x: (qrSize - logoSize) / 2, // 216 y: (qrSize - logoSize) / 2 // 216 }; ``` ## Best Practices [#best-practices] 1. Use PNG format with transparency 2. Enable excavation for solid logos 3. Use `Q` or `H` error correction 4. Keep logo size reasonable (10-30%) 5. Test scanning on target devices ## Next Steps [#next-steps] - [Downloads](https://qrdx.dev/docs/features/downloads) — Export QR codes with logos - [Templates](https://qrdx.dev/docs/features/templates) — Combine logos with templates - [Colors & Gradients](https://qrdx.dev/docs/features/colors-gradients) — Style logos with colors --- # Templates (Features) URL: https://qrdx.dev/docs/features/templates Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/templates.mdx Apply pre-designed frame templates to a QR code, or register your own. Apply professional QR code styles instantly. ## What Are Templates? [#what-are-templates] Templates combine colors, patterns, and visual styles into ready-to-use packages. ## Available Templates [#available-templates] ### Default [#default] Classic black and white. ```tsx import { QRCode } from 'qrdx'; ``` ### Arrow [#arrow] Modern geometric design. ```tsx ``` ### Standard Box [#standard-box] Clean, structured design. ```tsx ``` ### Square Border [#square-border] Bordered design with defined edges. ```tsx ``` ### Striked Box [#striked-box] Dynamic design with diagonal elements. ```tsx ``` ### Halloween [#halloween] Seasonal Halloween theme. ```tsx ``` ## Customize Templates [#customize-templates] Override template defaults: ```tsx ``` ## Template Registry [#template-registry] ### Get All Templates [#get-all-templates] ```tsx import { getAllTemplates } from 'qrdx'; const templates = getAllTemplates(); ``` ### Get Template IDs [#get-template-ids] ```tsx import { getTemplateIds } from 'qrdx'; const ids = getTemplateIds(); // Returns: ['default', 'Arrow', 'StandardBox', ...] ``` ## Custom Templates [#custom-templates] Create your own templates: ```tsx const brandTemplate = { id: 'brand-template', name: 'Brand Template', colors: { fgColor: '#1a56db', bgColor: '#ffffff', eyeColor: '#0d47a1', dotColor: '#3b82f6' }, bodyPattern: 'rounded', cornerEyePattern: 'rounded', cornerEyeDotPattern: 'rounded' }; ``` ## When to Use Templates [#when-to-use-templates] - **Brand Consistency** — Ensure all QR codes match - **Quick Setup** — Get professional results fast - **Seasonal Campaigns** — Use themed templates - **Batch Generation** — Apply consistent styles ## Template Selection Guide [#template-selection-guide] | Template | Best For | Mood | |----------|----------|------| | Default | Professional | Neutral | | Arrow | Tech, modern | Dynamic | | Standard Box | Corporate | Professional | | Square Border | Formal | Structured | | Striked Box | Marketing | Energetic | | Halloween | Seasonal | Fun | ## Next Steps [#next-steps] - [Patterns](https://qrdx.dev/docs/features/patterns) — Detailed pattern customization - [Colors & Gradients](https://qrdx.dev/docs/features/colors-gradients) — Advanced colors - [Logos](https://qrdx.dev/docs/features/logos) — Add logos to templates --- # Frames & print (Features) URL: https://qrdx.dev/docs/features/frames Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/frames.mdx Call-to-action frames, a "Will it scan?" score and print-ready PDF export. Add a "Scan me" frame, check that a design scans before you ship it, and export a vector file at an exact physical size. ## Call-to-action frames [#call-to-action-frames] The `frame` prop draws a frame with a short label around the code. It works on `QRCode`, `QRCodeSVG`, `QRCodeCanvas`, `getQRAsSVG`, `getQRData` and all download helpers. ```tsx import { QRCode } from "qrdx"; ``` The frame is added **around** the code: the output gets bigger, and the QR modules and quiet zone are never covered, so scannability is unchanged. A light gap in the code's background color separates the code from the frame. | Option | Default | Notes | | --- | --- | --- | | `style` | — | One of the 19 styles below, or `none` (`FRAME_STYLES`) | | `text` | `"Scan me"` | Up to 24 characters. Pass `""` to hide the label | | `color` | `#000000` | Frame color (`gradient-border`: start color, default violet) | | `textColor` | white on the frame, `color` on light areas | Label color | | `secondaryText` | none | Second label, up to 24 characters: the bottom band of `banner-top-bottom`, the lower arc of `circle`, the footer of `menu-card` | | `secondaryColor` | `#db2777` | End color of `gradient-border` | | `fontScale` | `1` | Label size from 0.5 to 1.5; labels still shrink to fit | Styles: - Banners and borders: `banner-bottom`, `banner-top`, `banner-top-bottom`, `border`, `rounded-border`, `gradient-border`, `focus` (corner brackets) - Bubbles: `speech-bubble`, `speech-bubble-top`, `chat-bubble` - Badges: `circle` (text set around a ring), `badge` (ribbon), `sticker` (die-cut with a shadow) - Objects: `polaroid`, `phone`, `ticket`, `tag` (hang tag with a hole), `envelope`, `menu-card` See them all, decoded in your browser, in [Shapes, eyes and frames](https://qrdx.dev/docs/styles#frames). Every frame is plain shapes and text with opaque colors, so SVG, PNG, PDF and EPS exports match (the gradient border is drawn as fine solid color steps, and the circle frame's curved letters are rotated text in every format). ```tsx ``` The label uses the system font stack, so nothing is downloaded. Because the output grows, use `getFrameDimensions(size, frame)` when you need the final width and height: ```ts import { getFrameDimensions } from "qrdx"; getFrameDimensions(400, { style: "banner-bottom" }); // { width: 496, height: 568 } ``` `FRAME_STYLES`, `frameSchema` (zod) and the `QRFrame` / `FrameStyle` types are also exported from `qrdx/types` for server-side validation. ### In the CLI [#in-the-cli] ```bash qrdx generate "https://example.com/menu" --frame banner-bottom --frame-text "Scan for the menu" -o menu.png ``` `--frame-color` and `--frame-text-color` take hex colors. Passing text or colors without `--frame` uses `banner-bottom`. ## Will it scan? [#will-it-scan] `getScanScore` estimates how reliably a design scans. It runs in the browser and loads [jsQR](https://github.com/cozmo/jsQR) on demand. ```ts import { getScanScore } from "qrdx"; const { score, grade, checks } = await getScanScore({ value: "https://example.com", bodyPattern: "circle", cornerEyePattern: "gear", }); // score: 0–100, grade: "great" | "ok" | "risky" | "fails" // checks: [{ id, passed, message, fix? }] ``` What it checks: - **Decoding at 96, 160 and 320 px.** The design is rendered at each width, given camera-like blur and contrast loss, and decoded a few times at slightly different scales. - **Contrast** between every code color (including gradient stops, eye and dot colors) and the background, and whether the code is dark on light. - **Logo coverage** compared with what the error-correction level can recover. - **Quiet zone**: the margin, plus the gap a frame adds. - **Eye shape risk**: `circle` eyes may be missed by some scanner apps (and the retired `curly` eyes, if a saved design still uses them, fail strict decoders). - **Style pairs** (`style-pair`): shapes that fail together in our decode tests, such as `small-square` dots next to square eyes, and retired centres in saved designs (see `getStylePairRisk`). `getImageScanScore(fileOrImage)` does the same for an existing image: it returns the decoded content plus decode, contrast, polarity, quiet-zone and resolution checks measured from the pixels. The [scan checker](https://qrdx.dev/tools/scan-checker) is built on it. The score is a heuristic built on one open-source decoder. Phone camera apps are often more forgiving, so treat it as a guide and test-scan important prints. ## Print-ready export [#print-ready-export] `downloadPrintPDF` writes a vector PDF at an exact physical size. Modules, eyes and frames are vector paths (a logo stays a raster image). ```ts import { downloadPrintPDF, downloadPrintSVG, getPrintLayout } from "qrdx"; const props = { value: "https://example.com", frame: { style: "banner-bottom" } } as const; await downloadPrintPDF(props, { size: 40, // width of the code itself unit: "mm", // or "in" bleed: 3, // millimetres on every side cropMarks: true, colorMode: "cmyk", }); await downloadPrintSVG(props, { size: 1.5, unit: "in" }); // width="…in" height="…in" const layout = getPrintLayout(props, { size: 40 }); layout.trimWidth; // finished width in mm, frame included layout.maxScanDistance; // 400 (mm) ``` - `size` is the width of the code; a frame adds to the finished size. - With bleed, the background (or a square frame's color) extends past the trim edge so trimming leaves no white sliver. Crop marks sit outside the bleed. - `colorMode: "cmyk"` uses a simple conversion (`rgbToCmyk`: K = 1 − max(R, G, B), no ICC profile; pure black prints as K only). Ask your printer for a color-managed conversion when exact brand colors matter. - Linear and radial gradients print as smooth shadings in the PDF, in CMYK when the PDF is CMYK; the print SVG keeps them but stays RGB. - Frame text in the PDF uses Helvetica Bold, so characters outside Latin-1 may not render. Use the SVG for other scripts. - `getQRAsPrintPDF` and `getQRAsPrintPDFBuffer` return the file instead of downloading it, and also work in Node. **How big to print:** a code scans from about ten times its width, so size ≈ scan distance ÷ 10 (2 cm wide for 20 cm). Denser codes need more; the [print size calculator](https://qrdx.dev/tools/print-size) accounts for that. --- # Art QR codes (Features) URL: https://qrdx.dev/docs/features/art-qr Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/art-qr.mdx Blend a logo or photo into the modules of a scannable QR code (vector, no AI), or generate AI artwork around one. QRdx has three ways to turn a QR code into artwork: | | How it works | Output | Cost | |---|---|---|---| | **Logo QR** | Modules inside your logo's shape take its colors; the rest fade to a lighter tone | Vector (SVG, PDF, EPS, PNG) | Free | | **Image QR** | Every module shows your photo around a center square that keeps the true value (halftone) | Vector (SVG, PDF, EPS, PNG) | Free | | **AI art** | Google's Gemini repaints the code as a scene, and every image is verified to scan | 1024 px image, 2048 px print copy | AI art credits | Logo QR and Image QR are drawn by the `qrdx` library itself, so they work offline, in the editor, on the server and in Node, and the result is always a real QR code: the finder, timing and alignment patterns are never touched. ## In the editor [#in-the-editor] Open the [playground](https://qrdx.dev/playground), go to **Customisations → Art** and pick **Logo QR** or **Image QR**: 1. Upload a logo or photo (PNG, JPG, WebP or SVG, up to 1 MB). A transparent PNG or SVG gives the cleanest Logo QR. 2. Set the **intensity**: low keeps a plain-looking code, high shows more of the picture. 3. Watch the **scan check** next to the slider. It decodes the art code in your browser at three sizes, like the [scan score](https://qrdx.dev/docs/qr-detection) in the preview. Uploading an art image sets error correction to `H`, which gives the art the most room. The image never leaves your browser: only a small color grid (64 or 96 pixels across) is saved with the design. ## In code [#in-code] Pass an `art` prop with a mode, a sampled image and an optional intensity (0 to 1, default 0.6): ```tsx import { QRCodeSVG, loadQRArtImage } from 'qrdx'; // Browser: sample an uploaded file, a data URL or an const image = await loadQRArtImage(file, { mode: 'logo' }); ; ``` On the server or in Node, decode the image yourself and pass the RGBA pixels to `createQRArtImage`: ```ts import sharp from 'sharp'; import { createQRArtImage } from 'qrdx'; const { data, info } = await sharp('photo.jpg') .ensureAlpha() .raw() .toBuffer({ resolveWithObject: true }); const image = createQRArtImage( { data, width: info.width, height: info.height }, { maxSize: 96 } ); // art={{ mode: 'halftone', image, intensity: 0.8 }} ``` `art.image` is plain JSON (`{ width, height, data }` with base64 RGBA), so you can store it with a saved design and render the same code anywhere, synchronously, without fetching the picture again. | Option | Values | Notes | |---|---|---| | `mode` | `logo`, `halftone` | `halftone` is "Image QR" in the editor | | `image` | from `loadQRArtImage` or `createQRArtImage` | at most 192 × 192 samples | | `intensity` | `0` to `1` | default `0.6`; `0` looks like a plain code | Art replaces the body pattern; the corner eyes keep their `cornerEyePattern`, `cornerEyeDotPattern` and colors. Art uses the first color of a gradient `fgColor`/`bgColor`, and needs a dark foreground on a light background (with inverted colors it falls back to a plain look). ### How scannability is kept [#how-scannability-is-kept] - **Logo QR**: dark modules inside the logo are recolored, but only ever darkened to a luminance a scanner still reads as dark; light modules inside the logo get a pale tint that stays above the light threshold. Dark modules outside the logo fade to grey, never lighter than the dark threshold. - **Image QR**: each module is split into a 3 × 3 grid. The outer cells show the photo, pushed darker or lighter to match the module's value, and the center keeps the exact foreground or background color, which is where scanners sample. - Finder, timing, alignment, format and version modules are always drawn solid. Check any design with `getScanScore(props)` before printing, and test it with a real phone camera. ## Exporting art codes [#exporting-art-codes] Art codes are vector shapes, so every export keeps them sharp: ```ts import { downloadQRCode, getQRAsEPS, getQRAsPrintPDF } from 'qrdx'; await downloadQRCode(props, { format: 'svg', size: { width: 1024, height: 1024 } }); const pdf = await getQRAsPrintPDF(props, { size: 40, unit: 'mm', colorMode: 'cmyk' }); const eps = await getQRAsEPS(props, { sizeMm: 40, bleedMm: 3, colorMode: 'cmyk' }); ``` `getQRAsEPS` writes an EPSF-3.0 file with a `%%BoundingBox` in points and every module, eye and frame as a PostScript path. Like the print PDF it keeps linear and radial gradients (as PostScript Level 3 shadings) and has no transparency. PNG and JPEG logos are embedded as images; other logo formats are converted in the browser and left out on the server. Frame text uses Helvetica, so characters outside Latin-1 print as `?`. `svgToEPS(svg, { width })` converts any SVG this library renders. The CLI writes EPS too: `qrdx generate "https://example.com" -o qr.eps`. --- # Downloads (Features) URL: https://qrdx.dev/docs/features/downloads Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/features/downloads.mdx Export QR codes as PNG, JPG, SVG, PDF or vector EPS, copy them to the clipboard or get a data URL. Export QR codes as PNG, JPG, SVG, or PDF. ## Quick Download [#quick-download] ```tsx import { downloadQRCode, PRESET_SIZES } from 'qrdx'; await downloadQRCode( { value: "https://example.com", size: 1024, fgColor: "#000000", bgColor: "#ffffff", }, { format: "png", size: PRESET_SIZES.large, filename: "my-qrcode.png" } ); ``` ## Supported Formats [#supported-formats] ### PNG [#png] Best for web, supports transparency. ```tsx await downloadQRCode( { value: "https://example.com", size: 512 }, { format: "png", size: { width: 512, height: 512 }, filename: "qrcode.png" } ); ``` ### JPG [#jpg] Smaller files, solid backgrounds only. ```tsx await downloadQRCode( { value: "https://example.com", size: 512 }, { format: "jpg", size: { width: 512, height: 512 }, filename: "qrcode.jpg" } ); ``` ### SVG [#svg] Perfect for print, infinitely scalable. ```tsx await downloadQRCode( { value: "https://example.com", size: 512 }, { format: "svg", size: { width: 512, height: 512 }, filename: "qrcode.svg" } ); ``` ### PDF [#pdf] Ideal for professional documents. PDF export needs the optional `jspdf` package (`npm i jspdf`); without it, PDF downloads throw a clear error. ```tsx await downloadQRCode( { value: "https://example.com", size: 1024 }, { format: "pdf", size: { width: 1024, height: 1024 }, filename: "qrcode.pdf" } ); ``` ### EPS [#eps] Vector PostScript for print shops and design tools (Illustrator, CorelDRAW, InDesign). No extra package is needed. Here `size.width` is in points (72 per inch), so this file is 512 pt wide: ```tsx await downloadQRCode( { value: "https://example.com", size: 512 }, { format: "eps", size: { width: 512, height: 512 }, filename: "qrcode.eps" } ); ``` For an exact physical size, use `getQRAsEPS`. It returns the EPS as a string: the code is `sizeMm` wide (default 50 mm), and a frame adds to that. ```tsx import { getQRAsEPS } from "qrdx"; const eps = await getQRAsEPS( { value: "https://example.com" }, { sizeMm: 40, bleedMm: 3, colorMode: "cmyk" } ); ``` EPS has no transparency and no crop marks. Linear and radial gradients print as smooth PostScript Level 3 shadings. PNG and JPEG logos are embedded. ## Preset Sizes [#preset-sizes] ```tsx import { PRESET_SIZES } from 'qrdx'; PRESET_SIZES.small // 200x200 PRESET_SIZES.medium // 400x400 PRESET_SIZES.large // 800x800 PRESET_SIZES.xlarge // 1200x1200 PRESET_SIZES["2xl"] // 1600x1600 PRESET_SIZES["3xl"] // 2000x2000 ``` ### Size Recommendations [#size-recommendations] - **Small (200px)** — Social media thumbnails - **Medium (400px)** — Standard web use - **Large (800px)** — High-quality web - **X-Large (1200px)** — Print materials - **2X-Large (1600px)** — Large format printing - **3X-Large (2000px)** — Maximum quality ## Advanced Options [#advanced-options] ### Get Data URL [#get-data-url] ```tsx import { getQRCodeDataUrl } from 'qrdx'; const dataUrl = await getQRCodeDataUrl( { value: "https://example.com", size: 512 }, "png", { width: 512, height: 512 } ); ``` ### Copy to Clipboard [#copy-to-clipboard] ```tsx import { copyQRCodeToClipboard } from 'qrdx'; await copyQRCodeToClipboard( { value: "https://example.com" }, { width: 512, height: 512 } ); ``` ### Get SVG String [#get-svg-string] ```tsx import { getSVGString } from 'qrdx'; const svgString = await getSVGString({ value: "https://example.com", size: 512 }); ``` ## Batch Downloads [#batch-downloads] Download multiple QR codes: ```tsx const qrCodes = [ { props: { value: "https://example.com", size: 512 }, options: { format: "png", size: { width: 512, height: 512 }, filename: "site.png" } }, { props: { value: "tel:+1234567890", size: 512 }, options: { format: "svg", size: { width: 512, height: 512 }, filename: "phone.svg" } } ]; for (const qr of qrCodes) { await downloadQRCode(qr.props, qr.options); } ``` ## Error Handling [#error-handling] ```tsx try { await downloadQRCode( { value: "https://example.com", size: 512 }, { format: "png", size: { width: 512, height: 512 }, filename: "qr.png" } ); } catch (error) { console.error("Download failed:", error); } ``` ## Format Selection [#format-selection] - **PNG** — Best quality, larger files, transparency - **JPG** — Smaller files, no transparency - **SVG** — Scalable, small files, web/print - **PDF** — Professional documents, larger files - **EPS** — Vector print files for print shops and design tools ## Size Optimization [#size-optimization] - **Web use** — 256-512px, PNG - **Print** — 1024px+, PDF or PNG - **Mobile** — 256px, optimized - **Email** — 200-300px, balance ## Next Steps [#next-steps] - [Usage](https://qrdx.dev/docs/usage) — Learn basic generation - [Templates](https://qrdx.dev/docs/features/templates) — Download styled QR codes - [API Reference](https://qrdx.dev/docs/api-reference) — Complete download API --- # shadcn/ui registry (Developers) URL: https://qrdx.dev/docs/developers/shadcn Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/developers/shadcn.mdx Eight QR code components for shadcn/ui projects, built on the qrdx library. Install one with a single command and own the code. QRdx publishes a [shadcn/ui](https://ui.shadcn.com) registry of QR code components built on the [`qrdx`](https://www.npmjs.com/package/qrdx) library. The shadcn CLI copies the source into your project, so you own the code and can change it. > This page describes qrdx 0.0.47. Run `npm view qrdx version` to see the version npm installs. ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code.json ``` Each component has its own page with a live preview, the source, installation steps, examples and an API reference. ## Components [#components] - [QR Code](https://qrdx.dev/docs/components/qr-code): Styled, accessible QR code with download buttons, colors, gradients, patterns, logos and frame templates. - [QR Code Card](https://qrdx.dev/docs/components/qr-code-card): A card with the code in a "Scan me" frame, a title, a description and room for actions. - [QR Code Download](https://qrdx.dev/docs/components/qr-code-download): A menu that exports PNG, SVG, JPG or PDF at the size you pick, or copies the image. - [QR Code Share Dialog](https://qrdx.dev/docs/components/qr-code-share-dialog): A dialog with the code for a link, a copy button, a PNG download and the native share sheet. - [QR Code Generator](https://qrdx.dev/docs/components/qr-code-generator): A text input with a live preview, style presets and download buttons. - [QR Code Scanner](https://qrdx.dev/docs/components/qr-code-scanner): A drop zone that reads a QR code from an image in the browser. - [Wi-Fi QR Code](https://qrdx.dev/docs/components/wifi-qr-code): A code that joins a Wi-Fi network, with an escaped payload builder. - [vCard QR Code](https://qrdx.dev/docs/components/vcard-qr-code): A code that saves a contact card, with a vCard 3.0 builder. ## What gets installed [#what-gets-installed] Each component is a single file in `components/`. The CLI also installs `qrdx` (plus `lucide-react` for the icons some components use) and adds the shadcn/ui components it builds on, such as `button`, `card`, `dialog`, `dropdown-menu` or `input`. The full index is at [`https://qrdx.dev/r/registry.json`](https://qrdx.dev/r/registry.json). ## Using the registry namespace [#using-the-registry-namespace] To install with a short name, add the registry to your `components.json`: ```json { "registries": { "@qrdx": "https://qrdx.dev/r/{name}.json" } } ``` Then add one or several components by name: ```bash npx shadcn@latest add @qrdx/qr-code @qrdx/qr-code-download @qrdx/qr-code-share-dialog ``` ## Styling [#styling] The components use Tailwind classes with the standard shadcn/ui theme tokens (`bg-card`, `border`, `text-muted-foreground`, `ring-ring`) and your own Button, Card, Dialog and Input, so they follow your theme and dark mode. QR codes keep a light background by default, because dark modules on a light tile scan with every reader. PDF downloads need the optional `jspdf` package (`npm install jspdf`). --- # QR Code (Components) URL: https://qrdx.dev/docs/components/qr-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code.mdx A styled, accessible QR code with download buttons. Colors, gradients, patterns, logos and frame templates come from the qrdx library. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code.json ``` ## Usage [#usage] ```tsx import { QrCode } from "@/components/qr-code"; ``` ```tsx ``` The code renders as an SVG with an accessible name, so it stays sharp at any size and screen readers announce what it links to. ## Examples [#examples] ### Sizes [#sizes] `size` sets the on-screen width and height in pixels. Downloads use `downloadSize`, so a small code on the page still exports a print-ready file. ### Colors [#colors] `fgColor` and `bgColor` take any CSS color. `eyeColor` and `dotColor` color the three corner eyes separately. Keep the modules darker than the background so every scanner can read the code. ### Gradients [#gradients] Pass a qrdx gradient instead of a color: `linear` with an `angle`, or `radial`. Gradients carry over into PNG, SVG and PDF downloads. ### Patterns [#patterns] `bodyPattern` shapes the data modules, `cornerEyePattern` the corner frames and `cornerEyeDotPattern` the dots inside them. See [patterns](https://qrdx.dev/docs/features/patterns) for every shape. ### Logo [#logo] `logo` places an image in the center and clears the modules behind it. Use error correction level `H` so the code still scans with part of it covered. A data URL keeps the logo in downloads; a remote image needs CORS headers. ### Frame templates [#frame-templates] `templateId` draws a qrdx [template](https://qrdx.dev/docs/features/templates) around the code. Pair it with `caption` for a call to action under the frame. ### Download formats [#download-formats] `downloadFormats` picks the buttons: `png`, `jpg`, `svg` and `pdf`. PDF export needs the optional `jspdf` package (`npm install jspdf`). Pass `[]` to hide the buttons. For a single menu with formats and sizes, use [QR Code Download](https://qrdx.dev/docs/components/qr-code-download). ### Dark mode [#dark-mode] The default white background is the safest choice in dark themes: dark modules on a light tile scan with every reader. To follow the theme instead, set `fgColor="currentColor"` and a transparent background. The code then inverts in dark mode, which phone cameras read but some older scanners don't. ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | ---------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------- | | `value` | `string` | Text or URL to encode. | | `size?` | `number` | Rendered width and height in pixels. Default: `200` | | `fgColor?` | `union` | Module color: any CSS color or a qrdx gradient. Default: `"#000000"` | | `bgColor?` | `union` | Background color: any CSS color or a qrdx gradient. Default: `"#FFFFFF"` | | `eyeColor?` | `union` | Corner eye color. Defaults to `fgColor`. | | `dotColor?` | `union` | Color of the dot inside each corner eye. Defaults to `fgColor`. | | `bodyPattern?` | `union` | Shape of the data modules. Default: `"circle"` | | `cornerEyePattern?` | `union` | Shape of the corner eye frames. Default: `"gear"` | | `cornerEyeDotPattern?` | `union` | Shape of the dot inside each corner eye. Default: `"circle"` | | `level?` | `union` | Error correction level. Use "H" with a logo. Default: `"Q"` | | `margin?` | `number` | Quiet zone around the code, in modules. Default: `2` | | `templateId?` | `string` | qrdx frame template id, e.g. "Arrow" or "StandardBox". | | `logo?` | `string` | Center logo: a data URL or an image URL served with CORS headers. | | `label?` | `string` | Accessible name. Defaults to "QR code: ". | | `caption?` | `ReactNode` | Visible caption under the code. | | `downloadFormats?` | `array` | Download buttons to show. Pass `[]` to hide them. "pdf" needs the optional `jspdf` package. Default: `["png", "svg"]` | | `downloadSize?` | `number` | Width and height of downloaded files in pixels (50 to 5000). Default: `1024` | | `filename?` | `string` | Download file name without extension. Default: `"qr-code"` | Any other prop, such as `className`, is passed to the wrapping `
`. --- # QR Code Card (Components) URL: https://qrdx.dev/docs/components/qr-code-card Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code-card.mdx A card with a QR code in a "Scan me" frame, a title, a description and room for actions. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code-card.json ``` ## Usage [#usage] ```tsx import { QrCodeCard } from "@/components/qr-code-card"; ``` ```tsx ``` Anything you pass as children renders in the card footer, such as a copy button or a [download menu](https://qrdx.dev/docs/components/qr-code-download). ## Examples [#examples] ### Frames [#frames] `frame` puts the call to action under the code (`"bottom"`), above it (`"top"`) or leaves the frame out (`"none"`). `cta` changes the label, and `frameClassName` restyles the frame, for example with your brand color. The frame uses your theme's `foreground` and `background` colors by default, so it stays legible in light and dark mode. The code itself keeps a light background, which every scanner reads. ### Styling the code [#styling-the-code] `qr` takes any qrdx style option: `fgColor`, `bgColor`, gradients, `bodyPattern`, corner patterns, `logo` and `templateId`. ```tsx ``` ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | ----------------- | ------------- | -------------------------------------------------------------------------------------- | | `value` | `string` | Text or URL to encode. | | `title?` | `ReactNode` | Heading under the code. | | `description?` | `ReactNode` | Supporting text under the heading. | | `cta?` | `string` | Call to action printed on the frame. Pass `""` for a plain frame. Default: `"Scan me"` | | `frame?` | `union` | Where the frame label sits, or `"none"` for no frame. Default: `"bottom"` | | `frameClassName?` | `string` | Extra classes for the frame, e.g. `"bg-primary text-primary-foreground"`. | | `size?` | `number` | Width and height of the code in pixels. Default: `168` | | `qr?` | `QrCodeStyle` | qrdx style options for the code. | | `label?` | `string` | Accessible name. Defaults to "QR code: ". | | `children?` | `ReactNode` | Footer content, such as buttons. | Any other prop, such as `className`, is passed to the [Card](https://ui.shadcn.com/docs/components/card). --- # QR Code Download (Components) URL: https://qrdx.dev/docs/components/qr-code-download Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code-download.mdx A download menu that exports a QR code as PNG, SVG, JPG or PDF at the size you pick, or copies it as an image. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code-download.json ``` ## Usage [#usage] ```tsx import { QrCodeDownload } from "@/components/qr-code-download"; ``` ```tsx ``` The menu lists each format with a short hint, a size picker, and "Copy image", which puts a PNG on the clipboard. Files are rendered in the browser; nothing is uploaded. ## Examples [#examples] ### Styled export [#styled-export] Give `qr` the same style object you render on screen, so every exported file matches the preview. `formats`, `sizes`, `label` and `variant` shape the menu and its button. ### Formats [#formats] | Format | Output | |--------|--------| | `png` | Image with the code's background | | `jpg` | Image with a solid background | | `svg` | Vector that scales to any size | | `pdf` | Single-page document. Needs the optional `jspdf` package (`npm install jspdf`) | Raster sizes go from 50 to 5000 pixels. Pass a single entry in `sizes` to hide the size picker. ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | -------------- | ------------- | ----------------------------------------------------------------------------------------------- | | `value` | `string` | Text or URL to encode. | | `qr?` | `QrCodeStyle` | qrdx style options for the exported code. | | `formats?` | `array` | Formats in the menu. "pdf" needs the optional `jspdf` package. Default: `["png", "svg", "jpg"]` | | `sizes?` | `array` | Pixel sizes to choose from. Pass one size to hide the size picker. Default: `[512, 1024, 2048]` | | `defaultSize?` | `number` | Size selected when the menu first opens. Default: `1024` | | `filename?` | `string` | Download file name without extension. Default: `"qr-code"` | | `copy?` | `union` | Show a "Copy image" item that copies a PNG to the clipboard. Default: `true` | | `label?` | `string` | Trigger button label. Default: `"Download"` | | `variant?` | `union` | Trigger button variant. Default: `"outline"` | | `onComplete?` | `function` | Called after a file is saved or the image is copied. | | `onError?` | `function` | Called when an export fails. | Any other prop, such as `className`, is passed to the wrapping `
`. --- # QR Code Share Dialog (Components) URL: https://qrdx.dev/docs/components/qr-code-share-dialog Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code-share-dialog.mdx A share dialog with a QR code for a link, a copy button, a PNG download and the native share sheet. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code-share-dialog.json ``` ## Usage [#usage] ```tsx import { QrCodeShareDialog } from "@/components/qr-code-share-dialog"; ``` ```tsx ``` The dialog shows the code, the link in a read-only field with a copy button, and a PNG download. On devices with a share sheet (most phones), a "Share…" button opens it. ## Examples [#examples] ### Custom trigger [#custom-trigger] Pass your own trigger as children, such as an icon button in a toolbar. `title`, `description` and `qr` customize the dialog. ### Controlled [#controlled] The dialog accepts the [Dialog](https://ui.shadcn.com/docs/components/dialog) props, so you can open it from code: ```tsx const [open, setOpen] = useState(false); ``` ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | -------------- | ------------- | ----------------------------------------------------------------------------------- | | `url` | `string` | The link to share. It is encoded in the QR code. | | `title?` | `string` | Dialog title. Default: `"Share link"` | | `description?` | `ReactNode` | Text under the title. Default: `"Anyone with the link or the QR code can open it."` | | `children?` | `ReactNode` | Custom trigger element. Defaults to a "Share" button. | | `size?` | `number` | Width and height of the code in pixels. Default: `200` | | `qr?` | `QrCodeStyle` | qrdx style options for the code. | | `filename?` | `string` | File name for the PNG download, without extension. Default: `"qr-code"` | `open`, `defaultOpen`, `onOpenChange` and `modal` are passed to the Dialog. --- # QR Code Generator (Components) URL: https://qrdx.dev/docs/components/qr-code-generator Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code-generator.mdx A text input with a live QR code preview, style presets and download buttons. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code-generator.json ``` ## Usage [#usage] ```tsx import { QrCodeGenerator } from "@/components/qr-code-generator"; ``` ```tsx ``` The preview updates as you type. Each preset button shows a small sample of its style, and downloads use the selected preset at 1024 pixels. ## Examples [#examples] ### Custom presets [#custom-presets] Replace the built-in presets with your own. A preset is an `id`, a `name` and a `style` with any qrdx option, including gradients, logos and error correction `level`. The built-in presets are exported as `QR_CODE_PRESETS`, so you can extend them: ```tsx import { QR_CODE_PRESETS, QrCodeGenerator } from "@/components/qr-code-generator"; ``` ### Saving the result [#saving-the-result] `onChange` reports the text and the selected preset on every change, for example to store the user's choice: ```tsx saveDraft({ value, style: preset.style })} /> ``` ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | ---------------- | -------- | ------------------------------------------------------------------------------------- | | `presets?` | `array` | Style presets to pick from. Default: `QR_CODE_PRESETS` | | `defaultPreset?` | `string` | Id of the preset selected first. Defaults to the first preset. | | `size?` | `number` | Width and height of the preview in pixels. Default: `200` | | `formats?` | `array` | Download buttons under the preview. Pass `[]` to hide them. Default: `["png", "svg"]` | | `filename?` | `string` | Download file name without extension. Default: `"qr-code"` | ### QrCodePreset [#qrcodepreset] ### QrCodePreset | Prop | Type | Description | | ------- | ------------- | ----------- | | `id` | `string` | | | `name` | `string` | | | `style` | `QrCodeStyle` | | --- # QR Code Scanner (Components) URL: https://qrdx.dev/docs/components/qr-code-scanner Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/qr-code-scanner.mdx A drop zone that reads a QR code from an image in the browser, with copy and open-link actions. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/qr-code-scanner.json ``` ## Usage [#usage] ```tsx import { QrCodeScanner } from "@/components/qr-code-scanner"; ``` ```tsx console.log(value)} /> ``` People drop an image or click to pick one. Decoding runs in the browser with qrdx's [detection helpers](https://qrdx.dev/docs/qr-detection), so the image never leaves the device. It reads image files, not a live camera feed. The result appears with a copy button, plus an "Open link" button when it is an `http` or `https` URL. ## Examples [#examples] ### Checking a result [#checking-a-result] `onResult` receives the decoded text, so you can act on it, for example to check in a ticket. `label` changes the heading in the drop zone. Download the sample code above and drop it in to see a valid ticket. ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | ----------- | ---------- | ----------------------------------------------------------------------- | | `onResult?` | `function` | Called with the decoded text when a QR code is found. | | `label?` | `string` | Heading inside the drop zone. Default: `"Scan a QR code from an image"` | Any other prop, such as `className`, is passed to the wrapping `
`. --- # Wi-Fi QR Code (Components) URL: https://qrdx.dev/docs/components/wifi-qr-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/wifi-qr-code.mdx A QR code that joins a Wi-Fi network when scanned, with a typed, escaped payload builder. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/wifi-qr-code.json ``` ## Usage [#usage] ```tsx import { WifiQrCode } from "@/components/wifi-qr-code"; ``` ```tsx ``` The iOS and Android cameras offer to join the network when they scan the code. No app is needed. ## Examples [#examples] ### Variants [#variants] `showPassword` prints the password for guests who type it in. Use `encryption="nopass"` for open networks and `hidden` for networks that don't broadcast their name. ### Payload builder [#payload-builder] `buildWifiPayload` returns the `WIFI:` text the code encodes. It escapes `\`, `;`, `,`, `:` and `"` in names and passwords, which is where hand-written payloads usually break. Use it with any QR renderer, or to print the payload for debugging. ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | --------------- | ------------- | --------------------------------------------------------------------------------- | | `ssid` | `string` | Network name (SSID). | | `password?` | `string` | Network password. Ignored when `encryption` is "nopass". | | `encryption?` | `union` | "WPA" covers WPA, WPA2 and WPA3. Use "nopass" for open networks. Default: `"WPA"` | | `hidden?` | `union` | Set when the network does not broadcast its name. Default: `false` | | `size?` | `number` | Width and height of the code in pixels. Default: `180` | | `qr?` | `QrCodeStyle` | qrdx style options for the code. | | `showDetails?` | `union` | Show the network name under the code. Default: `true` | | `showPassword?` | `union` | Also print the password, for guests who type it in. Default: `false` | Any other prop, such as `className`, is passed to the wrapping `
`. ### buildWifiPayload [#buildwifipayload] `buildWifiPayload(network: WifiNetwork): string` takes the same network fields as the component: `ssid`, `password`, `encryption` and `hidden`. --- # vCard QR Code (Components) URL: https://qrdx.dev/docs/components/vcard-qr-code Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/components/vcard-qr-code.mdx A QR code that saves a contact card when scanned, with a typed vCard 3.0 builder. ## Installation [#installation] ```bash npx shadcn@latest add https://qrdx.dev/r/vcard-qr-code.json ``` ## Usage [#usage] ```tsx import { VCardQrCode } from "@/components/vcard-qr-code"; ``` ```tsx ``` Phone cameras offer to save the contact when they scan the code. Empty fields are left out, which keeps the code small and quick to scan. ## Examples [#examples] ### vCard builder [#vcard-builder] `buildVCard` returns the vCard 3.0 text the code encodes, with commas, semicolons, backslashes and line breaks escaped. Use it with any QR renderer, or to offer the same contact as a `.vcf` download. Contact cards hold more text than a link, so the component uses error correction level `M` to keep the modules large. Add only the fields people need, and check how large to print it with the [print size calculator](https://qrdx.dev/tools/print-size). ## API Reference [#api-reference] ### $Fumadocs | Prop | Type | Description | | -------------- | ------------- | -------------------------------------------------------------- | | `contact` | `object` | The contact to encode. | | `size?` | `number` | Width and height of the code in pixels. Default: `200` | | `qr?` | `QrCodeStyle` | qrdx style options for the code. | | `showDetails?` | `union` | Show the name and organization under the code. Default: `true` | Any other prop, such as `className`, is passed to the wrapping `
`. ### VCardContact [#vcardcontact] ### VCardContact | Prop | Type | Description | | --------------- | -------- | ---------------------------------------------------------- | | `firstName` | `string` | | | `lastName?` | `string` | | | `organization?` | `string` | | | `title?` | `string` | Job title. | | `phone?` | `string` | Work phone, ideally in international format (+1 555 0100). | | `mobile?` | `string` | | | `email?` | `string` | | | `url?` | `string` | Website, including https://. | | `address?` | `object` | | | `note?` | `string` | | --- # Library API reference URL: https://qrdx.dev/docs/api-reference Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/api-reference.mdx Every component, function and type exported by the qrdx React library. For HTTP endpoints, see the REST API. Complete reference for all `qrdx` components, functions and types. > This page describes qrdx 0.0.47. Run `npm view qrdx version` to see the version npm installs. ## Components [#components] ### QRCode [#qrcode] Main component for rendering QR codes. ```tsx import { QRCode } from 'qrdx'; ``` #### Props [#props] | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `string` | **required** | URL or text to encode | | `size` | `number` | `128` | Size in pixels | | `scale` | `number` | `1` | Scale multiplier | | `fgColor` | `ColorConfig` | `#000000` | QR code color | | `bgColor` | `ColorConfig` | `#ffffff` | Background color | | `eyeColor` | `ColorConfig` | `fgColor` | Corner marker color | | `dotColor` | `ColorConfig` | `fgColor` | Color of the dot inside each corner eye | | `bodyPattern` | `BodyPattern` | `'circle'` | Data dot pattern | | `cornerEyePattern` | `CornerEyePattern` | `'gear'` | Corner marker pattern | | `cornerEyeDotPattern` | `CornerEyeDotPattern` | `'circle'` | Corner dot pattern | | `hideLogo` | `boolean` | `false` | Hide the logo. No logo is drawn unless `logo` is set | | `logo` | `string` | — | Logo URL | | `templateId` | `string` | `'default'` | Template ID | | `customTemplate` | `TemplateDefinition` | — | Custom template | | `level` | `ErrorLevel` | `'Q'` | Error correction | | `autoLevel` | `boolean` | `false` | Raise `level` to H when the logo covers more than 7% of the code (`AUTO_LEVEL_LOGO_COVERAGE`) | | `margin` | `number` | `0` | Margin in modules | | `imageSettings` | `ImageSettings` | — | Logo settings | | `eyeColors` | `EyeColors` | — | Per-eye `outer` / `inner` colors for `topLeft`, `topRight` and `bottomLeft`; each overrides `eyeColor` / `dotColor` | | `moduleScale` | `number` | `1` | Data module size, 0.5 to 1. Each pattern stops at the smallest size that still scans (`MODULE_SCALE_FLOOR`) | | `frame` | `QRFrame` | — | Call-to-action frame; see [Frames & print](https://qrdx.dev/docs/features/frames) | | `art` | `QRArt` | — | Blend a logo or photo into the modules (Logo QR, Image QR); see [Art QR](https://qrdx.dev/docs/features/art-qr) | All 23 body patterns, 17 eyes, 21 eye centres and 19 frames are shown live in [Shapes, eyes and frames](https://qrdx.dev/docs/styles). ### QRCodeCanvas [#qrcodecanvas] Canvas-based rendering. ```tsx import { QRCodeCanvas } from 'qrdx'; ``` ### QRCodeSVG [#qrcodesvg] SVG-based rendering. ```tsx import { QRCodeSVG } from 'qrdx'; ``` ## Download Functions [#download-functions] ### downloadQRCode [#downloadqrcode] Download QR codes in multiple formats. ```tsx downloadQRCode( qrProps: QRProps, options: { format: 'png' | 'jpg' | 'svg' | 'pdf' | 'eps'; size: { width: number; height: number }; filename?: string; } ): Promise ``` ### getQRCodeDataUrl [#getqrcodedataurl] Get as data URL. ```tsx getQRCodeDataUrl( qrProps: QRProps, format: DownloadFormat, size: DownloadSize ): Promise ``` ### copyQRCodeToClipboard [#copyqrcodetoclipboard] Copy to clipboard. ```tsx copyQRCodeToClipboard( qrProps: QRProps, size?: DownloadSize ): Promise ``` ### getSVGString [#getsvgstring] Get SVG markup. ```tsx getSVGString(qrProps: QRProps): Promise ``` ## Print and scan functions [#print-and-scan-functions] These are documented with examples on their feature pages. | Function | What it does | Docs | |----------|--------------|------| | `getQRAsSVG(props)` | SVG markup for a code, including its frame | [Frames](https://qrdx.dev/docs/features/frames) | | `getQRAsPrintPDF`, `downloadPrintPDF` | Vector PDF at an exact physical size, with bleed, crop marks and CMYK (needs `jspdf`) | [Print-ready export](https://qrdx.dev/docs/features/frames#print-ready-export) | | `downloadPrintSVG` | Print-ready SVG at an exact physical size | [Print-ready export](https://qrdx.dev/docs/features/frames#print-ready-export) | | `getQRAsEPS`, `downloadPrintEPS` | Vector EPS for print shops and design tools | [Downloads](https://qrdx.dev/docs/features/downloads) | | `getScanScore(props)` | Estimates how reliably a design scans | [Will it scan?](https://qrdx.dev/docs/features/frames#will-it-scan) | | `getImageScanScore(image)` | Scores a QR code image you already have (browser only) | [Will it scan?](https://qrdx.dev/docs/features/frames#will-it-scan) | | `scanQRPixels(pixels, expected)` | Checks that raw RGBA pixels decode to `expected`, also when blurred and shrunk; runs in Node | — | | `getStylePairRisk(...)` | Warns about eye and pattern pairs some scanners miss | [Patterns](https://qrdx.dev/docs/features/patterns) | ## Detection Functions [#detection-functions] ### handleImageLoad [#handleimageload] Detect QR codes from images. ```tsx handleImageLoad( imgElement: HTMLImageElement, size: number, returnFullData?: boolean ): Promise ``` ### downloadImageWithBoundingBox [#downloadimagewithboundingbox] Debug with bounding boxes. ```tsx downloadImageWithBoundingBox( imgElement: HTMLImageElement, boundingBox: { x: number; y: number }[], filename: string ): void ``` ## Template Functions [#template-functions] ### getTemplate [#gettemplate] Get specific template. ```tsx getTemplate(templateId: string): TemplateDefinition | undefined ``` ### getAllTemplates [#getalltemplates] Get all templates. ```tsx getAllTemplates(): TemplateDefinition[] ``` ### getTemplateIds [#gettemplateids] Get template IDs. ```tsx getTemplateIds(): string[] ``` ## Utility Functions [#utility-functions] ### Color & Contrast [#color--contrast] ```tsx getSolidColor(colorConfig: ColorConfig, fallback?: string): string getContrastRatio(color1: string, color2: string): number getContrastLevel(ratio: number): ContrastLevel getLuminance(r: number, g: number, b: number): number hexToRgb(hex: string): { r: number; g: number; b: number } ``` ### Validation [#validation] ```tsx validateSize(width: number, height: number): { isValid: boolean; error?: string; } ``` ### Feature Detection [#feature-detection] ```tsx SUPPORTS_PATH2D: boolean ``` ## Types [#types] ### Core Types [#core-types] ```tsx type QRProps = { value: string; size?: number; level?: ErrorLevel; bgColor?: ColorConfig; fgColor?: ColorConfig; margin?: number; imageSettings?: ImageSettings; // ... }; type ColorConfig = | string | { type: 'solid'; color: string } | LinearGradient | RadialGradient; type ErrorLevel = 'L' | 'M' | 'Q' | 'H'; ``` ### Pattern Types [#pattern-types] ```tsx // The arrays BODY_PATTERN, CORNER_EYE_PATTERNS, CORNER_EYE_DOT_PATTERNS and // FRAME_STYLES (and their zod schemas) are exported from 'qrdx/types'. type BodyPattern = | 'circle' | 'circle-large' | 'square' | 'diamond' | 'circle-mixed' | 'pacman' | 'rounded' | 'small-square' | 'vertical-line' | 'horizontal-line' | 'liquid' | 'liquid-horizontal' | 'liquid-vertical' | 'classy' | 'classy-rounded' | 'leaf' | 'plus' | 'hexagon' | 'star' | 'heart' | 'random-dots' | 'mosaic' | 'octagon'; type CornerEyePattern = | 'square' | 'rounded' | 'gear' | 'circle' | 'diya' | 'extra-rounded' | 'message' | 'pointy' | 'leaf' | 'drop' | 'octagon' | 'bevel' | 'squircle' | 'cushion' | 'shield' | 'petal' | 'leaf-reverse'; type CornerEyeDotPattern = | 'square' | 'rounded' | 'circle' | 'message' | 'message-reverse' | 'diya' | 'diya-reverse' | 'rounded-triangle' | 'leaf' | 'drop' | 'plus' | 'cross-rounded' | 'hexagon' | 'octagon' | 'flower' | 'heart' | 'squircle' | 'dots' | 'extra-rounded' | 'leaf-reverse' | 'drop-reverse'; // Retired (deprecated) values still render for saved designs but are not // offered: RETIRED_CORNER_EYE_PATTERNS, RETIRED_CORNER_EYE_DOT_PATTERNS. type EyeColors = { topLeft?: { outer?: ColorConfig; inner?: ColorConfig }; topRight?: { outer?: ColorConfig; inner?: ColorConfig }; bottomLeft?: { outer?: ColorConfig; inner?: ColorConfig }; }; type QRFrame = { style: FrameStyle; // "none" or a frame, see FRAME_STYLES text?: string; // up to 24 characters, default "Scan me" color?: string; textColor?: string; secondaryText?: string; // banner-top-bottom, circle, menu-card secondaryColor?: string; // gradient-border end color fontScale?: number; // 0.5 to 1.5 }; ``` ### Download Types [#download-types] ```tsx type DownloadFormat = 'png' | 'jpg' | 'svg' | 'pdf' | 'eps'; type DownloadSize = { width: number; height: number; }; const PRESET_SIZES: Record = { small: { width: 200, height: 200 }, medium: { width: 400, height: 400 }, large: { width: 800, height: 800 }, xlarge: { width: 1200, height: 1200 }, '2xl': { width: 1600, height: 1600 }, '3xl': { width: 2000, height: 2000 }, }; ``` ### Gradient Types [#gradient-types] ```tsx type LinearGradient = { type: 'linear'; stops: GradientStop[]; x1: number; y1: number; x2: number; y2: number; }; type RadialGradient = { type: 'radial'; stops: GradientStop[]; cx: number; cy: number; r: number; }; type GradientStop = { offset: number; color: string; }; ``` ### Image Settings [#image-settings] ```tsx type ImageSettings = { src: string; height: number; width: number; excavate?: boolean; x?: number; y?: number; /** Extra clear space around the logo when excavating, in pixels */ padding?: number; opacity?: number; }; ``` ## Error Correction Levels [#error-correction-levels] ```tsx const ERROR_LEVELS = { L: 'Low (~7%)', M: 'Medium (~15%)', Q: 'Quartile (~25%)', H: 'High (~30%)', } as const; ``` ## Error Handling [#error-handling] Wrap async functions in try-catch: ```tsx try { await downloadQRCode(props, options); } catch (error) { console.error('Failed:', error); } ``` Common errors: - Invalid image URLs - Network failures - Invalid size parameters - Canvas context unavailable ## Browser Support [#browser-support] - Chrome 90+ - Firefox 88+ - Safari 14+ - Edge 90+ ## Server Support [#server-support] Works in Node.js environments: - Next.js - Remix - Express.js - Other Node.js frameworks Some features require browser APIs: - Canvas operations - Clipboard API - File API --- # Utilities URL: https://qrdx.dev/docs/utilities Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/utilities.mdx Color, contrast, size validation and feature detection helpers exported by qrdx. Helper functions for colors, contrast, and QR code processing. ## Color Utilities [#color-utilities] ### getSolidColor [#getsolidcolor] Extract solid color from any color config: ```tsx import { getSolidColor } from 'qrdx'; const color = getSolidColor({ type: 'linear', stops: [ { offset: 0, color: '#ff6b6b' }, { offset: 1, color: '#4ecdc4' } ], x1: 0, y1: 0, x2: 1, y2: 1 }); // Returns: "#ff6b6b" ``` ### hexToRgb [#hextorgb] Convert hex to RGB: ```tsx import { hexToRgb } from 'qrdx'; const rgb = hexToRgb('#ff6b6b'); // Returns: { r: 255, g: 107, b: 107 } ``` ## Contrast Checking [#contrast-checking] ### getContrastRatio [#getcontrastratio] Calculate WCAG contrast ratio: ```tsx import { getContrastRatio } from 'qrdx'; const ratio = getContrastRatio('#000000', '#ffffff'); // Returns: 21 ``` ### getContrastLevel [#getcontrastlevel] Get contrast assessment: ```tsx import { getContrastLevel } from 'qrdx'; const level = getContrastLevel(4.5); // Returns: { level: 'AA', warning: false, message: 'Good contrast' } ``` **Contrast Guidelines:** - **`AAA` (7.0 and above)** — Excellent contrast - **`AA` (4.5 – 7.0)** — Good contrast - **`AA Large` (3.0 – 4.5)** — Low contrast, `warning: true`, may be hard to scan - **`Fail` (below 3.0)** — Not recommended, `warning: true` ### getLuminance [#getluminance] Calculate relative luminance: ```tsx import { getLuminance } from 'qrdx'; const luminance = getLuminance(255, 255, 255); // Returns: 1.0 (white) ``` ## Validation [#validation] ### validateSize [#validatesize] Validate download dimensions: ```tsx import { validateSize } from 'qrdx'; const validation = validateSize(512, 512); if (validation.isValid) { console.log("Valid size"); } else { console.error(validation.error); } ``` ## Feature Detection [#feature-detection] ### SUPPORTS_PATH2D [#supports_path2d] Check for Path2D support: ```tsx import { SUPPORTS_PATH2D } from 'qrdx'; if (SUPPORTS_PATH2D) { // Use optimized rendering } else { // Use fallback } ``` ## Example: Contrast Checker [#example-contrast-checker] ```tsx import { getContrastRatio, getContrastLevel } from 'qrdx'; import { useState } from 'react'; export default function ContrastChecker() { const [fgColor, setFgColor] = useState('#000000'); const [bgColor, setBgColor] = useState('#ffffff'); const ratio = getContrastRatio(fgColor, bgColor); const level = getContrastLevel(ratio); return (
setFgColor(e.target.value)} /> setBgColor(e.target.value)} />

Ratio: {ratio.toFixed(2)}:1

Level: {level.level}

{level.message}

); } ``` ## Example: Size Calculator [#example-size-calculator] ```tsx import { validateSize } from 'qrdx'; import { useState } from 'react'; export default function SizeCalculator() { const [width, setWidth] = useState(512); const [height, setHeight] = useState(512); const validation = validateSize(width, height); return (
setWidth(Number(e.target.value))} /> setHeight(Number(e.target.value))} />

{validation.isValid ? 'Valid' : validation.error}

); } ``` ## Next Steps [#next-steps] - [Usage](https://qrdx.dev/docs/usage) — Apply utilities to QR codes - [API Reference](https://qrdx.dev/docs/api-reference) — Complete utility reference --- # QR detection URL: https://qrdx.dev/docs/qr-detection Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/qr-detection.mdx Read and decode QR codes from images in the browser. Read and decode QR codes from uploaded images. ## Basic Detection [#basic-detection] ```tsx import { handleImageLoad } from 'qrdx'; async function detectQR(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = async (e) => { const img = new Image(); img.onload = async () => { try { const result = await handleImageLoad(img, 400, false); if (result === "NO_QR") { resolve("No QR code found"); } else { resolve(result); } } catch (error) { reject(error); } }; img.src = e.target?.result as string; }; reader.readAsDataURL(file); }); } ``` ## React Component [#react-component] ```tsx import { useState } from 'react'; import { handleImageLoad } from 'qrdx'; export default function QRDetector() { const [result, setResult] = useState(''); const handleFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { const qrData = await detectQR(file); setResult(qrData); } catch (error) { setResult(`Error: ${error.message}`); } }; return (
{result &&

{result}

}
); } ``` ## Detection Parameters [#detection-parameters] ```tsx handleImageLoad( imgElement: HTMLImageElement, size: number, // Processing size (recommended: 400) returnFullData?: boolean // Return full data or just text ) ``` ### Size Parameter [#size-parameter] - **200-400** — Faster, good for clear images - **600+** — Better for small/damaged QR codes - **Recommended** — 400 for most cases ## Full Data Response [#full-data-response] Get complete detection information: ```tsx const fullData = await handleImageLoad(img, 400, true); // Returns: // { // data: "https://example.com", // binaryData: Uint8ClampedArray, // location: { topLeftCorner, topRightCorner, ... }, // chunks: [...] // } ``` ## Debug Visualization [#debug-visualization] Download images with bounding boxes: ```tsx import { downloadImageWithBoundingBox } from 'qrdx'; const fullData = await handleImageLoad(img, 400, true); if (fullData?.location) { const boundingBox = [ fullData.location.topLeftCorner, fullData.location.topRightCorner, fullData.location.bottomRightCorner, fullData.location.bottomLeftCorner, ]; downloadImageWithBoundingBox(img, boundingBox, "debug.png"); } ``` ## Error Handling [#error-handling] ```tsx try { const result = await handleImageLoad(img, 400, false); if (result === "NO_QR") { console.log("No QR code found"); } else { console.log("QR data:", result); } } catch (error) { console.error("Detection failed:", error); } ``` ## When to Use [#when-to-use] - Process uploaded QR code images - Extract data from screenshots - Build QR code scanners - Verify QR code contents - Debug QR code issues ## Browser Requirements [#browser-requirements] - FileReader API - HTMLCanvasElement - ImageData and getImageData - ES6+ features ## Next Steps [#next-steps] - [Utilities](https://qrdx.dev/docs/utilities) — Detection utilities - [Downloads](https://qrdx.dev/docs/features/downloads) — Export detected data - [API Reference](https://qrdx.dev/docs/api-reference) — Complete detection API --- # Self-hosting guide URL: https://qrdx.dev/docs/self-hosting Source: https://github.com/bucharitesh/qrdx/blob/main/apps/docs/content/docs/self-hosting.mdx Run your own QRdx web app, dynamic QR codes and hosted pages included, with Docker Compose or Node.js. QRdx is AGPL-3.0 licensed, and you can run the whole web app (QR editor, accounts, dynamic codes and hosted pages, teams, saved themes, community gallery and these docs) on your own server. You don't need any of this to use the [`qrdx` React library](https://qrdx.dev/docs/getting-started) or the CLI. Those run anywhere without a backend. The Docker setup is new. If something doesn't work, please [open an issue](https://github.com/bucharitesh/qrdx/issues). ## Requirements [#requirements] - Docker with Compose v2, or Node.js 22 (22.22.3 or later) and pnpm 10 for the manual setup - A way to sign in, so accounts can be created: a GitHub or Google OAuth app, or [Resend](https://resend.com) for email sign-in codes. Codes are emailed only when `NEXT_PUBLIC_APP_URL` isn't `localhost`; `pnpm dev` prints them in the terminal instead. - Postgres: the container bundled in `docker-compose.yml`, or a [Neon](https://neon.tech) database ### How the database connection works [#how-the-database-connection-works] The app talks to Postgres through Neon's serverless HTTP driver. - **Neon:** works directly. Set `DATABASE_URL` to your Neon connection string. - **Plain Postgres:** needs a Neon-compatible HTTP proxy in front of it. `docker-compose.yml` runs one ([local-neon-http-proxy](https://github.com/TimoWilhelm/local-neon-http-proxy)) and sets `DATABASE_HTTP_ENDPOINT` so the app sends its queries there. The schema comes from `packages/database/schema.ts` and is applied with `drizzle-kit push`. ## Quick start with Docker Compose [#quick-start-with-docker-compose] ### 1. Clone the repository [#1-clone-the-repository] ```bash git clone https://github.com/bucharitesh/qrdx.git cd qrdx ``` ### 2. Configure the environment [#2-configure-the-environment] ```bash cp .env.example .env ``` Edit `.env`: - `BETTER_AUTH_SECRET`: generate one with `openssl rand -base64 32` - `NEXT_PUBLIC_APP_URL`: the public URL of your instance, for example `https://qr.example.com`. Keep `http://localhost:3000` for a local test. - `BETTER_AUTH_URL`: uncomment it and use the same value - `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`: from a [GitHub OAuth app](https://github.com/settings/developers) with the callback URL `/api/auth/callback/github` - or `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`: from a [Google OAuth client](https://console.cloud.google.com/apis/credentials) with the redirect URI `/api/auth/callback/google` You don't need to set `DATABASE_URL`. Compose points the app at its own Postgres container. To change the database password, set `POSTGRES_PASSWORD` before the first start. ### 3. Start the stack [#3-start-the-stack] ```bash docker compose up -d --build ``` This builds the image, starts Postgres and the proxy, runs the one-shot `migrate` service (`drizzle-kit push`) and then starts the app on port 3000. The first build takes several minutes. ```bash docker compose ps # migrate should show "exited (0)" docker compose logs -f migrate app # follow the schema sync and the app ``` ### 4. Create your first user [#4-create-your-first-user] Open `NEXT_PUBLIC_APP_URL` in a browser and sign in with GitHub, Google or an email code. Your account is created on first sign-in. ### 5. Create your first admin [#5-create-your-first-admin] Add the email address of the account you just created to `.env`: ```bash ADMIN_EMAILS=you@example.com ``` Recreate the app container so it picks up the change, then sign in and open `/admin`: ```bash docker compose up -d app ``` `ADMIN_USER_IDS` works the same way with user ids. ## Using Neon or another managed database [#using-neon-or-another-managed-database] You can skip the bundled Postgres and run only the app image: ```bash docker build -t qrdx --build-arg NEXT_PUBLIC_APP_URL=https://qr.example.com . docker run -d --env-file .env -p 3000:3000 qrdx ``` Set `DATABASE_URL` in `.env` to your Neon connection string and leave `DATABASE_HTTP_ENDPOINT` unset. Apply the schema once (and again after each update) with the migrator image: ```bash docker build --target migrator -t qrdx-migrate . docker run --rm -e DATABASE_URL="" qrdx-migrate ``` ## Manual setup (Node.js) [#manual-setup-nodejs] ```bash git clone https://github.com/bucharitesh/qrdx.git cd qrdx pnpm install cp .env.example apps/docs/.env.local # then edit it echo 'DATABASE_URL=' > packages/database/.env pnpm db:push # apply the schema pnpm --filter docs dev # http://localhost:3000 ``` For production, build and start the app: ```bash pnpm turbo build --filter=docs pnpm --filter docs start ``` `pnpm db:push` connects with Neon's driver, so it needs a Neon database. To use the Docker Postgres for local development instead, start it with `docker compose up -d db db-proxy`, apply the schema with `docker compose run --rm migrate`, and set these in `apps/docs/.env.local`: ```bash DATABASE_URL=postgresql://postgres:postgres@localhost:5432/qrdx DATABASE_HTTP_ENDPOINT=http://localhost:4444/sql ``` ## Environment variables [#environment-variables] [`.env.example`](https://github.com/bucharitesh/qrdx/blob/main/.env.example) lists every variable with a short note on where to get it. ### Minimum [#minimum] | Variable | Purpose | | --- | --- | | `NEXT_PUBLIC_APP_URL` | Public URL of your instance | | `DATABASE_URL` | Postgres connection string (set by Compose) | | `BETTER_AUTH_SECRET` | Signs sessions and cookies | Add one OAuth provider (GitHub or Google), or Resend for email sign-in codes, so people can sign in. ### Optional services [#optional-services] | Service | Variables | Without it | | --- | --- | --- | | GitHub sign-in | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | GitHub sign-in is unavailable | | Google sign-in | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Google sign-in is unavailable | | Auth base URL | `BETTER_AUTH_URL` | Inferred from each request, with a warning in the logs | | Admin panel | `ADMIN_EMAILS`, `ADMIN_USER_IDS` | Nobody can open `/admin` | | AI themes and AI QR art (Google Gemini) | `GOOGLE_API_KEY` | AI requests fail and the AI art page can't generate images (it's hidden from search engines). The editor works normally. AI art uses Gemini image models, which need a key on a paid (billing-enabled) Gemini API tier | | Switching AI QR art off | `AI_ART_ENABLED=0` | With `GOOGLE_API_KEY` set, AI art is offered unless this is `0`. Use it while the key has no billing: AI themes keep working, and the AI art page, its pricing lines and its sitemap entry are hidden (prerendered pages update on the next deploy) | | Short-link origin | `NEXT_PUBLIC_SHORT_LINK_BASE_URL` | [Dynamic codes](https://qrdx.dev/docs/dynamic-qr-codes#self-hosting) use `NEXT_PUBLIC_APP_URL` | | Scan privacy salt | `SCAN_HASH_SALT` | Derived from `BETTER_AUTH_SECRET` | | File storage (Cloudflare R2) | `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_PUBLIC_BASE_URL` | Uploads are off; pages can still link to files hosted elsewhere. See [Files](https://qrdx.dev/docs/files#set-up-storage-self-hosting) | | Email (Resend) | `RESEND_API_KEY`, `EMAIL_FROM` | Emails are skipped and logged, so email sign-in codes can't be sent; team invites use copyable links. See [Notifications](https://qrdx.dev/docs/notifications#self-hosting) | | Custom domains on Vercel | `VERCEL_API_TOKEN`, `VERCEL_PROJECT_ID`, `VERCEL_TEAM_ID` | Leave unset off Vercel: domains are then verified with a DNS `TXT` record. See [Custom domains](https://qrdx.dev/docs/custom-domains#self-hosting) | | Scheduled jobs | `CRON_SECRET` | In production the weekly digest and webhook retry jobs refuse to run | | Link safety (Google Web Risk) | `WEB_RISK_API_KEY` | Destinations are checked by the offline rules only. See [Code controls](https://qrdx.dev/docs/code-controls) | | Form spam check (Cloudflare Turnstile) | `NEXT_PUBLIC_TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY` | Forms rely on the built-in honeypot and rate limits | | Payments (Polar) | `POLAR_ACCESS_TOKEN`, `POLAR_WEBHOOK_SECRET`, `NEXT_PUBLIC_QRDX_PRO_MONTHLY_PRODUCT_ID`, `NEXT_PUBLIC_QRDX_PRO_YEARLY_PRODUCT_ID`, `NEXT_PUBLIC_QRDX_BUSINESS_MONTHLY_PRODUCT_ID`, `NEXT_PUBLIC_QRDX_BUSINESS_YEARLY_PRODUCT_ID` | Upgrading doesn't work (plans without a product id show "Coming soon"), and every account uses the Free limits | | Dub short links | `DUB_CLIENT_ID`, `DUB_CLIENT_SECRET`, `DUB_REDIRECT_URI`, `INTEGRATION_ENCRYPTION_KEY` | The Dub integration can't be connected | | Google Drive | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_DRIVE_REDIRECT_URI`, `INTEGRATION_ENCRYPTION_KEY` | The Google Drive integration can't be connected | | Bitly, Dropbox, OneDrive, Box, Notion | `BITLY_CLIENT_ID`/`_SECRET`, `DROPBOX_CLIENT_ID`/`_SECRET`, `MICROSOFT_CLIENT_ID`/`_SECRET`, `BOX_CLIENT_ID`/`_SECRET`, `NOTION_CLIENT_ID`/`_SECRET`, `INTEGRATION_ENCRYPTION_KEY` | That integration shows "Not configured" (Bitly still connects with pasted access tokens). Rebrandly and Short.io need no variables. See [Short links and file pickers](https://qrdx.dev/docs/short-links-and-file-pickers#self-hosting) | | Rate limiting (Upstash) | `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN` (or the Vercel KV names `KV_REST_API_URL`, `KV_REST_API_TOKEN`) | No rate limiting. Recommended in production. | | Analytics (PostHog, after cookie consent) | `NEXT_PUBLIC_POSTHOG_KEY`, `NEXT_PUBLIC_POSTHOG_HOST` | No product analytics | | Analytics (Umami, cookieless) | `NEXT_PUBLIC_UMAMI_WEBSITE_ID`, `NEXT_PUBLIC_UMAMI_SCRIPT_URL` | No visit counts | | Feedback widget | `NEXT_PUBLIC_FEATUREBASE_APP_ID` | The widget doesn't load | | Docs extras | `GITHUB_TOKEN` | Star counts may hit GitHub rate limits | `INTEGRATION_ENCRYPTION_KEY` must be set in production as soon as you enable an integration, because it encrypts the stored tokens. `NEXT_PUBLIC_*` values are inlined into the browser bundle at build time. After changing one, rebuild with `docker compose up -d --build`. ## Reverse proxy and HTTPS [#reverse-proxy-and-https] Put the app behind a reverse proxy such as Caddy, nginx or Traefik that terminates TLS, and don't expose port 3000 to the internet directly. Rate limiting reads the client IP from `X-Real-IP` and then `X-Forwarded-For`, so the proxy must set these headers and the app must only be reachable through it. Once HTTPS is set up, change `NEXT_PUBLIC_APP_URL` and `BETTER_AUTH_URL` to the `https://` URL, update the OAuth callback URLs, and rebuild. ### Content-Security-Policy [#content-security-policy] Production builds send a Content-Security-Policy on QRdx's own pages: signed-in pages get a fresh script nonce per request, prerendered pages an allowlist of script hosts. Hosted pages (`/p`) get a loose policy, because they run the owner's pixels and embed media from anywhere. If your reverse proxy also sets `Content-Security-Policy`, browsers enforce both, so leave it out there. Set `CSP_REPORT_ONLY=1` (at build time and at run time) to send the policy as report-only while you check a new setup; violations are logged by `/api/csp-report`. Add `CSP_HTTPS=1` when you serve over HTTPS outside Vercel, so browsers upgrade any `http://` resource. ## Updating [#updating] Back up the database first: ```bash docker compose exec db pg_dump -U postgres qrdx > qrdx-backup.sql ``` Then pull and rebuild. The `migrate` service syncs the schema again before the app starts. ```bash git pull docker compose up -d --build ``` For the manual setup, run `git pull`, `pnpm install` and `pnpm db:push`, then rebuild. If a schema change would drop data, `drizzle-kit push` asks for confirmation. In that case, run `docker compose run --rm migrate` in a terminal to review and confirm the changes. ## Troubleshooting [#troubleshooting] - **"Invalid environment variables" on startup:** the log names each missing or invalid variable. Optional variables must be commented out, not set to an empty value. - **"BETTER_AUTH_SECRET is missing" or "You are using the default secret":** set `BETTER_AUTH_SECRET` and recreate the container. - **OAuth `redirect_uri` mismatch:** the callback URL in your OAuth app must be exactly `/api/auth/callback/`. Set `BETTER_AUTH_URL` to the same base URL. - **App can't reach the database (`fetch failed`):** with plain Postgres, `DATABASE_HTTP_ENDPOINT` must point at the running proxy. Check `docker compose ps db-proxy`. - **`migrate` never finishes:** `drizzle-kit push` is waiting for a confirmation. See [Updating](#updating). - **A changed `NEXT_PUBLIC_*` value has no effect:** rebuild the image. - **Port 4444 is already in use:** change the `db-proxy` port mapping in `docker-compose.yml`. Only local development on the host uses it.