The browser SDK that lets your SaaS users connect their own domain.
Status: Maintained · v0.5.1 · TypeScript · published to npm with provenance
Website · Docs · Console · Widget and SDK reference · Releases · Changelog
| What it is | Browser SDK and React bindings for the CustomDomain™ connect flow |
| Who it's for | SaaS teams whose users want acme.com, not acme.yourapp.com |
| Live at | customdomain.ai · docs at docs.customdomain.ai/docs |
| Stack | TypeScript · pnpm workspace · customdomain-js ships zero runtime dependencies |
| Status | Maintained · customdomain-js and @customdomain/react both at 0.5.1 on npm · every pull request builds the real bundle and drives it through headless Chromium |
This repository is the client half of CustomDomain™: two npm packages your application installs, plus the widget they open. Your app calls one function; the user pastes a domain; DNS records, ownership verification and TLS certificates are handled by the control plane. The backend, the edge and the certificate machinery live in a separate private repository.
Letting customers bring their own domain is one of the highest-leverage features a SaaS product
can ship: it is what turns "powered by us" into the customer's own brand. It is also a swamp.
Every DNS provider exposes a different write API, or none. Root domains cannot hold a CNAME
(RFC 1034), so half the internet's advice is
wrong for half of your users. Certificates have to be issued on demand and renewed forever. And
the failure mode is not an exception in your logs; it is a support ticket that says
"it still says pending."
CustomDomain™ turns that into a payment-method-shaped interaction: paste the domain, and either the provider is authorized in one click or the widget shows the exact records and watches for them. Of the 63 DNS and registrar providers catalogued today, 25 have an automatic write path; the rest use a guided manual flow with automatic verification. Those counts come from a live endpoint, and the Quickstart below shows you how to read it yourself.
npm install customdomain-js # framework agnostic
npm install @customdomain/react # React hooks and componentsOr skip the build step entirely and load the hosted bundle, which is always current:
<script src="https://app.customdomain.ai/widget-assets/customdomain-sdk.js"></script>import { customdomain } from "customdomain-js";
customdomain.open({
applicationId: "app_123",
token: TOKEN_FROM_YOUR_SERVER, // minted server side; never ship an API key to the browser
getToken: fetchFreshTokenFromYourServer, // optional: keeps long manual setups alive
});
window.addEventListener("customdomain:success", (e) => {
console.log("connected:", e.detail.domain); // e.g. "acme.com"
});Check the live provider census the numbers above come from:
curl -s https://api.customdomain.ai/v1/providers/census | head -c 200- Opens the connect widget.
customdomain.open(config)mounts it in a sandboxed iframe, themed and localized. - Detects the provider and writes the records. One-click authorization, a scoped provider API token, or Domain Connect, with a guided manual path as the fallback.
- Verifies ownership without a separate challenge step. Control is proven by the rail that writes the DNS, or by the records appearing in authoritative DNS.
- Handles root domains.
wwwRedirectconnects the root andwwwand redirects the root, connects the root alone, or lets the user choose. - Streams progress to your app.
customdomain:success,:step,:close,:error,:fallback,:purchaseand:sharedevents, pluscheckDomain()andcheckRecords()if you want to drive your own UI. - White-labels. Colors, fonts, copy, locale and logo, so the flow reads as part of your product.
.
├── packages/sdk/ # customdomain-js: window.customdomain, framework agnostic
├── packages/react/ # @customdomain/react: hooks and components over the SDK
├── packages/widget/ # the widget UI used by CI's browser simulation (private, never published)
├── scripts/ # verify-release.sh (npm serves what this repo says) · release-notes.sh
├── llms.txt # machine-readable index of this repo, for agents
└── .github/workflows/ # ci · release (tag driven, npm provenance) · sdk-drift (weekly)
Entry point: packages/sdk/src/index.ts. Per-package APIs:
packages/sdk/README.md · packages/react/README.md.
flowchart LR
A["Your app<br/>customdomain-js"] -->|open| B[Widget]
B -->|domain + token| C["customdomain.ai<br/>control plane"]
C -->|records| D["User's DNS provider"]
C -->|ACME| E["Let's Encrypt"]
D -.->|verified| C
E -.->|certificate| C
C -->|customdomain:success| A
The SDK never talks to a DNS provider directly. It opens the widget, the widget talks to the control plane, and your app listens for events.
pnpm install
pnpm -r build # react typechecks against the SDK's generated .d.ts, so build first
pnpm -r typecheck
pnpm sim # drives the built widget through headless Chromium, 6 scenariosCI runs all four on every pull request. The sim is a behavior gate, not a unit test: it walks the OAuth journey, the manual fallback, locale and white-label theming, sequential multi-domain, resume of a live domain, and share-link minting against a stub control plane with real wire shapes.
Semantic Versioning. The two published packages move in lockstep and share
one CHANGELOG.md. Every published version has a vX.Y.Z tag and a
GitHub release whose notes are
that version's CHANGELOG entry.
To release: sync the source from the product monorepo, bump both package.json files, add the
CHANGELOG section, merge, then push a vX.Y.Z tag. release.yml publishes both packages with npm
provenance, runs scripts/verify-release.sh against the live registry, and only then creates the
GitHub release. A separate weekly sdk-drift job compares this repo against the registry, because
a release that silently never fires produces no failing check anywhere else.
@customdomain/widgetisprivate: trueand served hosted from app.customdomain.ai. You cannot self-host that bundle, and the copy here exists for CI's browser simulation.- 38 of the 63 catalogued providers have no usable delegated write API, so their users get the guided manual flow. Verified against
GET https://api.customdomain.ai/v1/providers/censuson 2026-09-26: 63 catalogued, 17 provider API, 6 OAuth, 2 Domain Connect, 38 manual. - Widget tokens must be minted by your server. There is no browser-safe API key, by design.
- docs: source of docs.customdomain.ai
- customdomain-mcp: the hosted MCP server for AI agents
- custom-domain-checks: a GitHub App for continuous DNS and TLS health checks
- awesome-custom-domains: the map of the whole space, including the alternatives
- Docs: docs.customdomain.ai
- Questions and ideas: GitHub Discussions
- Bugs and corrections: open an issue on this repository
- Service status: status.customdomain.ai
- Account and billing: connect@customdomain.ai
- Security: report privately to security@customdomain.ai, never in a public issue. Policy: app.customdomain.ai/security
Apache-2.0. CustomDomain™ is a product of EverJust Company.