Let your users bring their own domain — acme.com instead of acme.yourapp.com —
without writing a line of DNS.
Custom domains are one of the highest-leverage features a SaaS product can ship — they're
what turns "powered by [you]" into the customer's own brand. They're also a swamp: dozens of DNS
providers with incompatible record types, apex domains that can't hold a CNAME (RFC 1034),
ACME certificate issuance, propagation delays, and a support queue full of "it says pending."
customdomain-sdk is the client side of customdomain.ai — drop
a widget into your settings page, and your users connect a domain the way they'd connect a
payment method: paste it, follow three copy-paste DNS steps (or none, for the ~30 registrars
we can configure automatically via Domain Connect), done.
DNS verification, SSL issuance, and renewal happen behind the scenes.
| Package | npm | What it is |
|---|---|---|
packages/sdk |
customdomain-js |
The browser SDK — window.customdomain, opens the widget, streams connect events. Framework-agnostic. |
packages/react |
@customdomain/react |
A thin React wrapper — hooks and components around the SDK. |
packages/widget |
@customdomain/widget (private) |
The embeddable widget UI itself. Builds to the bundle the SDK loads; not published to npm — served hosted so it's always current. |
<!-- Hosted (recommended): always current, zero build step -->
<script src="https://app.customdomain.ai/widget-assets/customdomain-sdk.js"></script>npm install customdomain-js # or: npm install @customdomain/reactwindow.customdomain.open({
applicationId: "app_123",
token: TOKEN_FROM_YOUR_SERVER, // minted server-side — never ship your API key to the browser
});
window.addEventListener("customdomain:success", (e) => {
console.log("connected:", e.detail.domain);
});Full API, events, and the React hooks: packages/sdk/README.md ·
packages/react/README.md.
flowchart LR
A["Your app<br/>(customdomain-js / @customdomain/react)"] -->|opens| B[Widget]
B -->|domain + token| C["customdomain.ai<br/>control plane"]
C -->|DNS records| D["User's DNS provider<br/>(Domain Connect or manual)"]
C -->|ACME| E[Let's Encrypt]
D -.->|verified| C
E -.->|certificate| C
C -->|customdomain:success| A
The SDK never talks to DNS providers directly — it opens the widget, which talks to the control plane, which handles verification and certificates. Your app just listens for events.
pnpm install
pnpm -r build # build all packages
pnpm -r typecheck
pnpm sim # drive the real widget bundle through headless ChromiumCI (.github/workflows/ci.yml) runs typecheck + build + the Chromium behavior sim on every PR.
Publishing is automated with npm provenance (the verifiable "published from this repo"
signal on npmjs.com — our npm↔GitHub link). Bump versions, push a vX.Y.Z tag, and
.github/workflows/release.yml publishes customdomain-js +
@customdomain/react. Auth is npm Trusted Publishing (tokenless) or an NPM_TOKEN repo
secret — configured on npmjs.com, never in source. See the workflow header for setup.
- Product & docs: https://app.customdomain.ai/docs
- API reference: https://app.customdomain.ai/docs (also machine-readable at
llms.txt) - License: Apache-2.0
Backend, control plane, and infrastructure live in a separate private repository. This repo contains only the public browser clients — the part you actually
npm install.