Skip to content
DomainKit
Esc
↑↓navigate↵open⌘Jpreview
On this page

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 effect
pnpm add domainkit effect
yarn add domainkit effect
bun add domainkit effect
nub add domainkit effect
aube add domainkit effect
import { 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" };
});

Compare

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 Stale if 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

Last updated on

Was this page helpful?