---
title: "QR Code Generation"
description: "QR code generation powers high-engagement branding and physical-to-digital distribution across the platform by transforming short links into customizable, scannable matrix graphics. It bridges low-..."
last_updated: "2026-10-05T05:07:35.15703+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/link-management/qr-code-generation"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/web/lib/qr/index.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx)
- [apps/web/lib/qr/utils.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx)
- [apps/web/app/api/qr/route.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx)
- [apps/web/lib/qr/api.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/api.tsx)
- [apps/web/ui/modals/qr-code-design-fields.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx)
- [apps/web/ui/placeholders/feature-graphics/qr.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/placeholders/feature-graphics/qr.tsx)
- [apps/web/ui/shared/qr-code.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/shared/qr-code.tsx)
- [apps/web/app/api/og/partner-rewind/route.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/og/partner-rewind/route.tsx)
- [apps/web/lib/qr/codegen.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts)
- [apps/web/ui/partners/groups/design/previews/portal-preview.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/groups/design/previews/portal-preview.tsx)
- [apps/web/ui/links/link-builder/qr-code-preview.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/links/link-builder/qr-code-preview.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx)
- [apps/web/ui/partners/groups/design/previews/embed-preview.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/groups/design/previews/embed-preview.tsx)
- [apps/web/app/api/og/avatar/...seed/route.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/og/avatar/%5B%5B...seed%5D%5D/route.tsx)
- [apps/web/ui/shared/icons/qr.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/shared/icons/qr.tsx)
- [apps/web/app/api/og/program/route.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/og/program/route.tsx)
- [apps/web/ui/modals/link-qr-modal.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx)
- [apps/web/lib/openapi/qr/index.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/openapi/qr/index.ts)
- [apps/web/ui/modals/partner-link-qr-modal.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/partner-link-qr-modal.tsx)
- [packages/ui/src/icons/nucleo/qrcode.tsx](https://github.com/blade47/dub/blob/HEAD/packages/ui/src/icons/nucleo/qrcode.tsx)
- [apps/web/lib/qr/constants.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/constants.ts)
- [apps/web/lib/zod/schemas/qr.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/qr.ts)
</details>

## Overview

QR code generation powers high-engagement branding and physical-to-digital distribution across the platform by transforming short links into customizable, scannable matrix graphics. It bridges low-level error correction algorithms and vector rendering pipelines with user-facing interface controls, enabling both automated edge API generation and rich client-side design customization.

Sources: [apps/web/lib/qr/codegen.ts:17-31](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L17-L31), [apps/web/app/api/qr/route.tsx:18-58](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L18-L58), [apps/web/ui/modals/qr-code-design-fields.tsx:55-79](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L55-L79)

## Codegen and Error Correction Engine

### Overview

Low-level QR code generation coordinates version selection, bitstream construction, byte packing, Reed-Solomon error correction, and matrix symbol layout. The engine handles inputs through high-level text or binary factories (`QrCode.encodeText()`, `QrCode.encodeBinary()`) and mid-level segment builders (`QrCode.encodeSegments()`) before instantiating the immutable symbol layout.

Sources: [apps/web/lib/qr/codegen.ts:24-55](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L24-L55)

### Segment Encoding and Version Selection Walkthrough

The encoding pipeline executes a precise multi-step procedure to determine symbol dimensions, pack payloads, and format raw codewords:

1. `QrCode.encodeSegments()` iterates through version numbers starting from `minVersion` (default 1) up to `maxVersion` (40).
2. For each version, it computes `dataCapacityBits` (`QrCode.getNumDataCodewords(version, ecl) * 8`) and compares it against `QrSegment.getTotalBits(segs, version)`.
3. Once a version satisfies capacity requirements, it evaluates `boostEcl` across error correction levels (`LOW`, `MEDIUM`, `QUARTILE`, `HIGH`) to upgrade correction strength if space permits without increasing version size.
4. It concatenates segment mode bits (4 bits), character count bits, and segment payloads into a bit array `bb`.
5. It appends up to 4 terminator bits and zero-pads to a byte boundary, then fills remaining capacity up to `dataCapacityBits` with alternating pad bytes (`0xec` and `0x11`).
6. It packs the bit stream into big-endian byte arrays (`dataCodewords`) and invokes the low-level constructor `new QrCode(version, ecl, dataCodewords, mask)`.

Sources: [apps/web/lib/qr/codegen.ts:68-151](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L68-L151)

> [!NOTE]
> When `boostEcl` is enabled, the engine actively promotes the error correction level if the data payload fits comfortably within the chosen version at a higher redundancy tier.

Sources: [apps/web/lib/qr/codegen.ts:59-75](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L59-L75), [apps/web/lib/qr/codegen.ts:103-115](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L103-L115)

### Error Level Mapping and Constants

Constants and configuration mappings establish error correction lookup keys, canvas scale factors, and rendering defaults used across the QR pipeline.

| Constant Name | Value / Mapping | Purpose |
| --- | --- | --- |
| `ERROR_LEVEL_MAP` | `L` → `Ecc.LOW`, `M` → `Ecc.MEDIUM`, `Q` → `Ecc.QUARTILE`, `H` → `Ecc.HIGH` | Maps string codes to internal error correction enums |
| `DEFAULT_SIZE` | `128` | Default output dimension fallback in pixels |
| `DEFAULT_LEVEL` | `"L"` | Default error correction level specifier |
| `DEFAULT_BGCOLOR` | `"#FFFFFF"` | Default background color hex code |
| `DEFAULT_FGCOLOR` | `"#000000"` | Default foreground module color hex code |
| `DEFAULT_MARGIN` | `2` | Default quiet zone module width |
| `QR_LEVELS` | `["L", "M", "Q", "H"]` | Array of supported error correction level identifiers |
| `DEFAULT_IMG_SCALE` | `0.1` | Rough scale estimate for maximum allowed coverage |

Sources: [apps/web/lib/qr/constants.ts:3-22](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/constants.ts#L3-L22)

> [!WARNING]
> `DEFAULT_IMG_SCALE` uses a rough area estimate for maximum logo coverage. When integrating images, ensure dimensions do not exceed scannability limits despite fallback thresholds.

Sources: [apps/web/lib/qr/constants.ts:18-22](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/constants.ts#L18-L22)

### Codegen Design Trade-Offs

| Design Choice | Benefit | Cost |
| --- | --- | --- |
| Iterative version scanning (`minVersion` to `maxVersion`) | Automatically selects the smallest valid QR version for any payload | Increases CPU overhead for dynamic sizing on large segment arrays |
| Immutable module grid structure (`readonly size`, private `modules`) | Guarantees thread safety and prevents unintended mutation post-construction | Allocates new 2D boolean arrays per generated code symbol |
| Automatic mask evaluation (`mask = -1`) | Tests all 8 mask patterns to minimize penalty scores | Computationally intensive step during matrix finalization |

Sources: [apps/web/lib/qr/codegen.ts:17-31](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L17-L31), [apps/web/lib/qr/codegen.ts:68-75](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L68-L75), [apps/web/lib/qr/codegen.ts:87-101](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L87-L101), [apps/web/lib/qr/codegen.ts:164-167](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/codegen.ts#L164-L167)

## Geometry and Path Construction

### Overview

The geometry and path construction subsystem transforms raw module arrays generated by the QR engine into optimized SVG paths, customized dot configurations, and structured finder patterns. Instead of rendering individual DOM nodes for every dark module — which scales poorly for high-density symbols like version 40 — the renderer constructs unified path strings using efficient string concatenation and geometric helpers.

Sources: [apps/web/lib/qr/index.tsx:362-368](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L362-L368), [apps/web/lib/qr/api.tsx:60-66](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/api.tsx#L60-L66)

### Finder Pattern Construction and Compound Paths

Finder patterns (the 7×7 alignment and positioning markers located at the three corners of the matrix) are constructed using dedicated SVG path builders and compound geometries. The `finderBorderPath` helper generates outer boundary rings and inner cutouts based on the selected `borderStyle`.

| Border Style Name | Path Implementation Strategy | Corner Radius / Dimensions |
| --- | --- | --- |
| `square` | Sharp-cornered outer rectangle with a nested inner rectangular cutout | Outer: 7×7 at `(x, y)`, Inner: 4×4 at `(x+1, y+1)` |
| `rounded-square` | Rounded rectangle paths using `roundedRectPath` with continuous arc commands | Outer radius: `1.5`, Inner radius: `0.75` |
| `circle` | Concentric circular paths using half-arc SVG commands via `circlePath` | Outer radius: `3.5`, Inner center offset: `cx = x + 3.5`, `cy = y + 3.5` |

Sources: [apps/web/lib/qr/utils.tsx:343-403](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L343-L403)

> [!NOTE]
> Finder border paths use the `evenodd` fill rule (`fillRule="evenodd"`), allowing the central ring gap to remain transparent without requiring a separate background-colored blocking rectangle. This prevents unwanted hard-cornered white squares from appearing under rounded or circular marker styles.

Sources: [apps/web/lib/qr/utils.tsx:378-382](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L378-L382), [apps/web/lib/qr/utils.tsx:434-434](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L434-L434)

### Finder Position Mapping Walkthrough

The layout engine determines the exact coordinates for the three standard finder patterns relative to the module matrix and margin offset:

1. It reads the total module count from `cells.length`.
2. It constructs an array of three finder positions (`finderPositions`) combining the module dimensions, quiet zone `margin`, and 7×7 finder dimensions:
   - Top-left: `{ x: margin, y: margin }`
   - Top-right: `{ x: numModules - 7 + margin, y: margin }`
   - Bottom-left: `{ x: margin, y: numModules - 7 + margin }`
3. It maps each position through `getFinderPatternSVGString` (or the `FinderPattern` component) supplying the computed `effectiveMarkerColor`, `bgColor`, `borderStyle`, and `centerStyle`.
4. It joins the resulting SVG strings into the final markup payload.

Sources: [apps/web/lib/qr/index.tsx:369-387](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L369-L387), [apps/web/lib/qr/utils.tsx:560-566](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L560-L566)

## Canvas and SVG Rendering Pipelines

### Overview

The canvas and SVG rendering pipelines handle the transformation of encoded module matrices into final, renderable visual outputs across raster HTML5 canvases and vector SVG documents. These pipelines manage device pixel ratios, image loading lifecycles, logo embedding, and module excavation to prevent obscured data codewords.

Sources: [apps/web/lib/qr/index.tsx:315-399](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L315-L399), [apps/web/lib/qr/index.tsx:440-498](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L440-L498), [apps/web/lib/qr/api.tsx:13-85](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/api.tsx#L13-L85)

### Raster Canvas Drawing Execution Walkthrough

The raster rendering pipeline builds HTML5 canvas elements and converts them into data representations or raw DOM nodes. The execution proceeds through the following phases:

1. `getQRAsCanvas()` extracts parameters from `props`, resolving defaults for `size`, `level`, `bgColor`, `fgColor`, `margin`, `dotStyle`, and `markerColor`.
2. It calls `qrcodegen.QrCode.encodeText(value, ERROR_LEVEL_MAP[level]).getModules()` to retrieve the raw boolean module grid, computes `numCells` including margins, and calls `getImageSettings()` to determine logo bounds.
3. If an image is configured, it instantiates an `Image` object with `crossOrigin = "anonymous"` and invokes `waitUntilImageLoaded(image, imageSettings.src)`. If `calculatedImageSettings.excavation` is defined, it runs `excavatesModules(cells, calculatedImageSettings.excavation)`.
4. It queries `window.devicePixelRatio`, sets `canvas.height` and `canvas.width` to `size * pixelRatio`, derives the scaling factor via `(size / numCells) * pixelRatio`, and applies it using `ctx.scale(scale, scale)`.
5. It paints the background rectangle with `bgColor` and loops through dot styles (`rounded`, `extra-rounded`, or default square paths via `Path2D`) to fill data modules while omitting finder pattern cells via `isFinderPatternCell()`.
6. Finally, it renders the logo image using `ctx.drawImage()` if `haveImageToRender` is true, and returns the `canvas` instance or a data URL depending on the `getCanvas` flag.

Sources: [apps/web/lib/qr/index.tsx:440-598](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L440-L598), [apps/web/lib/qr/utils.tsx:307-317](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L307-L317)

> [!NOTE]
> During extra-rounded dot rendering, the pipeline inspects adjacent cells in all four cardinal directions (`top`, `right`, `bottom`, `left`) using an `isDark` helper. Connected sides dynamically suppress corner rounding to ensure smooth, contiguous shapes across adjacent modules.

Sources: [apps/web/lib/qr/index.tsx:507-533](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L507-L533)

### SVG Generation and Logo Embedding Pipelines

SVG rendering pipelines optimize DOM node counts by consolidating dark modules into single vector paths rather than creating individual `<rect>` nodes per cell. For instance, Level 1 symbols reduce node counts from 441 to just 2 DOM nodes (background and foreground paths).

| Pipeline Function | Output Type | Logo Image Handling Strategy |
| --- | --- | --- |
| `getQRAsSVGDataUri` | `Promise<string>` (Data URI) | Converts source URLs via `getBase64Image()` to inline base64 data URIs inside `<image>` tags. |
| `getQRAsSVG` | `JSX.Element` | Fetches base64-encoded representations via external proxy `https://wsrv.nl/?url=...&encoding=base64` and injects them into `<image>` nodes. |
| `QRCodeSVG` | `JSX.Element` | Supports `isOGContext` flags; switches between standard SVG `<image>` elements and absolute-positioned HTML `<img>` tags for Open Graph rendering contexts. |

Sources: [apps/web/lib/qr/index.tsx:315-360](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/index.tsx#L315-L360), [apps/web/lib/qr/utils.tsx:454-531](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L454-L531), [apps/web/lib/qr/api.tsx:13-58](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/api.tsx#L13-L58)

> [!WARNING]
> When `isOGContext` is true and image excavation is requested with low error correction levels (`L` or `M`), `QRCodeSVG` automatically upgrades the effective error correction level to `Q` to compensate for modules removed by logo placement.

Sources: [apps/web/lib/qr/utils.tsx:473-476](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/qr/utils.tsx#L473-L476)

## Public QR API and Validation

### Overview

The public QR code API endpoint runs at the `/qr` route on the edge runtime, providing dynamic QR image generation with built-in schema validation, rate limiting, and intelligent logo resolution. The endpoint accepts query parameters, executes validation checks, and returns Open Graph-compatible SVG image responses.

Sources: [apps/web/app/api/qr/route.tsx:1-18](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L1-L18), [apps/web/lib/openapi/qr/index.ts:29-34](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/openapi/qr/index.ts#L29-L34)

### Call-Chain Execution Walkthrough

When an HTTP `GET` request hits the `/qr` route, the execution proceeds through a strict sequence of validation, rate enforcement, logo resolution, and rendering steps:

1. `GET()` extracts request URL search parameters and invokes `getQRCodeQuerySchema.parse(getSearchParams(req.url))` to validate and coerce query inputs against schema defaults.
2. It executes `ratelimitOrThrow(req, "qr")` to enforce rate-limiting constraints on the incoming request.
3. It calls `getQRCodeLogo({ url, logo, hideLogo })` to determine the correct branding asset for the target link.
4. Inside logo resolution, `getShortLinkViaEdge(url.split("?")[0])` queries the edge store for short link data; if no short link matches, it immediately returns `DUB_QR_LOGO`.
5. If a short link exists, `getWorkspaceViaEdge({ workspaceId: shortLink.projectId })` retrieves the associated project workspace. If the workspace plan is `"free"`, it returns `DUB_QR_LOGO`.
6. If `hideLogo` is set to true, it returns `null`. If a custom `logo` string is passed in the query parameters, it returns that logo.
7. If the link belongs to a Dub-owned domain (`isDubDomain(shortLink.domain)`) and no workspace logo is configured, it falls back to `DUB_QR_LOGO`. Otherwise, it queries `getDomainViaEdge(shortLink.domain)` to check for a custom domain logo, falling back sequentially to `workspace?.logo` and finally `DUB_QR_LOGO`.
8. Returning to `GET()`, it passes the resolved parameters and `qrCodeLogo` into `QRCodeSVG()` and wraps the resulting element in `ImageResponse` with `CORS_HEADERS`, setting response dimensions and headers before returning the final image.

Sources: [apps/web/app/api/qr/route.tsx:18-58](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L18-L58), [apps/web/app/api/qr/route.tsx:60-104](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L60-L104)

### Query Schema and Validation Reference

The query parameters accepted by the QR endpoint are validated using a Zod schema that enforces type coercion, defaults, and descriptive metadata for OpenAPI documentation generation.

| Parameter | Type / Schema | Default Value | Description / Validation Rule |
| --- | --- | --- | --- |
| `url` | `parseUrlSchema` | *None* (Required) | The target URL to generate a QR code for. |
| `logo` | `z.string().optional()` | `undefined` | The logo URL to embed. Restricted to paid plans on Dub. |
| `size` | `z.coerce.number().optional()` | `600` | The size of the QR code in pixels. |
| `level` | `z.enum(QR_LEVELS).optional()` | `"L"` | Error correction level (`L`, `M`, `Q`, `H`). |
| `fgColor` | `z.string().optional()` | `#000000` | Foreground color of the QR code in hex format. |
| `bgColor` | `z.string().optional()` | `#ffffff` | Background color of the QR code in hex format. |
| `hideLogo` | `booleanQuerySchema.optional()` | `false` | Whether to hide the logo. Restricted to paid plans. |
| `margin` | `z.coerce.number().optional()` | `DEFAULT_MARGIN` | Size of the margin around the QR code matrix. |
| `includeMargin` | `booleanQuerySchema.optional()` | `true` | **Deprecated**. Margin is included by default; use `margin` instead. |

Sources: [apps/web/lib/zod/schemas/qr.ts:11-67](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/qr.ts#L11-L67)

> [!WARNING]
> Passing custom `logo` parameters or setting `hideLogo` requires a paid workspace plan on Dub. Free-tier workspaces ignore custom logo overrides and automatically fall back to the default Dub QR logo (`DUB_QR_LOGO`).

Sources: [apps/web/app/api/qr/route.tsx:76-92](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L76-L92)

### API Endpoint Design Trade-offs

| Design Choice | Benefit | Cost |
| --- | --- | --- |
| Edge Runtime Execution (`export const runtime = "edge"`) | Ultra-low latency responses close to clients worldwide with fast cold starts. | Limited access to Node.js built-in modules and reliance on edge-compatible database clients. |
| Zod Schema Coercion (`z.coerce.number()`) | Automatically converts string query parameters (like `size=400`) into typed numbers. | Hides strict type mismatch errors from callers by attempting silent casting. |
| Centralized CORS Header Injection (`CORS_HEADERS`) | Ensures consistent cross-origin access (`Access-Control-Allow-Origin: *`) across both `GET` and `OPTIONS` preflight responses. | Exposes public generation endpoints to unrestricted embedding on external domains. |

Sources: [apps/web/app/api/qr/route.tsx:11-16](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L11-L16), [apps/web/app/api/qr/route.tsx:106-111](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/qr/route.tsx#L106-L111), [apps/web/lib/zod/schemas/qr.ts:19-25](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/qr.ts#L19-L25)

## Interactive Client Customization Modals

### Overview

Dub provides interactive client-side customization modals that allow users to visually configure QR code designs in real time, persist their preferences to local storage, and export finalized graphics in multiple formats. The customization interface spans across link-specific modals (`LinkQRModal`), partner link modals (`PartnerLinkQRModal`), and inline link builder components (`QRCodePreview`), all powered by the shared form body component `QRCodeDesignFields`.

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:55-79](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L55-L79), [apps/web/ui/modals/link-qr-modal.tsx:54-107](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx#L54-L107), [apps/web/ui/modals/partner-link-qr-modal.tsx:40-81](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/partner-link-qr-modal.tsx#L40-L81), [apps/web/ui/links/link-builder/qr-code-preview.tsx:23-76](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/links/link-builder/qr-code-preview.tsx#L23-L76)

### UI Modal Controls and State Management

The customization interface exposes granular styling controls for dot shapes, finder marker centers, finder marker borders, foreground colors, and logo visibility. Workspace-specific design states are synchronized with local storage using custom persistence hooks prefixed by workspace identifiers.

| Control Name | State Property | Supported Values / Options | Purpose |
| --- | --- | --- | --- |
| Dot Style | `dotStyle` | `"square"`, `"rounded"`, `"extra-rounded"` | Controls the rendering geometry of data modules within the QR matrix. |
| Marker Center | `markerCenterStyle` | `"square"`, `"circle"` | Customizes the central block shape inside the three finder pattern corners. |
| Marker Border | `markerBorderStyle` | `"square"`, `"rounded-square"`, `"circle"` | Defines the outer border geometry surrounding each finder pattern. |
| Foreground Color | `fgColor` | Hex color string (e.g., `#000000`) | Sets the primary color applied to data modules and markers. |
| Logo Toggle | `hideLogo` | `boolean` | Determines whether the center brand logo is displayed or excavated. |

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:34-51](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L34-L51), [apps/web/ui/modals/qr-code-design-fields.tsx:165-250](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L165-L250), [apps/web/ui/modals/link-qr-modal.tsx:79-85](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx#L79-L85)

> [!NOTE]
> Pro-tier restrictions apply to logo visibility controls. Non-pro workspaces have the logo toggle disabled, forcing the UI to fall back to the default Dub QR logo (`DUB_QR_LOGO`) without allowing custom hide toggles.

Sources: [apps/web/ui/modals/link-qr-modal.tsx:87-89](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx#L87-L89), [apps/web/ui/modals/link-qr-modal.tsx:160-194](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx#L160-L194)

### Interactive Preview Rendering and Debounced Updates

Live preview rendering combines React state management with debounced input handlers and animated transitions. Color inputs use `useDebouncedCallback` with a 500ms delay to prevent excessive re-rendering and matrix recalculation while users manipulate hex color pickers.

```typescript
// Call-chain execution for updating QR design state and preview rendering:
// setData() -> state mutation -> previewKey re-calculation -> AnimatePresence transition -> <QRCode /> re-evaluation

const onColorChange = useDebouncedCallback(
  (color: string) => setData((d) => ({ ...d, fgColor: color })),
  500,
);

const previewKey = `${data.fgColor}-${data.hideLogo}-${data.dotStyle}-${data.markerCenterStyle}-${data.markerBorderStyle}-${data.markerColor ?? ""}`;
```

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:83-94](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L83-L94), [apps/web/ui/modals/qr-code-design-fields.tsx:130-152](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L130-L152)

> [!TIP]
> The preview container wraps the `<QRCode />` component inside `AnimatePresence` with a dynamic `previewKey` string. Any modification to dot styles, colors, or logo visibility triggers a smooth 100ms fade-and-blur transition.

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:130-152](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L130-L152)

### Format Exporting and Clipboard Handling

The preview header features dedicated download and copy action popovers (`DownloadPopover` and `CopyPopover`) supplied with precomputed `qrData` objects. These utilities interface with canvas and SVG export helpers to generate downloadable raster or vector assets as well as clipboard-ready payloads.

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:1-2](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L1-L2), [apps/web/ui/modals/qr-code-design-fields.tsx:106-123](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L106-L123)

### Client Customization Architecture Trade-offs

| Design Choice | Benefit | Cost |
| --- | --- | --- |
| Local Storage Persistence (`useLocalStorage`) | Retains user-configured design preferences across sessions per workspace without backend round-trips. | State can become stale if workspace context or branding assets change externally. |
| Debounced Color Handlers (`useDebouncedCallback`) | Prevents lagging UI performance during continuous color picker dragging. | Introduces a 500ms delay before preview updates reflect color picker input values. |
| Memoized QR Data Computation (`useMemo`) | Avoids redundant matrix generation computations on every minor parent re-render. | Requires strict dependency tracking across all design property parameters. |

Sources: [apps/web/ui/modals/qr-code-design-fields.tsx:83-91](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/qr-code-design-fields.tsx#L83-L91), [apps/web/ui/modals/link-qr-modal.tsx:79-82](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/link-qr-modal.tsx#L79-L82), [apps/web/ui/shared/qr-code.tsx:30-54](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/shared/qr-code.tsx#L30-L54)

## Related

- [Link Creation and Builder UI](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/link-management/link-creation-and-builder-ui)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
