# Custom domains

Connect a domain you own or buy a new domain for your DeepSpace app.

By default, every deployed app lives at `<name>.app.space` - a fully production-grade URL with SSL that most apps ship and stay on. When you want a branded URL, you can attach your own domain - `myapp.com`, `myapp.ai`, `myapp.io` - directly from the CLI. For domains bought through DeepSpace, purchase, DNS, and SSL are handled end-to-end. Domains you already own stay at your registrar and connect through DNS verification.

## Connect a domain you already own

Available in SDK v0.33.0. Use this flow for a domain registered at Cloudflare,
Namecheap, or another registrar. No purchase or transfer is involved.

```bash
# Prepare ownership verification for this app
npx deepspace@latest app domain attach example.com --external --app my-app --json

# After adding the returned TXT record at your DNS provider
npx deepspace@latest app domain verify example.com --app my-app --json
```

The first command returns a TXT record tied to your account and the target app.
Add it at the domain's DNS provider, then run the returned verification command.
DeepSpace creates the certificate after DNS ownership is proven and returns the
remaining records: a routing CNAME and any additional
Cloudflare ownership TXT. Add these records, then repeat `verify` until the
hostname and certificate are active. Finally, open your domain over HTTPS to
confirm that it serves your app.

Starting with v0.33.1, new external-domain certificates use automatic HTTP
validation and renewal. No `_acme-challenge` record is needed for these
attachments. Older attachments that return a certificate CNAME still require
that record to remain **DNS-only** for automatic renewal.

The routing CNAME must keep pointing to
the SaaS target returned by the command. Root domains require CNAME flattening;
Cloudflare DNS supports it. The flow connects exactly the requested hostname.
For `www`, configure a redirect to the root in your DNS provider's hosting controls,
or attach and verify that hostname separately.

`list` and `status` include verified external domains. Purchase costs and
registration renewals remain managed at your existing registrar. To connect the
domain to a different app, detach it first and repeat the flow for that app;
the DNS proof changes with the target app. Detaching removes DeepSpace routing
and its certificate. Remove the DeepSpace DNS records yourself when you stop
using the service; registration remains intact.

## How purchases work

DeepSpace registers domains through a hybrid backend:

* **Cloudflare Registrar** for \~27 TLDs at cost (`.com`, `.dev`, `.app`, `.xyz`, and others)
* **Porkbun** for `.ai`, `.io`, `.me`, `.co`, ccTLDs, and other variants

Routing uses Cloudflare Custom Hostnames against the platform's SaaS zone (`app.space`). After purchase, the platform provisions ownership and DCV records on the new domain's zone, then activates a hostname route.

Billing is via Stripe Checkout - the same payment flow as in-app purchases. Auto-renew is on by default.

## Search and buy

```bash
# Find an available domain and its price
npx deepspace app domain search my-startup
npx deepspace app domain search my-startup --limit 20

# Buy via Stripe Checkout (browser opens for payment)
npx deepspace app domain buy my-startup.com
```

`buy` opens a Stripe Checkout tab in your browser. After payment, the CLI polls for provisioning:

* **Cloudflare Registrar TLDs** (`.com`, `.dev`, `.app`): \~60-90 seconds
* **Porkbun TLDs** (`.ai`, `.io`, `.me`): 15-60 minutes (registry-side NS propagation)

You can Ctrl-C out of the polling loop without losing progress - provisioning continues server-side. Re-check with `domain status <domain>`.

### Buy options

```bash
# Print the Checkout URL instead of opening a browser
npx deepspace app domain buy myapp.com --no-open

# Exit immediately after Checkout session is created (no polling)
npx deepspace app domain buy myapp.com --no-wait

# Skip the confirmation prompt (required in non-TTY contexts)
npx deepspace app domain buy myapp.com --yes

# Combine with --app to buy and attach in one command
npx deepspace app domain buy myapp.com --app my-app
```

For `buy`, `attach`, and `verify`, `--app` accepts the **immutable app id or the current live name**; without it, the CLI resolves `DEEPSPACE_APP_ID` from the surrounding app directory. Names are URL leases, not durable identity, so scripts should prefer the id — see [app identity](/guides/app-identity).

With `--json`, `buy` returns the Checkout session immediately **without polling**, and supplies the `domain status` follow-up action to run once payment completes.

**Do not automate `buy` in CI or tests.** It spends real money and enables auto-renew. Purchase and renewal changes are user decisions - obtain explicit approval before passing `--yes`, and never use a real owned domain for routine tests.

## List, inspect, and manage

```bash
# Domains you own
npx deepspace app domain list
npx deepspace app domain list --json

# Detail for one domain (registrar status, hostname status, expiry, errors)
npx deepspace app domain status myapp.com
npx deepspace app domain status myapp.com --json
```

Domains, billing, and renewal status are also visible in the web dashboard at [dashboard.deep.space](https://dashboard.deep.space).

## Re-point a domain at a different app

```bash
npx deepspace app domain attach myapp.com --app new-app-name
```

`attach` re-points an owned registration to the **resolved immutable app id** - the CLI resolves whatever you pass to `--app` (id or live name) to the id and binds that. Use it when:

* You want to move a domain from a staging app to production
* You're consolidating multiple domains under one app

Because the binding is to the id, **renaming an app does not require re-attaching its domain**. The domain follows the app through any rename, along with data, secrets, and collaborators - see [app identity](/guides/app-identity#renaming-an-app).

## Detach without releasing

```bash
npx deepspace app domain detach myapp.com --yes
```

`detach` removes routing but keeps the registration on file. You keep owning the domain and can re-attach later. There is no `domain release` - releasing a registration goes through the registrar's own portal.

## Auto-renew

```bash
# Disable auto-renew (will expire at end of term)
npx deepspace app domain renew myapp.com --auto off

# Re-enable
npx deepspace app domain renew myapp.com --auto on
```

## Agent-friendly flags

The CLI is designed for both humans and automation:

| Flag          | Available on                                          | Purpose                                        |
| ------------- | ----------------------------------------------------- | ---------------------------------------------- |
| `--json`      | `search`, `buy`, `list`, `status`, `attach`, `verify` | Machine-readable stdout                        |
| `--yes`       | `buy`, `detach`                                       | Skip confirmation prompt (required in non-TTY) |
| `--no-wait`   | `buy`                                                 | Exit after Checkout session creation           |
| `--no-open`   | `buy`                                                 | Print URL instead of opening browser           |
| `--external`  | `attach`                                              | Connect a domain bought elsewhere              |
| `--limit <n>` | `search`                                              | Cap the number of results                      |

## Pitfalls

\`--app\` defaults to your current directory

If you run `deepspace app domain buy myapp.com` outside an app directory without `--app`, the CLI errors with `No app specified. Pass --app <name>, or run from an app directory with a wrangler.toml.`

Premium domains can have different registration and renewal prices

`chargedCents` in `list` / `status` is what you paid this year. The pricing object exposes separate `registrationCost` / `renewalCost`. For non-premium TLDs they match; for premium domains, introductory pricing can reset at renewal - read `domain status` before assuming.

Don't wrap \`buy\` in \`timeout N\`

The CLI polls for up to 5 minutes (Cloudflare TLDs) or 60 minutes (Porkbun TLDs). An artificial timeout aborts before payment completes. Use `--no-wait` for fire-and-forget; otherwise let it run and Ctrl-C if needed (safe; provisioning continues server-side).

## Pricing visibility

The CLI shows the price during `search` and the Checkout flow. The price starts from the registrar's cost (Cloudflare Registrar or Porkbun) and DeepSpace adds a markup: you pay the registrar cost × 1.3, plus a $1 fee. Renewals charge to your saved Stripe card automatically.

## Next steps

* [App identity](/guides/app-identity) - the immutable id that domains bind to.
* [Deployment](/concepts/deployment) - how deploys work, and where subdomains come from.
* [CLI reference](/cli-reference/commands#app-domain) - all `domain` subcommands and flags.
