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

Cloudflare for SaaS and DomainKit work together

Cloudflare for SaaS issues certificates and routes customer hostnames. DomainKit writes the CNAME and validation records in the customer's DNS zone.

Last reviewed 2026-09-29. Cloudflare changes its docs and plans, so check them before you build. To correct something, open an issue on GitHub.

Verdict

Cloudflare for SaaS handles the custom hostname, the certificate, and the traffic. DomainKit handles the DNS records your customer has to add so that it works. You can use either alone. Together they cover both halves.

Who does what

Zone What lives there Handled by
Your Cloudflare account Custom hostname, certificate, fallback origin Cloudflare for SaaS
Your customer’s DNS zone CNAME app.customer.com pointing at your hostname, and the validation TXT records DomainKit, after the customer approves

Two zones. Cloudflare for SaaS works in yours. DomainKit works in theirs.

What Cloudflare for SaaS does

Cloudflare for SaaS lets you serve your customers’ own hostnames, such as app.customer.com, through Cloudflare. It issues and renews the certificates, routes the traffic, and gives you an API to create a custom hostname for each customer (1).

On the Free, Pro, and Business plans, Cloudflare’s docs list 100 custom hostnames included and $0.10 for each one after that. Enterprise is priced by contract (2).

That’s a solid product for the part it covers. It doesn’t write into your customer’s DNS.

What the customer still has to do

Cloudflare’s own docs tell you to have the customer add a CNAME that points their hostname at yours (5). When you validate the hostname ahead of time, they also add TXT records that prove ownership and validate the certificate (4).

Type Name Value
CNAME app.customer.com customers.yourapp.com
TXT The name Cloudflare returns, such as _cf-custom-hostname.app.customer.com The value Cloudflare returns

Those are the fields people copy into a registrar by hand. This is where tickets come from.

Where DomainKit fits

Declare the records Cloudflare gave you as requirements. The customer connects their DNS account, reviews the exact records, and approves. DomainKit writes them and checks that they resolve.

  1. Create the custom hostname with Cloudflare’s API. Read back the validation records: the ownership_verification record and every TXT entry in ssl.validation_records.

  2. Declare the CNAME and every TXT record as requirements.

    /**
     * What Cloudflare told you to have the customer add for one custom hostname. `validation` holds
     * every TXT record Cloudflare returned: the `ownership_verification` record and each TXT entry in
     * `ssl.validation_records`.
     */
    export const requirements = (input: {
      readonly hostname: string;
      readonly target: string;
      readonly validation: ReadonlyArray<{
        readonly name: string;
        readonly value: string;
      }>;
    }) => [
      DnsRecord.cname({
        name: input.hostname,
        target: input.target,
        purpose: "Send traffic to your app",
      }),
      ...input.validation.map((record) =>
        DnsRecord.txt({
          name: record.name,
          value: record.value,
          purpose: "Prove you own the domain",
        }),
      ),
    ];
  3. The customer connects Cloudflare or Vercel, reviews the plan, and approves.

    export const connectOAuth = Effect.map(
      Connect.start({
        provider: "cloudflare",
        method: Connect.Method.oauth({ returnTo: "/settings/domains" }),
        domain: "app.example.com",
      }),
      (started) => (started._tag === "Redirect" ? started.authorizationUrl : null),
    );
    /** Approval binds consent to the digest. Without `operationIds` it covers every write. */
    export const approveEverything = (plan: Plan.Model) => Provision.approve(plan);
    
    /** Partial approval names the operations and admits that conflicts stay behind. */
    export const approveSome = (plan: Plan.Model) =>
      Provision.approve(plan, {
        operationIds: Plan.writes(plan)
          .slice(0, 1)
          .map((operation) => operation.id),
        allowPartial: true,
      });
  4. DomainKit applies the plan and observes the records in public DNS.

    /**
     * One call reads the provider through the attachment's session and the public pool through
     * `Resolver`, stores readiness per requirement, and says when to look again.
     */
    export const check = Effect.map(Verify.observe({ domain }), (readiness) => ({
      ready: readiness.overall === "ready",
      nextCheckAt: readiness.nextCheckAt,
      pending: readiness.requirements
        .filter((requirement) => requirement.status !== "satisfied")
        .map((requirement) => `${requirement.record._tag} ${requirement.record.name}`),
    }));
  5. Show the certificate as pending on the same readiness screen until Cloudflare marks it active.

    /** Merge what only your app can see — an email identity, a certificate — without re-reading DNS. */
    export const recordCertificate = Effect.gen(function* () {
      const observedAt = yield* DateTime.now;
      return yield* Verify.attachEvidence({
        domain,
        evidence: [
          new Verify.HostEvidence({
            source: "edge-certificate",
            status: "pending",
            label: "TLS certificate",
            detail: "Issuance starts once the CNAME resolves",
            observedAt,
          }),
        ],
      });
    });

The customer’s DNS can be on Cloudflare too. Their account is separate from yours.

Hosting on Vercel? The shape is the same. Vercel’s Domains API attaches the domain to your project and issues the certificate (6). DomainKit’s Vercel adapter writes the customer’s DNS records when their domain’s DNS is hosted at Vercel. See the Vercel provider page.

What DomainKit doesn’t do

DomainKit doesn’t create custom hostnames, issue certificates, or proxy traffic. It doesn’t replace Cloudflare for SaaS. It can only write to a customer whose DNS is at Cloudflare or Vercel. For anyone else, your app still shows the records to add by hand.

The full path from declaring records to cleanup is in SaaS custom domain setup, step by step.

FAQ

Do I need Cloudflare for SaaS to use DomainKit?

No. DomainKit writes DNS records for any product that needs them, such as email sending domains. Cloudflare for SaaS is one reason to need them.

Does DomainKit create the custom hostname in Cloudflare?

No. You do that with Cloudflare’s API. DomainKit writes what the customer has to add on their side.

Does DomainKit handle certificate issuance?

No. Cloudflare issues and renews the certificate. You can show its status next to the DNS status as host evidence.

What if the customer isn't on Cloudflare or Vercel?

Then DomainKit can’t write their records today. Show them the CNAME and TXT values to add by hand.

Sources

All accessed 29 September 2026.

  1. https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/
  2. https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/plans/
  3. https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/create-custom-hostnames/
  4. https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/hostname-validation/
  5. https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/hostname-validation/realtime-validation/
  6. https://vercel.com/docs/multi-tenant/domain-management

Last updated on September 29, 2026

Was this page helpful?