Custom Domains

Purpose and Scope

Custom domains are the branded hostnames that make Dub short links look and behave like part of a user's own product or marketing site. In practice, a domain resource is the bridge between link creation, DNS setup, redirect reliability, and workspace-level defaults. This page explains how custom domain management is exposed in the repository through the public API path registry and the CLI workflow, so developers can understand where domain operations are declared and how end users select a domain for shortening URLs from a terminal.

Sources: apps/web/lib/openapi/domains/index.ts, packages/cli/src/api/domains.ts, packages/cli/src/commands/domains.ts, packages/cli/README.md

Dub's broader product documentation treats custom domains as an operational feature, not just a data model. Users add a domain, configure DNS, wait for SSL provisioning, and can optionally configure behavior such as a not found URL for missing short links. The repository evidence for this page focuses on the public surfaces that connect to that experience: OpenAPI routes for creating, listing, registering, updating, deleting, and checking domains, plus CLI commands that let a user choose a workspace domain after authenticating with Dub.

Sources: apps/web/lib/openapi/domains/index.ts, packages/cli/src/commands/domains.ts

Relevant Source Files

  • apps/web/lib/openapi/domains/index.ts - Defines the OpenAPI path map for the Domains API family, including collection, item, registration, and status endpoints.
  • packages/cli/src/api/domains.ts - Implements the CLI-side domain fetcher that combines workspace domains from the generated Dub SDK with default domains from the REST API.
  • packages/cli/src/commands/domains.ts - Registers the dub domains command, prompts the user to select a domain, validates the selected slug, and saves it to local CLI config.
  • packages/cli/README.md - Documents dub domains as an available CLI command and gives local development commands for running the CLI during development.

Domain Management Surfaces

The OpenAPI entry point makes the domain API family easy to identify. domainsPaths is a ZodOpenApiPathsObject, and it maps HTTP paths to imported operation definitions. The collection route /domains supports post for domain creation and get for listing. The item route /domains/{slug} supports patch for updates and delete for removal. Additional routes handle registration through /domains/register and DNS or availability status through /domains/status. This compact index is important because generated API documentation and SDK tooling can discover the whole domain family from one exported object.

Sources: apps/web/lib/openapi/domains/index.ts

The CLI exposes a narrower but very practical domain workflow. Its purpose is not to create or register domains; it configures which existing workspace domain the command-line experience should use. The command is named domains and described as Configure your workspace domain. When a user runs it, the command starts a spinner, fetches the available slugs, presents a selection prompt, validates that the selected slug is at least three characters, and persists the chosen value into the CLI configuration. That makes domain choice part of the local developer workflow rather than requiring every shortening command to ask again.

Sources: packages/cli/src/commands/domains.ts

The domain list used by the CLI is intentionally broader than only custom domains. getDomains initializes the generated Dub SDK with the saved access token, calls dub.domains.list(), and also requests https://api.dub.co/domains/default with the same bearer token. It then parses both responses, maps workspace domain objects to their slug values, appends default domains, and returns a de-duplicated slug list with Array.from(new Set(allSlugs)). This means the selection prompt can include both the user's custom domains and Dub-provided defaults.

Sources: packages/cli/src/api/domains.ts

API Reference Mapping

CapabilityRoute or commandSource-level entry pointNotes
List domainsGET /domainslistDomainsExposed through the OpenAPI domains path map.
Create domainPOST /domainscreateDomainDeclared as the collection-level create operation.
Update domainPATCH /domains/{slug}updateDomainUses slug as the path identifier.
Delete domainDELETE /domains/{slug}deleteDomainRemoves a domain by slug.
Register domainPOST /domains/registerregisterDomainSeparate registration operation from normal creation.
Check domain statusGET /domains/statuscheckDomainStatusUsed for status-oriented domain checks.
Configure CLI domaindub domainsdomains commandPrompts for and saves the selected workspace domain.

The API map also shows a design decision that consumers should preserve when extending the domain surface: lifecycle actions are split by intent. Normal CRUD operations live under /domains and /domains/{slug}, while registration and status checks are separate named routes. That keeps resource mutation, registrar-style behavior, and DNS or readiness checks distinguishable in generated docs and SDKs. If a new domain capability is added, it should follow this pattern by choosing whether it belongs on the domain resource itself or deserves a separate operation-specific path.

Sources: apps/web/lib/openapi/domains/index.ts

CLI Execution Flow

A typical command-line user first logs in, then configures a domain, and then shortens URLs. The CLI README lists dub login, dub config, dub domains, dub shorten [url] [key], and dub links [options] as available commands. For domain management, the important point is sequencing: dub domains relies on a stored access token because its API helper calls getConfig() and sends that token both to the generated Dub SDK and to the default-domain REST endpoint. Without a configured token, the domain fetch cannot represent the user's workspace.

Sources: packages/cli/README.md, packages/cli/src/api/domains.ts

The command itself is interactive. It uses ora to show Fetching domains, calls the internal showDomains helper, converts each slug into a prompt choice with title and value, and uses prompts to show a select prompt labeled Select a domain. Cancellation is handled explicitly: the command logs a blank line, warns You canceled the prompt., logs another blank line, and exits with status zero. Successful selection writes { domain: options.domain } through setConfig, marks the spinner as Done, and prints a green success message.

Sources: packages/cli/src/commands/domains.ts

For local development, the CLI README documents two useful modes. During active development, a contributor can enter packages/cli, run pnpm dev to build in watch mode, and then run pnpm start [command] from another terminal. For a production-like setup, the README instructs contributors to build with pnpm build, link the package globally with npm link, verify with dub -v, and then run dub [command]. These steps are the fastest way to test domain selection changes without publishing a package.

Sources: packages/cli/README.md

cd packages/cli
pnpm dev
pnpm start domains
cd packages/cli
pnpm build
npm link
dub domains

Operational Behavior and User Expectations

Custom domains involve DNS and TLS state that may not be immediately ready even when the domain exists as a resource. The official product guidance warns users about common setup failures such as redirect loops when Cloudflare proxying is enabled and temporary HTTPS errors while SSL certificates are still being generated. The repository evidence here does not implement that troubleshooting text, but it does show a dedicated GET /domains/status API path. That separation is a useful signal: callers should treat domain readiness as something to check explicitly rather than assuming creation and readiness are the same event.

Sources: apps/web/lib/openapi/domains/index.ts

The official docs also describe a custom not found URL, where a workspace can redirect visitors when a short link under a custom domain does not exist. In the source paths for this page, that behavior is represented only at the API-family level through the update route, not through a visible option schema. Developers should therefore avoid assuming the CLI domain command edits domain settings. The CLI command only chooses the domain stored in local config; domain settings such as not found behavior belong to dashboard or API update workflows.

Sources: apps/web/lib/openapi/domains/index.ts, packages/cli/src/commands/domains.ts

Proxy configuration is another operational constraint. Product docs recommend avoiding Cloudflare Proxy, CloudFront, or similar layers in front of Dub custom domains because they can distort geolocation analytics, add redirect latency, reduce reliability, and cause redirect-loop failures. That advice matters when interpreting domain API status and support reports: a domain may be present in /domains and selectable in the CLI, while its runtime redirect behavior is still affected by DNS provider configuration outside Dub. The API and CLI surfaces manage Dub state; DNS providers still control external routing.

Sources: apps/web/lib/openapi/domains/index.ts, packages/cli/src/api/domains.ts

Implementation Details for Contributors

When changing the Domains API, start with the OpenAPI path registry. The imports in apps/web/lib/openapi/domains/index.ts make the family boundary explicit: checkDomainStatus, createDomain, deleteDomain, listDomains, registerDomain, and updateDomain are the operation modules that feed the exported path object. If a route is renamed or moved, generated clients and documentation may also change. Contributors should keep the path map aligned with operation names so the public contract remains predictable for API users.

Sources: apps/web/lib/openapi/domains/index.ts

When changing the CLI, keep the distinction between fetching domains and configuring the selected domain. packages/cli/src/api/domains.ts is responsible for remote data access and response parsing. packages/cli/src/commands/domains.ts is responsible for terminal interaction, validation, persistence, and error handling. This split makes the command easier to reason about: failures from API calls can be routed through handleError, while validation failures stay close to the prompt. It also makes the getDomains helper reusable if another command needs the same slug list.

Sources: packages/cli/src/api/domains.ts, packages/cli/src/commands/domains.ts

The validation rule in the command is deliberately minimal: the selected slug must be a string with at least three characters. Because choices are generated from API results, the prompt normally receives valid slugs from Dub rather than arbitrary user input. If future CLI changes allow manual domain entry, this validation would need to become stricter. For the current implementation, it primarily guards prompt shape and prevents an empty or malformed saved config value from being written after selection.

Sources: packages/cli/src/commands/domains.ts

Next Steps

If you are implementing user-facing domain management, read the domain API operation modules imported by the OpenAPI path registry next, especially the create, update, status, and register operations. If you are improving the terminal experience, run the CLI locally with pnpm dev and pnpm start domains, then verify that selecting a custom or default domain updates the local config used by later shortening commands. For adjacent documentation, continue with the Domains API reference, CLI Workflows, and Manage Domains pages.