# Custom domains

Every web service you expose gets a default HTTPS URL from its first deployment. You can also point your own domains at it — delivered over HTTPS with a certificate Koo issues and renews for you.

Domains belong to the **service**: any exposed web service can hold its own set of custom domains, each with its own DNS records and live status.

## The default URL

When you expose a web service, Koo's hosting layer assigns it a public URL on `koo.app` automatically. There is nothing to configure: HTTPS is included, with the certificate managed for you.

The URL is the first entry of the service's address list in the console (under **Settings → Networking**), and the `status.url` field on the [Service object](/api/objects/service). The field is sticky — set once the service first goes live, and kept from then on; whether the service is currently up is its health, not its URL.

A custom domain adds a hostname. The default URL keeps working alongside it — nothing replaces it.

## Add a custom domain

The service must be **deployed and exposed** first. Exposing is a staged change — turn on **Public networking** in the service's **Settings → Networking** and press Deploy; see [staged changes](/docs/projects/staged-changes). Domains themselves are different: adding or removing one applies **immediately**, with no deploy step.

**In the console:**

1. 
2. 
3.

**Over the API.** Write the full desired set of hostnames through a [service update](/api/services), as a `domains` array of strings. Domains are declarative on the service and the hostname is its own identity — there are no domain IDs to track.

```json
{
  "domains": ["app.example.com", "www.example.com"]
}
```

The array is the complete desired state: a hostname you leave out is removed. Read each hostname back on the service's read-only `domains` array, where every entry is enriched with the DNS records to set and its live status.

## Set the DNS records

As soon as you add a hostname, Koo returns the exact DNS records to create — an ownership record and a routing record. The console shows each record's name and value with copy buttons, and keeps them visible even after the domain goes live; over the API they come back on the service's `domains[].dnsRecords`. Don't guess the values — set exactly what Koo returns, at your DNS provider.

Routing is always a `CNAME`, for both subdomains and apex domains. Because the DNS standard doesn't allow a `CNAME` at the zone root, point an apex domain (like `example.com`) at Koo's target using your DNS provider's CNAME flattening — often labelled ALIAS or ANAME. You never configure NS delegation, and Koo never asks you to.

## Verification, routing, and TLS

Once the records are set, Koo takes it from there — you don't trigger anything. A domain walks a simple lifecycle, readable from its `status` on the service:

- 
- 
- 
-

While a domain is `pending` or `error`, Koo re-checks it automatically about once a minute, so you just set the records and wait — there is no verify button to press. A domain is never marked `active` until DNS and the certificate are both real, so the status you see is the truth.

## Remove a domain

Remove a hostname in the console — the trash button on its row, with an inline confirm — or leave it out of the service's `domains` array on the next update. Removal applies immediately: traffic on that hostname stops routing right away, while the default URL and any other custom domains keep working. To restore it, add it again and re-set the DNS records.

## Plan limits

Custom domains aren't included on the [Free plan](/docs/get-started/free-tier). Every paid plan includes them, with a cap that rises by tier — see [usage and limits](/docs/account/usage-and-limits) for the numbers.

> **Note:**
>
> When you reach your plan's cap, the console blocks adding another domain and names the plan that raises it, and the API returns a quota error naming the tier to upgrade to. Plan changes happen in [Billing](/docs/account/billing).
