Skip to main content
The dns plugin extends datumctl with commands for creating DNS zones, pointing your domain at Datum, and editing records — all from the terminal. Once you install it, datumctl dns behaves like any other datumctl command and inherits your current organization, project, and credentials.
The plugin is a preview release. The current published version is v0.7.0-dev.1, so expect commands, flags, and output to change before a stable release.
For DNS concepts — zones, nameservers, record types, and ALIAS/CNAME flattening — see DNS. For how plugins are installed, verified, and upgraded in general, see Using plugins.

Install the plugin

1

Install from the official catalog

The plugin ships in the official datum catalog, so no extra catalog registration is needed:
This installs the version the catalog recommends. To pin a specific one instead, see pinning a version.
2

Verify the install

version runs entirely offline — no credentials, no project, and no API call — so it is the first command to run whenever something else fails.
3

Select a project

Every other command acts on a project. Set a context once, or pass --project per command:
The plugin never guesses a project. See Contexts & scoping for how scope is resolved.
Upgrade and remove the plugin with the usual commands:
If you build the plugin yourself and put a datumctl-dns binary on your PATH, datumctl blocks it until you run datumctl plugin trust dns. Trust is recorded against the binary’s fingerprint, so rebuilding it means trusting it again.

Create a zone

A zone holds the records for one domain. Create one by domain name:
The command waits up to two minutes for Datum to assign nameservers, then prints them — a zone is not usable until it has them, and you cannot delegate the domain without knowing what they are. Change that with --timeout, or skip the wait entirely:
A zone’s domain name is immutable. There is no zone update, and moving to a different domain means creating a new zone.

Point your domain at Datum

Creating a zone does not make the domain resolve through Datum. Your registrar has to publish the nameservers Datum assigned to the zone.
Copy the printed nameservers into your registrar’s control panel, then re-check:
Without --check, the command compares against what the control plane last reconciled. --check queries public DNS live, as a second and independent source of truth: it observes what the internet resolves right now rather than trusting stored state, which is what tells you whether delegation actually works. Use --timeout to change the per-query timeout. The command reports delegation as one of four states: Unknown is not a failure, and the summary line says which of its three causes applies:
  • no nameservers assigned yet — the zone does not have its nameservers yet. This is normal for the first few minutes after you create a zone.
  • the registrar's nameservers have not been checked yet — the zone has a linked domain, but nobody has looked at what the registrar publishes.
  • no linked domain to check the registrar against — there is no domain object to compare against at all.
None of the three is evidence about your registrar, so none of them means your delegation is wrong. For the same reason, individual nameservers are annotated unknown rather than not set at registrar while the state is Unknown. Registrar changes take time to propagate. Allow for the parent zone’s TTL before you treat a change as failed.

List and inspect zones

Filter with --status ok|pending|error, add -o wide for extra columns, and pass --no-headers to drop the header row. For one zone in detail — its status, its delegation, and what it contains — use describe:

Delete a zone

Deleting a zone deletes every record in it, and the domain stops resolving through Datum. The operator owns each record set through a controller ownerReference, so they are garbage-collected with the zone.
The confirmation asks for the zone name typed in full, and the command refuses to run non-interactively without --yes:

Add and change records

Give the zone, the name, the type, and one or more values. Names are relative to the zone: www, *, _dmarc, or @ for the domain itself. create appends — the values already at that name stay, and an exact duplicate is refused:
set overwrites — every value already at that name and type is removed and the ones you give take their place:
set is the “change my A record” verb and create is the “add a second A record” verb. They are separate because one command cannot express both intents safely. Both accept --dry-run to validate and show the change without applying it, and --wait to block until the DNS backend has programmed the record (--timeout bounds the wait). Two rules catch most mistakes:
  • Names are relative. Use www, not www.example.com. Use @ for the domain itself.
  • Targets are absolute. End every hostname inside a value with a dot: mail.example.com., not mail.

Enter record values

Flat types take their value positionally. Repeat the argument for several values:
Structured types are taught with named flags:
Both notations work for every type, so a value pasted out of a provider export or dig output needs no translation. Mixing the two notations for one value is an error, not a merge.
For long TXT values that will not survive shell quoting, --data reads a file with @path or standard input with -:
Datum manages a few records for you — the zone’s SOA, its apex NS records, and anything a Gateway creates. Editing or deleting one requires --force, and a platform-managed record you overwrite reverts.

Set a TTL

--ttl takes seconds or a duration, and the units combine: --ttl 300, --ttl 5m, --ttl 1h30m. Omit it and the record uses Auto, which the backend resolves to 300 seconds. TTLs are stored as written and are not rounded onto a preset ladder.

List and inspect records

Records are listed one row per value, with the name, type, TTL, value, and status:
--status accepts programmed, pending, conflict, not-owner, error, and rejected; the first word alone also works, so not selects Not owner. STATUS is the per-owner-name condition, not the rolled-up one on the record set — the interesting outcomes only exist per name. describe shows the values at one name both in presentation format and broken out into named fields, along with the backend’s own status message. Omit the type to see every type at that name:
A semicolon starts a comment in zone-file syntax, so TXT values containing one display escaped — v=DMARC1; p=none reads back as "v=DMARC1\; p=none". The stored value is unchanged.

Delete a record

Pass a value to remove only that value, or leave it off to remove every value at that name and type. The prompt says how many, so the difference is never a surprise.
When the last value of a type leaves a zone, the record set holding it is deleted rather than left empty.

Import an existing zone

Moving a domain in from another provider starts with the zone file you exported there:
If you do not have a zone file, snapshot what the domain resolves to today and import that:
Import groups records by type before writing, so each record type costs one API call however many records it holds. TTLs are taken from the file exactly as written. Use --timeout to bound how long --discover waits.

Export and apply a zone file

zone export flattens every record set in the zone back into a BIND zone file:
record apply is the declarative counterpart: it diffs a zone file against the live zone, prints what would change, and converges. Export, edit, and apply is a closed loop — exporting and re-applying an untouched file reports no changes, which makes apply --dry-run usable as a drift check.
By default apply only adds and updates. --prune also deletes the records the file does not mention. Platform-managed records are never pruned or modified, and what was skipped is always reported. There is no --force on apply — a zone file is not the place to say “yes, delete my delegation”, so use record delete --force where the record is named explicitly.
With --prune, the diff is computed from the zone as it was read. If another writer changes the zone while the command runs, the retry converges against the newer state and may delete a record the diff did not show. The dry run is a good description of the change, not an upper bound on it.

Script the plugin

Every command accepts -o:
record list -o json and -o yaml emit the raw record set objects rather than the flattened rows, so a script never has to reconstruct them. --no-headers drops the header row from table and wide. Data goes to standard output and everything else to standard error, so -o json > zones.json is always clean.
The table view is a presentation and its columns can change. Script against -o json or -o name.
Useful global flags: --project, --org, -o/--output, -v/--verbose, -q/--quiet, -y/--yes, and --color auto|always|never. For the wider scripting picture, see Output formats & scripting.

Exit codes

A bulk operation that partly fails never exits 0. Errors print a problem, an optional fix, and the exit status:
Add --verbose to see the underlying cause.

Troubleshoot

Next steps

DNS

Zones, nameservers, record types, and ALIAS/CNAME flattening explained.

Using plugins

How plugins are installed, verified, upgraded, and trusted.

Contexts & scoping

Control which organization and project the plugin acts on.

Output & scripting

Machine-readable output, structured errors, and CI patterns.
Last modified on August 26, 2026