Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
The Link Creation and Builder UI serves as the core interface and pipeline for generating, managing, and configuring shortened URLs, custom domains, and redirect rules across workspaces. It bridges client-side form management with robust server-side mutation endpoints, enabling users to orchestrate complex link parameters such as Open Graph social previews, UTM tracking strings, geo-device targeting, webhooks, and A/B test variants. By abstracting plan-tier validations, security scans, and case-sensitivity routing behind unified providers and modal layers, the architecture delivers a seamless experience for creating standard, partner-affiliated, and embedded referral links alike.
Sources: apps/web/ui/modals/link-builder/index.tsx:58-75, apps/web/ui/links/link-builder/link-builder-provider.tsx:40-67, apps/web/lib/api/links/process-link.ts:61-120, apps/web/lib/api/links/create-link.ts:35-62
The Link Builder architecture is structured around a centralized context provider, React Hook Form integration, and dual presentation modes that span both modal dialogs and dedicated full-page editing routes. At its core, LinkBuilderProvider wraps child components with a React Hook Form context (FormProvider) and a custom LinkBuilderContext that shares builder properties, modal flags, and metatag generation states across input controls and preview surfaces.
When the builder initializes, LinkBuilderProvider configures form default values using either existing link properties, duplicated link configurations, or fallback defaults defined by DEFAULT_LINK_PROPS. Conversion tracking is automatically enabled if the workspace plan qualifies beyond free or pro tiers and conversion tracking is globally allowed.
Note
For new link creation flows where neither existing properties nor duplicate properties supply a custom domain, an useEffect hook monitors available workspace domains via useAvailableDomains and populates the form domain field with the primary domain once loading completes.
The link builder renders across different application entry points, including workspace dashboards, link list toolbars, and dedicated URL management views. The architecture bifurcates into two distinct rendering wrappers: modal presentation via LinkBuilderOuter and LinkBuilderInner, and dedicated page presentation inside apps/web/app/app.dub.co/dashboard/slug/links/...link/page-client.tsx which renders LinkBuilderProvider with modal={false} directly inside the dashboard layout, attaching keyboard shortcuts for submission (CMD+S / CTRL+S) and navigation (Escape).
Sources: apps/web/ui/modals/link-builder/index.tsx:62-184, apps/web/app/app.dub.co/dashboard/slug/links/...link/page-client.tsx:84-134
The following table summarizes the primary context properties, hooks, and form management primitives utilized across the link builder ecosystem.
Sources: apps/web/ui/links/link-builder/link-builder-provider.tsx:30-67, apps/web/ui/links/link-builder/link-builder-header.tsx:24-134, apps/web/ui/modals/link-builder/index.tsx:77-154, apps/web/app/app.dub.co/dashboard/slug/links/...link/page-client.tsx:93-134
Link persistence and mutation flow through useLinkBuilderSubmit, which constructs payload bodies by normalizing form values—such as mapping tags array to tagIds, resolving "unsorted" folders to null, and handling partner parameters. The request executes against /api/links via POST for new links or /api/links/[id] via PATCH for updates.
When the form submission triggers, execution proceeds through a structured sequence of payload sanitization, network transmission, response evaluation, and cache invalidation steps:
handleSubmit(onSubmit) (React Hook Form) → triggers validation and invokes useLinkBuilderSubmit callback.Sources: apps/web/ui/modals/link-builder/index.tsx:174, apps/web/app/app.dub.co/dashboard/slug/links/...link/page-client.tsx:218
tags to tagIds, converts "unsorted" folder ID to null, clears empty optional fields (expiredUrl, ios, android, externalId, tenantId), and appends programId if partnerId is present.fetch using POST (/api/links?workspaceId=...) or PATCH (/api/links/[id]?workspaceId=...).res.status === 200 branch → invokes onSuccess?.(data), redirects if domain or key changed, mutates SWR cache prefixes via mutatePrefix, copies short links to clipboard for new links, and updates workspace stats via mutate('/api/workspaces/[slug]').res.status !== 200) → parses error message, triggers upsell gating if message includes "Upgrade to ", or maps validation errors to specific form fields (root, key, url).Upon successful server response, the client executes cache invalidation across endpoints and handles error fields according to validation rules.
Warning
Image validation errors contain the word "URL" in their descriptions but lack a dedicated form field of their own. The error handler explicitly checks for the keyword "image" before evaluating "url" branches to prevent image errors from incorrectly attaching to the destination URL input field instead of form root.
When server responses return error payloads containing the substring "Upgrade to ", the client intercepts the error message, extracts the target plan name or defaults to workspace next plan name, and renders an UpgradeRequiredToast via toast.custom.
The link builder interface integrates real-time social preview rendering, image manipulation utilities, and an Open Graph (OG) modal override system. Users can cycle through platform-specific previews, customize metadata fields, upload or resize images, and enable proxy-based metatags.
Sources: apps/web/ui/links/link-builder/link-preview.tsx:44-175, apps/web/ui/modals/link-builder/og-modal.tsx:199-215
The LinkPreview component reads form values via useWatch for proxy, title, description, image, url, and password. It computes a debounced hostname (falling back to dub.co if password protection is enabled) and renders a tabbed interface supporting four distinct platforms.
Note
Pressing the L key triggers a registered keyboard shortcut, which immediately opens the Open Graph configuration modal for rapid metadata editing.
Sources: apps/web/ui/links/link-builder/link-preview.tsx:91, apps/web/ui/modals/link-builder/og-modal.tsx:221-235
When users upload or select an image through file upload, Unsplash search, or a direct URL paste, the image undergoes client-side resizing via resizeImage(file). If the workspace is on a paid plan (non-free), updating the image automatically toggles the proxy flag to true to ensure custom metatags are rendered publicly.
Sources: apps/web/ui/modals/link-builder/og-modal.tsx:250-321, apps/web/ui/links/link-builder/link-preview.tsx:95-98
Caution
Custom Link Previews and proxy metadata overriding require a Pro plan or above. Free-tier workspaces attempting to toggle the proxy switch encounter a disabled state with an interactive upsell tooltip linking to checkout or upgrade routes.
The link builder interface features specialized configuration modals that manage granular targeting options, UTM tracking templates, webhooks, and advanced link identifiers. Each modal synchronizes its internal state with the parent LinkFormData context via react-hook-form, supporting quick keyboard shortcuts and real-time validation previews.
Sources: apps/web/ui/modals/link-builder/utm-modal.tsx:1-68, apps/web/ui/modals/link-builder/targeting-modal.tsx:1-54
The UTMModal component renders a dedicated UTM parameter builder alongside a real-time destination URL preview. When users modify UTM fields or load templates via UTMTemplatesCombo, the updateTargeting callback automatically propagates matching parameters to existing iOS, Android, and geographic targeting URLs.
The parameter propagation execution walkthrough proceeds as follows: updateTargeting() extracts parent destination and targeting URLs → getParamsFromURL() parses existing query parameters → UTM_PARAMETERS.filter() identifies parameters matching the root destination URL → constructURLFromUTMParams() reconstructs target URLs with updated UTM values → setValueParent() commits the modified target URLs back to the parent form with { shouldDirty: true }.
Tip
Pressing the U key invokes a registered keyboard shortcut that instantly opens the UTM Builder modal from anywhere in the link creation flow.
Sources: apps/web/ui/modals/link-builder/utm-modal.tsx:2, apps/web/ui/modals/link-builder/utm-modal.tsx:178-192
The TargetingModal component allows redirection based on visitor locations using country comboboxes paired with Vercel flag CDN SVGs, sorting the United States to the top of the option list. Concurrently, the WebhooksModal and WebhookSelect components integrate workspace webhooks using useWebhooks(), rendering multi-select comboboxes with real-time badge counters and keyboard shortcuts mapped to the W key.
Sources: apps/web/ui/modals/link-builder/targeting-modal.tsx:25-217, apps/web/ui/modals/link-builder/webhooks-modal.tsx:17-237
Warning
When updating targeting URLs on blur, getNewParams() checks that parentUrl contains parameters that are missing on the target URL before injecting them, preventing overwrites of distinct custom query parameters already present on localized links.
The AdvancedLinkFeaturesModal component manages system-level identifiers via externalId and tenantId fields, bound to the V keyboard shortcut. If either identifier is populated on the parent form, a quick-removal toggle button appears at the bottom-left of the form to reset externalId back to null.
Link processing and validation take place on the server during link creation via the POST route handler, which first evaluates workspace usage limits using throwIfLinksUsageExceeded(workspace) and parses request bodies against createLinkBodySchemaAsync.
For unauthenticated requests where no session exists, the handler extracts the client IP address from the x-forwarded-for header (falling back to LOCALHOST_IP) and enforces rate limiting by invoking assertRateLimit. Inside assertRateLimit, if rate limiting is active, a Redis key is constructed by joining policy.keyPrefix and the identifier. It calls ratelimit(policy.attempts, policy.window).limit(key). If success is false, formatRetryAfter calculates the remaining seconds until reset, formats a human-friendly duration using pluralize, evaluates custom policy messages or a default string, and throws a DubApiError with code rate_limit_exceeded.
Important
When shouldApplyRateLimit is disabled in the local environment, assertRateLimit immediately returns without performing Redis checks or throwing rate limit errors.
The payload is subsequently passed to processLink, which validates destination URLs and enforces workspace plan constraints. If url is provided, it is normalized via getUrlFromString and checked with isValidUrl; unparseable URLs return an unprocessable entity error with code unprocessable_entity. If url is absent and the key is not _root, processing stops with a bad request error.
Warning
For Dub-owned domains like chatg.pt or spti.fi, usage of geo-targeting, device targeting, and A/B testing is strictly blocked, returning an unprocessable entity error.
Sources: apps/web/lib/api/links/process-link.ts:132-262, apps/web/lib/upstash/assert-rate-limit.ts:28-67
Link persistence and upsert operations manage the transition from validated payloads to stored database entities, handling case-sensitivity encoding, key transformations, and conditional record updates. When a request hits the upsert pipeline, the system evaluates existing records by workspace and destination URL to determine whether to execute a creation or an update routine.
When transforming and decoding links, data passes through the verified PUT → transformLink → decodeLinkIfCaseSensitive → decodeKey execution chain. The PUT upsert route handler (apps/web/app/api/links/upsert/route.ts) invokes transformLink (apps/web/lib/api/links/utils/transform-link.ts), which evaluates case sensitivity and calls decodeLinkIfCaseSensitive (apps/web/lib/api/links/case-sensitivity.ts). If the link domain is case-sensitive, decodeLinkIfCaseSensitive delegates directly to decodeKey (apps/web/lib/api/links/case-sensitivity.ts) to reverse the base64 encoding and XOR obfuscation applied by XOR_SECRET_KEY.
Sources: apps/web/app/api/links/upsert/route.ts:23-107, apps/web/lib/api/links/utils/transform-link.ts:35-44, apps/web/lib/api/links/case-sensitivity.ts:30-88
Sources: apps/web/app/api/links/upsert/route.ts:23-107, apps/web/lib/api/links/utils/transform-link.ts:35-44, apps/web/lib/api/links/case-sensitivity.ts:30-88
Case-sensitive domains require key obfuscation because underlying storage or routing layers may normalize character casing. The system maintains an explicit array of case-sensitive domains and uses a fixed XOR secret string combined with base64 encoding to persist keys securely while retaining exact casing upon retrieval.
Important
When evaluating whether key checks can be skipped during upsert operations, the system compares lowercased keys alongside domain matching to prevent redundant conflict queries when only casing is adjusted on non-sensitive domains.
Sources: apps/web/app/api/links/upsert/route.ts:81-165, apps/web/lib/api/links/create-link.ts:164-245