Add custom domains to your app
Connect a DNS provider, review the exact records your product needs, apply only what the customer approved, and keep credentials and policy in your app.
DomainKit helps you add custom domains to a SaaS product without sending customers to copy-paste DNS instructions. It turns your product’s DNS requirements into a plan a customer can review, applies only the plan digest they approved, keeps a receipt of every write, and plans cleanup from that receipt. Cloudflare and Vercel are built in, and a provider is one declarative value.
Your app keeps identity, tenancy, credentials, storage, routes, consent, and audit. DomainKit supplies the lifecycle, not a hosted control plane.
npm install domainkit effectpnpm add domainkit effectyarn add domainkit effectbun add domainkit effectnub add domainkit effectaube add domainkit effectimport { Effect } from "effect";
import { DnsRecord, Provision, Verify } from "domainkit";
const requirements = [
DnsRecord.cname({
name: "app.example.com",
target: "edge.acme.dev",
purpose: "Serve your site",
}),
DnsRecord.txt({
name: "_acme.app.example.com",
value: "acme-verify=7f3a",
purpose: "Prove ownership",
}),
];
export const program = Effect.gen(function* () {
const plan = yield* Provision.plan({ domain: "app.example.com", requirements });
// Create the CNAME, no-op the TXT already in place, conflict on anything incompatible,
// and a digest over all of it for the customer to approve.
const approval = yield* Provision.approve(plan);
const receipt = yield* Provision.apply(approval);
// One outcome per operation. A write that fails after an earlier one lands is a `partial`
// receipt, not an exception, and retrying any step replays its result.
const readiness = yield* Verify.observe({ domain: "app.example.com" });
return { receipt, ready: readiness.overall === "ready" };
});Quickstart
Build, approve, and apply a plan in memory, without credentials.
Plans
How review, approval, apply, and receipts fit together.
Connections
Connect a provider without putting credentials in the browser.
Host integration
Mount the routes, write Identity, install storage.
React
A complete flow, or the controllers behind it.
Glossary
The shared vocabulary for the lifecycle, connections, observation, and host seams.
Custom domain setup
A step-by-step guide from declared records to verified DNS.
Email sending domains
SPF, DKIM, MX, and tracking records for your customers’ domains.
Custom domains on Vercel
Add the hostname with Vercel, then write the customer’s DNS records.
Samva case study
How an email API sets up customer domains with DomainKit.
Cloudflare
API tokens, or OAuth with refresh and revoke.
Vercel
Personal and team tokens, or a marketplace integration.
Compare
- DomainKit vs Entri
- Cloudflare for SaaS and DomainKit
- Domain Connect and DomainKit
- Copy-paste DNS instructions and where they break
The safety model
- Nothing is written before it is shown. A plan lists the creates, the exact matches, and the conflicts, with a digest over all of them.
- Apply accepts one approval. The approval names the digest and the exact operations; apply
re-plans first and fails
Staleif the zone moved. - Additive, fail closed. Missing records are created, exact records are no-ops, and incompatible state is a conflict. DomainKit never updates or deletes a record it did not create.
- Partial application is data. A failure after a confirmed write produces a receipt with
status: "partial"listing every operation, not an exception. - Cleanup is its own plan. It is built from an apply receipt, and a record that no longer matches is left alone.
Who owns what
| DomainKit | Your application |
|---|---|
| Building the plan and its digest | Which DNS records the product needs |
| Refusing an apply the customer did not approve | Identity, tenancy, consent, and audit policy |
| Cloudflare and Vercel integrations | The database and the encryption key |
| Provider readback and public DNS observation | Authenticated routes and product readiness |
| Cleanup bound to an apply receipt | When a customer may remove records |
| React flows and their contracts | Branding, layout, wording, and telemetry |
Entry points
| Package | Use it for |
|---|---|
domainkit |
Lifecycle services, host seams, providers, values, failures |
domainkit/server |
The mountable route group and its Web handler |
domainkit/client |
The browser transport over those routes |
domainkit/testing |
Fakes, a recording transport, and conformance runners |
@domainkit/react |
Headless React hooks for the flow, over your transport |
@domainkit/capsuledb |
Optional PostgreSQL persistence for Effect hosts |