Skip to content

Repository files navigation

namecheap-cli

A cross-platform CLI tool for managing Namecheap DNS records.

Installation

From source

cargo install --path .

From crates.io

cargo install namecheap-cli

Configuration

Interactive setup

namecheap auth login

You are prompted for your API user and key; the key is typed or pasted without being echoed. The credentials are checked against the API before anything is saved, so a mistyped key is never stored.

Where the API key is kept

A Namecheap API key is full control of the account's domains and DNS, so it is not written to the config file. auth login stores it in the OS keychain — Keychain on macOS, Credential Manager on Windows, Secret Service on Linux — where it is encrypted at rest and scoped to your login session. Each profile gets its own entry, so --profile work and --profile personal stay separate.

The config file then holds no secret at all, which makes it safe to sync or commit:

[profiles.default]
api_user = "your-api-user"
sandbox = false

When a key is needed, it is looked for in this order:

Order Source For
1 NAMECHEAP_API_KEY CI and one-off overrides — no stored state needed
2 OS keychain The normal case, written by auth login
3 api_key in the config file Profiles saved before the keychain was used

auth status tells you which one answered:

$ namecheap auth status
✓ Authenticated
  Profile: default
  API User: your-api-user
  API Key: from your keychain

Managing stored credentials:

# Move keys out of an existing plaintext config and into the keychain
namecheap auth migrate

# Remove a profile's key from both the keychain and the config file
namecheap auth logout --profile default

# Machines with no keychain (headless Linux): keep it in the config file
namecheap auth login --store file

On a machine with no keychain, auth login says so and falls back to the config file rather than failing. The config file is always written 0600, and auth status warns if it is readable by anyone else or still holds a plaintext key.

Environment variables

export NAMECHEAP_API_USER="your-api-user"
export NAMECHEAP_API_KEY="your-api-key"
export NAMECHEAP_USERNAME="your-username"  # Optional, defaults to API user
export NAMECHEAP_CLIENT_IP="your-ip"       # Optional, auto-detected
export NAMECHEAP_SANDBOX="true"            # Optional, use sandbox API
export NAMECHEAP_BACKUP_DIR="/path/to/dir" # Optional, where zone snapshots are kept
export NAMECHEAP_API_URL="https://..."     # Optional, override the API endpoint

Config file

Configuration is stored in ~/.config/namecheap-cli/config.toml, written 0600:

default_profile = "default"

[profiles.default]
api_user = "your-api-user"
username = "your-username"
sandbox = false

[profiles.sandbox]
api_user = "your-api-user"
sandbox = true

Note there is no api_key — it lives in the keychain. An api_key here still works for profiles saved before that, and supports environment expansion (api_key = "${NAMECHEAP_API_KEY}"), but auth migrate will move it.

Usage

Authentication

# Configure credentials interactively
namecheap auth login

# Check authentication status
namecheap auth status

# Show current user info and balance
namecheap auth whoami

Domain Management

# List all domains
namecheap domains list

# Get domain info
namecheap domains info example.com

# Check domain availability
namecheap domains check example.com example.org

# Only domains expiring soon, soonest first
namecheap domains list --expiring 30

# Lock a domain against transfer
namecheap domains lock example.com
namecheap domains unlock example.com

# The lock state is shown by `domains info`, not `domains list` — Namecheap's
# listing reports IsLocked="false" for domains that are in fact locked, so the
# lock is read from getRegistrarLock separately
namecheap domains info example.com

# Renew (charges your Namecheap account, so it always confirms)
namecheap domains renew example.com --years 2

# What a TLD costs — the price this account pays, not the list price
namecheap domains pricing com
namecheap domains pricing .io --action renew
namecheap domains pricing com --action transfer --years 1

Without a TLD, pricing returns every TLD Namecheap sells, which is a long list. --action is one of register, renew, transfer or reactivate.

DNS Records

# List DNS records
namecheap dns list example.com

# Filter by type
namecheap dns list example.com -t A

# Add a record
namecheap dns add example.com A @ 1.2.3.4

# Add several values at once (one record per value, written in a single call)
namecheap dns add example.com A @ 185.199.108.153,185.199.109.153,185.199.110.153,185.199.111.153
namecheap dns add example.com AAAA @ 2606:50c0:8000::153,2606:50c0:8001::153

# Add MX record with priority
namecheap dns add example.com MX @ mail.example.com --priority 10

# Set (replace) a record
namecheap dns set example.com A @ 5.6.7.8

# Set replaces the whole set for that type + host
namecheap dns set example.com A @ 185.199.108.153,185.199.109.153

# Remove a record
namecheap dns rm example.com A @

# Remove specific value
namecheap dns rm example.com A @ 1.2.3.4

# Edit the whole zone in your editor
namecheap dns edit example.com

# Export records
namecheap dns export example.com --format json
namecheap dns export example.com --format zone

# Show diff between current and desired
namecheap dns diff example.com records.json

# Import records from a file (sync and import are the same command)
namecheap dns sync example.com records.json
namecheap dns import example.com records.json
namecheap dns import example.com zone.txt

# Make the zone match the file exactly, removing anything not in it
namecheap dns sync example.com records.json --delete

# Clone every record from another domain
namecheap dns clone old-domain.com new-domain.com

# Clone just the records you need (e.g. a GitHub Pages setup)
namecheap dns clone old-domain.com new-domain.com -t A,AAAA
namecheap dns clone old-domain.com new-domain.com -t CNAME --host www

# Make the target an exact mirror, dropping records the source does not have
namecheap dns clone old-domain.com new-domain.com --delete

clone copies records verbatim — same host, value, TTL and priority — then shows the diff against the target and asks before applying, so you can review it first (or use --dry-run to only see the diff). Values are not rewritten: anything naming the source domain (a redirect target, a mail host, a verification token) is copied as-is and listed as a warning for you to fix afterwards. Narrow the copy with -t/--host, and add --delete to remove target records the source lacks.

add and set accept a comma-separated list for the types that can legitimately hold several values at one host — A, AAAA, MX and NS — creating one record per value in a single zone write. Every other type takes its value verbatim: TXT, CAA, SRV and the redirect types because a comma can be part of a single legitimate value, and CNAME and ALIAS because a host can only have one of them (a comma in those is reported as an invalid hostname).

add skips values already present, so re-running it will not duplicate records; set replaces every record for that type and host with the values given.

Validation

Every command that writes DNS checks its records first and refuses to write anything if they would be invalid, explaining both the cause and the remedy:

$ namecheap dns add example.com A @ 1.2.3.400
✗ Invalid record
A @ 1.2.3.400 1800
  Problem: "1.2.3.400" is not a valid IPv4 address — each of the four parts must be a number from 0 to 255
  Fix:     Use an address such as 185.199.108.153. To point at a hostname instead, use CNAME (on a subdomain) or ALIAS (at the domain root).

Error: Validation error: 1 record(s) would be invalid — nothing was changed

Checks run on your input before any API call, then again on the zone the write would produce, so conflicts with records already in the zone are caught too. What is checked:

Area Examples
Address values A holds IPv4 and AAAA holds IPv6 (each suggests the other type when swapped)
Targets CNAME, ALIAS, NS and MX point at a hostname, not an IP or a URL
Structured values CAA flags and tags, SRV's four fields, URL records having a scheme
Hosts Valid labels; a host that already spells out the domain, which would create www.example.com.example.com
TTL Within the 60–60000 seconds Namecheap accepts
Zone rules No CNAME at the root, no CNAME sharing a host with another record, one SPF record per host

Problems that are legal but probably unintended — a TXT value over the 255-character single-string limit, a duplicate record, an SRV host without a leading underscore — are reported as warnings and do not block the write. Validation is scoped to the hosts you are changing, so a pre-existing problem elsewhere in the zone never blocks an unrelated update. With --json, issues are emitted as an issues array.

Editing a zone

dns edit opens the domain's records in $EDITOR (or $VISUAL, or --editor) as plain text. Saving applies whatever you changed:

# DNS records for example.com
#
# Edit and save. Change a line to change a record, delete a line to remove
# it, add a line to create one. Lines starting with # are ignored.
#
# Use @ for the domain itself. An MX or SRV record's priority goes at the
# start of its value, as below.
#
# host    ttl    type    value

@       1800  A      185.199.108.153
@       1800  A      185.199.109.153
www     3600  CNAME   user.github.io.
@       1800  MX     10 mx1.privateemail.com
@       1800  TXT    v=spf1 include:_spf.google.com ~all
_dmarc  1800  TXT    v=DMARC1; p=none

The buffer is the whole zone, so deleting a line deletes that record. What you save is diffed against what is live, shown, and confirmed before anything is written — the same flow as sync, with the same validation. If what you save cannot be read, the problem is reported with its line number and the editor reopens so no work is lost.

The value is the last field and runs to the end of the line, so TXT records need no quoting. Semicolons are kept verbatim, which matters for DMARC and DKIM; only # starts a comment.

This is the same format dns export --format zone produces, and dns sync and dns diff now accept it as well as JSON:

namecheap dns export example.com --format zone > zone.txt
namecheap dns sync example.com zone.txt

Working across several domains

preset apply and dns clone take more than one domain, or read a list from a file — one domain per line, # comments ignored:

namecheap preset apply github-pages a.com b.com c.com
namecheap dns clone template.com a.com b.com

namecheap preset apply github-pages --domains-from domains.txt
namecheap dns clone template.com --domains-from domains.txt

Each domain is diffed and confirmed separately, and dns clone reads the source zone once however many targets there are. If one domain fails the rest still run; the failures are listed at the end and the exit code is non-zero.

Checking for drift in CI

dns diff --check exits 8 when the live zone differs from the file, so a pipeline can assert that DNS still matches what is checked in:

namecheap dns export example.com --format zone > expected.txt
# ...later, in CI:
namecheap dns diff example.com expected.txt --check

Export as JSON instead if the domain forwards mail — the JSON form carries the forwarded mailboxes as well as the records, so --check catches drift in both:

namecheap dns export example.com > expected.json
namecheap dns diff example.com expected.json --check

Rollback

Namecheap's API replaces a domain's whole record set on every write, so there is no server-side undo. Before each change, the zone as it stands is saved locally, and dns rollback puts it back:

# What snapshots exist for a domain
namecheap dns rollback example.com --list

# Undo the last change
namecheap dns rollback example.com

# Go back to a specific snapshot (id from --list)
namecheap dns rollback example.com --at 1755600000
$ namecheap dns rollback example.com --list
╭────────────┬─────────────────────────┬─────────┬──────────────╮
│ ID         │ Taken                   │ Records │ Before       │
├────────────┼─────────────────────────┼─────────┼──────────────┤
│ 1755600000 │ 2025-08-19 10:40:00 UTC │ 1       │ preset apply │
│ 1755500000 │ 2025-08-18 06:53:20 UTC │ 2       │ dns sync     │
╰────────────┴─────────────────────────┴─────────┴──────────────╯

A rollback restores the zone exactly as it was, so records added since the snapshot are removed as well as changed ones put back. It goes through the same diff-and-confirm flow as sync, so you see what it will do before agreeing to it, and --dry-run shows the diff without applying it.

Snapshots include the account's email routing mode, which must be restored alongside the records or MX records would be silently unpublished. Snapshots taken before an email-forwarding change also record the forwarded mailboxes, and rolling back to one of those puts the forwarding back too. Snapshots taken before an ordinary DNS write do not — a setHosts call cannot change forwarding, and restoring an empty set would delete mailboxes the change never touched.

The 20 most recent snapshots per domain are kept, under your platform's application-data directory (override with NAMECHEAP_BACKUP_DIR). Pass --no-backup to skip taking one.

A snapshot that cannot be written is reported as a warning but does not stop the change you asked for.

Presets

# List available presets
namecheap preset list

# Show preset details
namecheap preset show github-pages

# Apply a preset
namecheap preset apply github-pages example.com -V username=myuser

# Remove preset records
namecheap preset remove github-pages example.com

Applying an email preset switches the domain's Namecheap email routing mode to MX and removes any MX records from a previous mail provider, along with a stale v=spf1 TXT record on the same host — leaving either in place would break mail delivery. Unrelated records are untouched unless you pass --replace.

Available presets:

  • github-pages - GitHub Pages hosting
  • google-workspace - Google Workspace email
  • fastmail - Fastmail email
  • protonmail - ProtonMail email
  • microsoft-365 - Microsoft 365 email
  • cloudflare - Cloudflare DNS proxy
  • netlify - Netlify hosting
  • vercel - Vercel hosting
  • sendgrid - SendGrid email sending

Nameservers

# List nameservers
namecheap ns list example.com

# Set custom nameservers
namecheap ns set example.com ns1.cloudflare.com ns2.cloudflare.com

# Reset to Namecheap defaults
namecheap ns reset example.com

Child nameservers (glue records)

If the domain runs its own nameservers, the registry needs to know their addresses — otherwise nothing can find ns1.example.com in order to ask it about example.com. That registration is a child nameserver, or glue record.

# Register ns1.example.com at 203.0.113.10
namecheap ns create example.com ns1.example.com 203.0.113.10

# Move it to a new address (the old one is looked up for you)
namecheap ns update example.com ns1.example.com 203.0.113.20

# What the registry has
namecheap ns info example.com ns1.example.com

# Remove it — anything still pointing at it stops resolving, so this confirms
namecheap ns delete example.com ns1.example.com

Registering a child nameserver does not make any domain use it. That is ns set, and the two are independent: you can register ns1.example.com and point a completely different domain at it.

Domain Privacy (WhoisGuard)

Privacy is sold as a subscription that is allotted to a domain. The API acts on the subscription's id rather than the domain name, so every command here looks the domain up first — you never have to handle ids yourself.

# Every subscription on the account, and which domain has it
namecheap whoisguard list

# Turn privacy on or off for one domain
namecheap whoisguard on example.com --email you@example.com
namecheap whoisguard off example.com

# Across several domains, or all of them at once
namecheap whoisguard on a.com b.com --email you@example.com
namecheap whoisguard on --all --email you@example.com
namecheap whoisguard on --domains-from domains.txt --email you@example.com

enable requires --email: Namecheap needs somewhere to forward the masked mail. Domains already in the state you asked for are skipped rather than re-sent, and domains with no subscription are reported rather than ignored.

Turning privacy off publishes your name, address, phone and email in public WHOIS, where scrapers pick it up quickly. It always lists the affected domains and confirms, unless you pass -y.

Attaching subscriptions to domains

A subscription bought without a domain starts unattached, and privacy cannot be turned on for a domain until one is attached to it.

# Attach the first spare subscription to a domain
namecheap whoisguard allot example.com

# Attach and turn privacy on in one step
namecheap whoisguard allot example.com --email you@example.com

# Attach a specific one (id from `whoisguard list`)
namecheap whoisguard allot example.com --id 123456

# Free a subscription up for a different domain
namecheap whoisguard unallot example.com
namecheap whoisguard unallot --id 123456

# Renew a domain's privacy subscription (charges your account, so it confirms)
namecheap whoisguard renew example.com --years 2

Email Forwarding

Forwarding is not part of the zone. It has its own place in the API, never shows up in dns list, and Namecheap only delivers it while the domain's mail routing mode is FWD — which any MX record switches away from. These commands say which mode the domain is in rather than reporting a write as done and leaving mail on the floor.

# What the domain forwards
namecheap dns email list example.com

# Forward a mailbox (add is an alias for set)
namecheap dns email set example.com hello you@gmail.com

# Catch everything not matched by another mailbox
namecheap dns email set example.com @ you@gmail.com

# Stop forwarding a mailbox
namecheap dns email rm example.com hello

Two traps this guards against:

  • Adding an MX record to a domain that is forwarding mail switches it to MX routing and the forwarded mailboxes stop being delivered. dns add and dns set warn before they do it.
  • Setting up forwarding on a domain that is already on MX routing appears to work — the mailboxes are stored — but nothing arrives. dns email list and dns email set say so.

Forwarding round-trips through dns export --format json, under an email_forwarding key, and dns sync and dns diff read it back:

# Capture a domain's records and its forwarding
namecheap dns export example.com > example.com.json

# Put both back
namecheap dns sync example.com example.com.json

# Assert in CI that neither has drifted (exit code 8 if either has)
namecheap dns diff example.com example.com.json --check

Mailboxes in the file are added or updated; mailboxes it does not mention are left alone unless --delete is given, exactly as for records. A file with no email_forwarding key says nothing about forwarding, so forwarding is left untouched rather than read as "there should be none". A zone file has no way to express forwarding at all, so --format zone notes that it exists rather than pretending it round-trips.

Domain Transfers

Transferring a domain in to Namecheap. The domain must be unlocked at its current registrar, and you need the auth code (also called an EPP code or transfer key) from them.

# Start a transfer — charges your account, so it always confirms
namecheap transfer start example.com --epp-code ABC123XYZ

# Add more than the default year
namecheap transfer start example.com --epp-code ABC123XYZ --years 2

# Every inbound transfer on the account
namecheap transfer list

# Where one has got to
namecheap transfer status 10

# Nudge a stalled one
namecheap transfer resubmit 10

Starting a transfer is an order, not an instant move: the losing registrar still has to release the domain, which can take up to five days. Transferring out is not a transfer command — it is namecheap domains unlock plus the auth code, which you get from Namecheap's web interface.

URL Redirects

# List redirects
namecheap redirect list example.com

# Add redirect
namecheap redirect add example.com @ https://www.example.com

# Add permanent (301) redirect
namecheap redirect add example.com old https://new.example.com --permanent

# Add frame/masked redirect
namecheap redirect add example.com masked https://example.com --frame

# Remove redirect
namecheap redirect rm example.com @

DNS Verification

# Verify DNS records are propagated
namecheap verify example.com

# Verify specific record type
namecheap verify example.com -t A

# Wait for propagation
namecheap verify example.com --wait --timeout 300

Shell Completions

# Bash
namecheap completions bash > ~/.local/share/bash-completion/completions/namecheap

# Zsh
namecheap completions zsh > ~/.zfunc/_namecheap

# Fish
namecheap completions fish > ~/.config/fish/completions/namecheap.fish

# PowerShell
namecheap completions powershell > namecheap.ps1

Global Options

Option Description
--config <path> Path to config file
-p, --profile <name> Profile to use
--json Output as JSON
--dry-run Don't make any changes
-q, --quiet Minimal output
-v, --verbose Verbose output
-y, --yes Skip confirmation prompts
--no-backup Don't snapshot the zone before changing it

Exit Codes

Code Description
0 Success
1 General error
2 Authentication error
3 Domain not found
4 Record not found
5 Validation error
6 Network error
7 Verification failed
8 dns diff --check found differences

Reliability

A request is abandoned after 30 seconds, and a connection after 10. Without that, a dropped connection hangs forever and the retry policy never fires — a request that never returns never fails, so there is nothing to retry.

Requests that fail for a transient reason — a timeout, a dropped connection, an HTTP 5xx, a rate-limit response, or a gateway page in place of the API's XML — are retried up to three times with an exponentially growing wait (2s, 4s, 8s). -v reports each retry as it happens.

Only commands that can be repeated safely are retried. Every write this tool makes sends the complete desired state rather than a delta, so repeating one converges on the same zone. Everything that spends money is deliberately excluded — domains renew, whoisguard renew and transfer start — so a lost response can never become a second charge.

Verifying against a real account

The test suite proves the code does what it thinks it does. It cannot prove Namecheap agrees — that the XML shapes are right, that writes land, and that what the tool reports afterwards matches what the API actually holds.

scripts/live-check.sh closes that gap by driving the built binary against a real domain and reading every change back:

cargo build

# Read-only. Makes no changes at all.
scripts/live-check.sh example.com

# The safe write phases: records and email forwarding, restored afterwards.
scripts/live-check.sh example.com read dns email

# Everything that does not spend money.
scripts/live-check.sh example.com all

all runs these, in order:

Phase What it exercises
read Every read-only command. Makes no changes.
dns dns add / set / rm / sync / diff / rollback, and validation refusals
edit dns edit, driven by a scripted stand-in for $EDITOR
preset preset list / show / apply / remove, including variable substitution
redirect redirect add / list / rm across URL, URL301 and FRAME
email The email-forwarding lifecycle, and that it reaches export/diff/sync/rollback
bulk --domains-from, and --all as a dry run
lock The registrar lock, read back from domains info
glue ns create / update / delete / info
privacy whoisguard on / off / allot
ns ns set / reset

Two more are opt-in and not covered by all, because one needs a second domain and the other can leave a domain with its registrant details public:

Phase Why it is opt-in
clone Writes to a second domain — set NC_CLONE_TARGET
unallot Detaches the privacy subscription and re-attaches it; a failed re-attach leaves the domain with no privacy

Anything past read rewrites the domain, so it takes a full export as a baseline first and restores from it on exit — including when a check fails partway. Write phases make you type the domain name to confirm.

Use a domain that is not serving traffic. The zone is rewritten repeatedly.

Nothing in the script spends money: domains renew, whoisguard renew and transfer start are deliberately absent, and it refuses to run if asked for them. Verify those by hand, once, when you actually want to buy something.

Namecheap allows roughly 20 requests a minute and 700 an hour, and a single dns add costs three of them, so the script paces itself. A full all run is several hundred calls and takes upwards of half an hour — set NC_SLEEP=<seconds> to pace it further apart. Going over the limit gets HTML gateway pages instead of XML, which the tool reports as such and retries.

API Access

To use this tool, you need API access enabled on your Namecheap account:

  1. Go to Profile > Tools > Namecheap API Access
  2. Enable API Access
  3. Add your IP address to the whitelist
  4. Copy your API Key

For testing, you can use the sandbox API by setting sandbox = true in your profile.

Code quality gate

CI tracks the CRAP metric — cyclomatic complexity weighted by how much of the function tests actually execute:

CRAP(f) = CC(f)^2 * (1 - coverage(f))^3 + CC(f)

A fully covered function scores its complexity; an untested one scores that cubed against it. The point is to find the branchy code no test is watching.

scripts/crap.sh              # report against the committed baseline
scripts/crap.sh --check      # what CI runs
scripts/crap.sh --update     # accept the current scores as the new baseline

Needs rustup component add llvm-tools-preview and cargo install cargo-llvm-cov cargo-crap.

The gate is a ratchet against crap_baseline.json, not an absolute bar. The cli::* command handlers carry real debt today — they are the thin layer between argument parsing and the API, and the wiremock tests exercise the logic underneath rather than the handlers themselves. Failing the build on that now would only mean a permanently red build. What CI rejects is a change that makes it worse: an existing function whose score rises, or a new function that lands above the threshold of 30.

When a change legitimately alters the scores — you covered something, split a function, or decided the debt is worth taking — run scripts/crap.sh --update and commit crap_baseline.json alongside it. The baseline is sorted by (file, function, line), so that diff stays readable.

License

MIT

About

cli app to manage a domain on namecheap

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages