Skip to content

Cloudflare.DNS reference

Source: src/Cloudflare/DNS/AccountSettings.ts

The DNS settings of a Cloudflare account (/accounts/{account_id}/dns_settings) — the account-wide enforceDnsOnly override and the default DNS settings applied to every new zone (zoneDefaults).

The settings object is a per-account singleton — it always exists with Cloudflare defaults, so this resource never creates or deletes anything physical. Reconcile patches only the fields you declare (and only when the observed value differs); destroy restores the managed fields to the values they had before Alchemy first touched the account (captured as initialSettings).

Some fields are plan-gated: zoneDefaults.nsTtl and custom SOA values require the custom nameserver TTL / custom SOA entitlements, foundationDns is a paid add-on, and internalDns is Enterprise Internal DNS only.

AccountDnsSettings: Account-wide overrides

Section titled “AccountDnsSettings: Account-wide overrides”
yield* Cloudflare.DNS.AccountDnsSettings("DnsSettings", {
enforceDnsOnly: true,
});

Flatten CNAMEs in every new zone

yield* Cloudflare.DNS.AccountDnsSettings("DnsSettings", {
zoneDefaults: { flattenAllCnames: true },
});

Default new zones to multi-provider DNS

yield* Cloudflare.DNS.AccountDnsSettings("DnsSettings", {
zoneDefaults: { multiProvider: true },
});

Source: src/Cloudflare/DNS/Dnssec.ts

DNSSEC configuration for a Cloudflare zone (/zones/{zone_id}/dnssec).

DNSSEC is a per-zone singleton — it always exists in either an enabled or disabled state, so this resource never creates or deletes anything physical. Reconcile patches the configuration toward the desired state; destroy restores the state the zone had before Alchemy first managed it (enabled stays enabled, previously-disabled zones are deactivated again).

Activation is eventually consistent: after enabling, Cloudflare reports pending until the ds attribute (the DS record) is submitted at the domain’s registrar. The reconciler polls with bounded retries for the zone to leave the disabled state but does not wait for full active — that depends on the registrar.

Safety: when there is no prior state and DNSSEC is already enabled on the zone, read reports it as Unowned and the engine refuses to take it over unless --adopt (or adopt(true)) is set.

Sign the zone

const dnssec = yield* Cloudflare.DNS.Dnssec("ZoneDnssec", {
zoneId: zone.zoneId,
});
// Paste `dnssec.ds` at your registrar to complete activation.

Multi-signer DNSSEC

yield* Cloudflare.DNS.Dnssec("ZoneDnssec", {
zoneId: zone.zoneId,
dnssecMultiSigner: true,
});
yield* Cloudflare.DNS.Dnssec("ZoneDnssec", {
zoneId: zone.zoneId,
status: "disabled",
});

Source: src/Cloudflare/DNS/Firewall.ts

A Cloudflare DNS Firewall cluster.

DNS Firewall sits in front of your authoritative DNS infrastructure, caching responses on Cloudflare’s anycast network and shielding the upstream nameservers from attack traffic. Creating a cluster assigns a set of Cloudflare anycast IPs (dnsFirewallIps) that you point NS glue records at; queries hitting those IPs are answered from cache or forwarded to your upstreamIps.

DNS Firewall is a paid add-on (typically Enterprise / contract). On accounts without the entitlement, creation fails with the typed DnsFirewallNotEntitled error (Cloudflare error code 10101).

All settings are mutable in place; only name (the cold-state recovery identity) triggers a replacement.

Basic cluster

const cluster = yield* Cloudflare.DNS.Firewall("dns-shield", {
upstreamIps: ["192.0.2.1", "192.0.2.2"],
});
// Point NS glue records at the assigned anycast IPs:
const ips = cluster.dnsFirewallIps;

Tuned caching and attack mitigation

const cluster = yield* Cloudflare.DNS.Firewall("dns-shield", {
upstreamIps: ["192.0.2.1"],
minimumCacheTtl: 120,
maximumCacheTtl: 3600,
negativeCacheTtl: 300,
ratelimit: 600,
retries: 2,
attackMitigation: {
enabled: true,
onlyWhenUpstreamUnhealthy: true,
},
});
const cluster = yield* Cloudflare.DNS.Firewall("dns-shield", {
upstreamIps: ["192.0.2.1"],
reverseDns: {
"203.0.113.1": "ns1.example.com",
},
});

Source: src/Cloudflare/DNS/ReadDns.ts

Binding that lets a Worker read Cloudflare DNS records at runtime.

Creates a least-privilege AccountApiToken with only the DNS Read permission, scoped to the single zone passed to bind, and binds its value into the Worker so runtime code can authenticate.

Bind the client in the Worker’s Init phase and provide ReadDnsBinding. The zone is fixed by ReadDnsBinding(zone) — the provisioned token only grants access to that zone, so calls take no zoneId. Pass the Zone resource directly (it’s an Effect), or yield* Zone for a resolved value.

import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
const Zone = Cloudflare.Zone.Zone("MyZone", { name: "example.com" });
export class ReadDnserWorker extends Cloudflare.Worker<ReadDnserWorker>()(
"ReadDnserWorker",
{ main: import.meta.url },
Effect.gen(function* () {
// Init phase — bind the read client scoped to the zone.
const dns = yield* Cloudflare.DNS.ReadDns(Zone);
return {
fetch: Effect.gen(function* () {
const { result } = yield* dns.listDnsRecords({ type: "A" });
const record = yield* dns.getDnsRecord(result[0].id);
return yield* HttpServerResponse.json({ id: record.id });
}),
};
}).pipe(Effect.provide(Cloudflare.DNS.ReadDnsBinding)),
) {}

Source: src/Cloudflare/DNS/ReadWriteDns.ts

Binding that lets a Worker perform the full Cloudflare DNS record CRUD surface at runtime.

Creates a least-privilege AccountApiToken with both the DNS Read and DNS Write permissions, scoped to the single zone passed to bind, and binds its value into the Worker so runtime code can authenticate.

ReadWriteDns: Managing DNS records at runtime

Section titled “ReadWriteDns: Managing DNS records at runtime”

Bind the client in the Worker’s Init phase and provide ReadWriteDnsBinding. The zone is fixed by ReadWriteDnsBinding(zone) — the provisioned token only grants access to that zone, so calls take no zoneId. Pass the Zone resource directly (it’s an Effect), or yield* Zone for a resolved value.

import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
const Zone = Cloudflare.Zone.Zone("MyZone", { name: "example.com" });
export class Worker extends Cloudflare.Worker<Worker>()(
"Worker",
{ main: import.meta.url },
Effect.gen(function* () {
// Init phase — bind the full CRUD client scoped to the zone.
const dns = yield* Cloudflare.DNS.ReadWriteDns(Zone);
return {
fetch: Effect.gen(function* () {
const { result } = yield* dns.createDnsRecord({
type: "A",
name: "app.example.com",
content: "192.0.2.1",
ttl: 1,
});
const record = yield* dns.getDnsRecord(result.id);
yield* dns.deleteDnsRecord(result.id);
return yield* HttpServerResponse.json({ id: record.id });
}),
};
}).pipe(Effect.provide(Cloudflare.DNS.ReadWriteDnsBinding)),
) {}

Source: src/Cloudflare/DNS/Record.ts

A single DNS record on a Cloudflare-managed zone.

Safety: when there is no prior state, read scans the zone for an existing (name, type) match. DNS records carry no ownership markers we can inspect, so an existing match is reported as Unowned and the engine refuses to take it over unless --adopt (or adopt(true)) is set. This protects hand-edited records (especially the apex A/AAAA and email DKIM/SPF records that the dashboard often manages) from being clobbered.

Several records may legitimately share (name, type) — MX fallbacks, multiple TXT records, round-robin A records. When the scan finds more than one candidate, the declared content (and priority) must match exactly one record; a record with no exact match is treated as missing (a new sibling record is created), and a still-ambiguous match fails with an error listing the candidates. To adopt one record out of such a set, declare its current content/priority verbatim first, then change them in a follow-up deploy.

Record: Proxied CNAME pointing at a tunnel

Section titled “Record: Proxied CNAME pointing at a tunnel”
yield* Cloudflare.DNS.Record("AdminCname", {
zoneId: zone.zoneId,
name: "cluster-admin.example.com",
type: "CNAME",
content: `${tunnel.tunnelId}.cfargotunnel.com`,
proxied: true,
comment: "research admin UI",
});
yield* Cloudflare.DNS.Record("ApiA", {
zoneId: zone.zoneId,
name: "api.example.com",
type: "A",
content: "203.0.113.42",
ttl: 300,
});

Record: Structured service binding records

Section titled “Record: Structured service binding records”

SVCB record

yield* Cloudflare.DNS.Record("McpSvcb", {
zoneId: zone.zoneId,
name: "_mcp._agents.example.com",
type: "SVCB",
content: {
priority: 1,
target: "mcp.example.com.",
value: 'mandatory="alpn,port" alpn="h2,h3" port="443"',
},
});

HTTPS record

yield* Cloudflare.DNS.Record("WebsiteHttps", {
zoneId: zone.zoneId,
name: "example.com",
type: "HTTPS",
content: {
priority: 1,
target: ".",
value: 'alpn="h2,h3"',
},
});

Source: src/Cloudflare/DNS/View.ts

An Internal DNS view (/accounts/{account_id}/dns_settings/views) — a named set of internal zones that DNS queries can be resolved against, for split-horizon / internal DNS setups.

Requires the Enterprise Internal DNS entitlement on the account (creation fails with InternalDnsNotAvailable otherwise). Both name and zones are mutable in place.

View over internal zones

const view = yield* Cloudflare.DNS.View("Internal", {
zones: [internalZone.zoneId],
});

View with an explicit name

const view = yield* Cloudflare.DNS.View("Internal", {
name: "datacenter-east",
zones: [zoneA.zoneId, zoneB.zoneId],
});

Source: src/Cloudflare/DNS/WriteDns.ts

Binding that lets a Worker create, update, and delete Cloudflare DNS records at runtime.

Creates a least-privilege AccountApiToken with only the DNS Write permission, scoped to the single zone passed to bind, and binds its value into the Worker so runtime code can authenticate.

Create, update, and delete records from inside a Worker

Bind the client in the Worker’s Init phase and provide WriteDnsBinding. The zone is fixed by WriteDnsBinding(zone) — the provisioned token only grants access to that zone, so calls take no zoneId. Pass the Zone resource directly (it’s an Effect), or yield* Zone for a resolved value.

import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
const Zone = Cloudflare.Zone.Zone("MyZone", { name: "example.com" });
export class WriteDnsrWorker extends Cloudflare.Worker<WriteDnsrWorker>()(
"WriteDnsrWorker",
{ main: import.meta.url },
Effect.gen(function* () {
// Init phase — bind the write client scoped to the zone.
const dns = yield* Cloudflare.DNS.WriteDns(Zone);
return {
fetch: Effect.gen(function* () {
const { result } = yield* dns.createDnsRecord({
type: "A",
name: "app.example.com",
content: "192.0.2.1",
ttl: 1,
proxied: true,
});
yield* dns.updateDnsRecord(result.id, {
type: "A",
name: "app.example.com",
content: "192.0.2.2",
ttl: 1,
});
yield* dns.deleteDnsRecord(result.id);
return yield* HttpServerResponse.json({ id: result.id });
}),
};
}).pipe(Effect.provide(Cloudflare.DNS.WriteDnsBinding)),
) {}

Apply a batch of changes atomically

yield* dns.batchDnsRecords({
posts: [{ type: "A", name: "a.example.com", content: "192.0.2.1", ttl: 1 }],
deletes: [{ id: oldRecordId }],
});

Source: src/Cloudflare/DNS/ZoneSettings.ts

The DNS settings of a Cloudflare zone (/zones/{zone_id}/dns_settings) — nameserver assignment, NS TTL, SOA components, CNAME flattening, multi-provider mode, and zone mode.

The settings object is a per-zone singleton — it always exists with Cloudflare defaults, so this resource never creates or deletes anything physical. Reconcile patches only the fields you declare (and only when the observed value differs); destroy restores the managed fields to the values they had before Alchemy first touched the zone (captured as initialSettings).

Some fields are plan-gated: foundationDns is a paid add-on, nameservers.type: "custom.*" requires account custom nameservers, internalDns and secondaryOverrides are Enterprise features.

Lower the NS record TTL

yield* Cloudflare.DNS.ZoneDnsSettings("DnsSettings", {
zoneId: zone.zoneId,
nsTtl: 3600,
});

Flatten every CNAME in the zone

yield* Cloudflare.DNS.ZoneDnsSettings("DnsSettings", {
zoneId: zone.zoneId,
flattenAllCnames: true,
});
yield* Cloudflare.DNS.ZoneDnsSettings("DnsSettings", {
zoneId: zone.zoneId,
soa: { minTtl: 300 },
});
yield* Cloudflare.DNS.ZoneDnsSettings("DnsSettings", {
zoneId: zone.zoneId,
multiProvider: true,
});

Source: src/Cloudflare/DNS/ZoneTransferAcl.ts

A Secondary DNS zone-transfer ACL (/accounts/{account_id}/secondary_dns/acls) — an account-wide IPv4/IPv6 range that may receive NOTIFYs for secondary zones and from which Cloudflare accepts AXFR/IXFR requests for outgoing transfers.

Requires the Secondary DNS (zone transfer) entitlement on the account. Both name and ipRange are mutable in place.

Allow a primary nameserver range

const acl = yield* Cloudflare.DNS.ZoneTransferAcl("PrimaryNs", {
ipRange: "192.0.2.48/28",
});

ACL with an explicit name

const acl = yield* Cloudflare.DNS.ZoneTransferAcl("PrimaryNs", {
name: "primary-nameservers",
ipRange: "2001:db8::/64",
});

Source: src/Cloudflare/DNS/ZoneTransferIncoming.ts

The incoming zone-transfer configuration of a secondary zone (/zones/{zone_id}/secondary_dns/incoming) — links the zone to the peers Cloudflare transfers it in from and sets the auto-refresh interval.

Requires the Secondary DNS (zone transfer) entitlement, and the zone must be created with type: "secondary". The configuration is a per-zone singleton: zoneId is the identity (replacement on change), everything else is mutable in place.

ZoneTransferIncoming: Configuring incoming transfers

Section titled “ZoneTransferIncoming: Configuring incoming transfers”
const peer = yield* Cloudflare.DNS.ZoneTransferPeer("Primary", {
ip: "192.0.2.53",
port: 53,
});
yield* Cloudflare.DNS.ZoneTransferIncoming("Incoming", {
zoneId: zone.zoneId,
name: "example.com.",
peers: [peer.peerId],
autoRefreshSeconds: 86400,
});

Source: src/Cloudflare/DNS/ZoneTransferOutgoing.ts

The outgoing zone-transfer configuration of a primary zone (/zones/{zone_id}/secondary_dns/outgoing) — links the zone to the peers Cloudflare NOTIFYs and serves AXFR/IXFR to, and toggles transfers on or off via the dedicated enable/disable endpoints.

Requires the Secondary DNS (zone transfer) entitlement on the zone. The configuration is a per-zone singleton: zoneId is the identity (replacement on change), everything else is mutable in place.

ZoneTransferOutgoing: Configuring outgoing transfers

Section titled “ZoneTransferOutgoing: Configuring outgoing transfers”

Serve a primary zone to an external secondary

const peer = yield* Cloudflare.DNS.ZoneTransferPeer("Secondary", {
ip: "192.0.2.53",
port: 53,
});
yield* Cloudflare.DNS.ZoneTransferOutgoing("Outgoing", {
zoneId: zone.zoneId,
name: "example.com.",
peers: [peer.peerId],
});

Configure transfers but keep them disabled

yield* Cloudflare.DNS.ZoneTransferOutgoing("Outgoing", {
zoneId: zone.zoneId,
name: "example.com.",
peers: [peer.peerId],
enabled: false,
});

Source: src/Cloudflare/DNS/ZoneTransferPeer.ts

A Secondary DNS zone-transfer peer (/accounts/{account_id}/secondary_dns/peers) — an external nameserver Cloudflare exchanges zone transfers with. Link peers to a zone via ZoneTransferIncoming (secondary zones) or ZoneTransferOutgoing (primary zones).

Requires the Secondary DNS (zone transfer) entitlement on the account. Cloudflare’s create API only accepts a name; the provider follows up with an update when ip, port, tsigId, or ixfrEnable are declared, all of which remain mutable in place.

Primary nameserver to transfer from

const peer = yield* Cloudflare.DNS.ZoneTransferPeer("Primary", {
ip: "192.0.2.53",
port: 53,
});

Peer with TSIG authentication

const tsig = yield* Cloudflare.DNS.ZoneTransferTsig("TransferKey", {
algo: "hmac-sha512.",
secret: Redacted.make(process.env.TSIG_SECRET!),
});
const peer = yield* Cloudflare.DNS.ZoneTransferPeer("Primary", {
ip: "192.0.2.53",
tsigId: tsig.tsigId,
ixfrEnable: true,
});

Source: src/Cloudflare/DNS/ZoneTransferTsig.ts

A Secondary DNS TSIG key (/accounts/{account_id}/secondary_dns/tsigs) — shared-secret authentication for zone transfers between Cloudflare and external nameservers. Reference it from a ZoneTransferPeer via tsigId.

Requires the Secondary DNS (zone transfer) entitlement on the account. All fields are mutable in place; the secret is redacted and never persisted in attributes.

const tsig = yield* Cloudflare.DNS.ZoneTransferTsig("TransferKey", {
algo: "hmac-sha512.",
secret: Redacted.make(process.env.TSIG_SECRET!),
});
const peer = yield* Cloudflare.DNS.ZoneTransferPeer("Primary", {
ip: "192.0.2.53",
port: 53,
tsigId: tsig.tsigId,
});