Custom domains
Your App answers at https://{workspace}--{app}.wawesome.app from its first deploy. Attach a domain
you own and it answers there too. The wawesome address keeps working, so nothing you have already
handed out breaks.
npx wawesome domains is where you look when the name is not doing what you expected. It prints the
domain, where it got to, when we last checked, and the records still to add.
Attach one
Section titled “Attach one”Put the hostname in wawesome-function.json and deploy:
{
"app": "checkout",
"function": "api",
"domain": "shop.client.com"
}
npx wawesome deploy
The deploy attaches the name and prints the records. It does not wait for DNS, so the deploy lands
whether or not you add them today. If the code and files are unchanged since the last deploy, it
still attaches the name, and prints CONFIGURATION APPLIED rather than promoting a version:
Domain: shop.client.com: not verified yet, so add the records below
DNS records to add at your registrar:
Add this first. It proves you own the name:
TXT _cf-custom-hostname.shop.client.com b1946ac92492d2347c6235b4d2611184
Add this first too. It issues the certificate and keeps it renewed:
CNAME _acme-challenge.shop.client.com shop.client.com.9f8c2e1a.dcv.cloudflare.com
Delete any _acme-challenge TXT record already at that name.
Add this once the certificate is active. It points traffic here:
CNAME shop.client.com wawesome.app
The deploy is finished. This domain comes up on its own once the
records resolve.
The dashboard attaches a domain, and so does an agent over MCP. Neither writes your
wawesome-function.json, so the file goes on saying nothing about the name. npx wawesome domains adopt
writes it in.
An App carries one domain. To move to a different one, detach the one it has and deploy the new one.
Where the deploy cannot read the App’s domains at all, it says where it got to is unknown rather
than not attached. A domain that was serving is still serving, and the deploy landed. Run
npx wawesome domains once the gateway answers again.
Both halves of the name
Section titled “Both halves of the name”Attaching client.com claims www.client.com with it, and attaching www.client.com claims
client.com. That is one domain with two names, and each name has its own records. wawesome domains
lists them under the name they belong to:
Domain: client.com: coming up, and each half of the name is below
Last checked 2026-09-02 09:23 UTC.
client.com: name verified, so add the certificate CNAME below
www.client.com: not verified yet, so add the records below
DNS records to add at your registrar:
client.com:
Add this first. It proves you own the name:
TXT _cf-custom-hostname.client.com 4f9b1c07a3e2d8b56104f7c3
Add this first too. It issues the certificate and keeps it renewed:
CNAME _acme-challenge.client.com client.com.9f8c2e1a.dcv.cloudflare.com
Delete any _acme-challenge TXT record already at that name.
Add this once the certificate is active. It points traffic here:
CNAME client.com wawesome.app
www.client.com:
Add this first. It proves you own the name:
TXT _cf-custom-hostname.www.client.com 9c1185a5c5e9fc5462863
Add this first too. It issues the certificate and keeps it renewed:
CNAME _acme-challenge.www.client.com www.client.com.9f8c2e1a.dcv.cloudflare.com
Delete any _acme-challenge TXT record already at that name.
Add this once the certificate is active. It points traffic here:
CNAME www.client.com wawesome.app
This domain comes up on its own once the records resolve.
A half we hold nothing for reads as not claimed, with the reason printed beneath it:
client.com: serving
www.client.com: not claimed
'www.client.com' is attached to another App, so this claim does
not carry it. Detach it where it is attached and attach this
name again to pick the leg up.
The other reason a half stays unclaimed is a DNS provider that will not hold a CNAME at the naked
name. The apex could never answer there, so we do not claim it, and we name the provider. Attaching
the name again picks the half up once the reason is gone.
The three records
Section titled “The three records”| Record | What it does | When |
|---|---|---|
_cf-custom-hostname.<name> TXT |
Proves the name is yours | First |
_acme-challenge.<name> CNAME |
Issues the certificate and keeps it renewed | First |
<name> CNAME to wawesome.app |
Points traffic here | Last, once the certificate is issued |
The first two prove the name and get a certificate. Neither moves any traffic.
The third is the cutover. The moment it resolves, visitors typing your domain arrive here. Add it last. A name already serving a live site would otherwise land on a certificate that does not exist yet, and every visitor would meet a TLS error.
The certificate CNAME is added once and never replaced. Cloudflare answers its own challenge beneath
that name for every renewal, so you are not asked back at the registrar each time the certificate
turns over. If an _acme-challenge TXT record is already at that name, delete it. A TXT and this
CNAME cannot both sit there.
Every record stays in the table until the domain is serving, including ones already in and already read. The state line above the table is what says how far along you are, not the length of the list.
We check every domain that is still coming up once a minute, so a record you added a moment ago is
not late yet. Last checked is when we looked.
What each state means
Section titled “What each state means”npx wawesome domains
[wawesome] The domain on app 'checkout':
Domain: shop.client.com: name verified, so add the certificate CNAME below
Last checked 2026-09-02 09:23 UTC.
DNS records to add at your registrar:
Add this first. It proves you own the name:
TXT _cf-custom-hostname.shop.client.com b1946ac92492d2347c6235b4d2611184
Add this first too. It issues the certificate and keeps it renewed:
CNAME _acme-challenge.shop.client.com shop.client.com.9f8c2e1a.dcv.cloudflare.com
Delete any _acme-challenge TXT record already at that name.
Add this once the certificate is active. It points traffic here:
CNAME shop.client.com wawesome.app
This domain comes up on its own once the records resolve.
The words after the hostname are where it got to. Only serving means a visitor reaches your App at
the name.
| What it prints | Serving at your name | What is left to do |
|---|---|---|
not verified yet, so add the records below |
No | Add the ownership TXT and the certificate CNAME |
not verified yet, and the TXT record is not ready |
No | Nothing. The certificate vendor has not named that record yet |
name verified, so add the certificate CNAME below |
No | Add the certificate CNAME |
name verified, and the certificate record is not ready |
No | Nothing. The certificate vendor has not named that record yet |
certificate issued, so point the CNAME here to cut over |
No | Add the traffic CNAME. This is the cutover |
the CNAME is in at Cloudflare with the proxy on, so set that record to DNS only |
No | Turn the orange cloud grey. See below |
serving |
Yes | Nothing |
coming up, and each half of the name is below |
No, not at every half | Read the half that is behind, listed under it |
serving at both halves of the name |
Yes | Nothing |
being detached |
No, and it has stopped | Wait for the certificate to be given back |
A domain that is attached and not yet serving is not one to hand to a client. Until it says serving,
the gateway refuses a request to that hostname in the same bytes it refuses a hostname nobody
attached. Nothing in that answer says the name is on its way.
The App’s own wawesome.app address answers in every one of these states. The serving block says
so:
Domain: shop.client.com: serving
Last checked 2026-09-02 09:23 UTC.
This app's own address answers too, so nothing pointed at it breaks.
The record is in and the CLI still asks for it
Section titled “The record is in and the CLI still asks for it”You added the traffic CNAME at Cloudflare, it is there in the dashboard, and the domain will not
come up. What is different about that record is the orange cloud beside it, which Cloudflare turns on
for every new CNAME.
While it is on, the name resolves to Cloudflare’s own addresses rather than to the value you typed. Public DNS then answers exactly as it answers a record nobody added. We read that answer, recognise Cloudflare behind it, and say which of the two we are looking at:
Domain: shop.client.com: the CNAME is in at Cloudflare with the proxy on, so set that record to DNS only
Do not delete the record and add it again. Open it on Cloudflare’s DNS page, click the orange cloud so
it turns grey and reads DNS only, and save. The name starts answering here within minutes.
In practice this only catches the traffic CNAME. A TXT record cannot be proxied, and neither can the
_acme-challenge CNAME under its underscore label, so ownership and the certificate come up
normally. This is the one step it strands you on.
Two limits. Cloudflare is the only provider whose proxy addresses we hold. Another provider proxying the same record still reads as a record nobody added.
The naked name is never reported this way either. Cloudflare flattens a CNAME at the apex whether
the proxy is on or off, so we cannot tell the two apart there. An apex stuck at certificate issued, so point the CNAME here to cut over is worth checking for an orange cloud even though nothing says
so.
domains adopt
Section titled “domains adopt”adopt writes the domain the App already answers at into wawesome-function.json. Nothing about the
domain changes. It takes no hostname, because it reads the name off the platform rather than from you:
npx wawesome domains adopt
[wawesome] wawesome-function.json now declares 'shop.client.com'.
[wawesome] Nothing about the domain changed: it was attached already, and the file
[wawesome] now says so. Commit it, and every deploy from here agrees.
This is the fix for a domain attached from the dashboard or by an agent. A deploy reports the same drift and never acts on it, because a deploy does not edit a file you have committed:
Domain: shop.client.com: attached, and this project declares no domain
Nothing was detached and it is still serving. A domain comes off
an App when somebody names it, never because a line was deleted.
To write it into wawesome-function.json, run
wawesome domains adopt
To take it off the App instead, run
wawesome domains detach shop.client.com
Where the file names one domain and the App answers at another, adopt writes nothing and prints both:
[wawesome] Error: Two domains are in play on app 'checkout'.
[wawesome] wawesome-function.json declares 'shop.client.com'
[wawesome] the app answers at 'checkout.client.com'
[wawesome] Nothing was written. An App carries one domain, so which of the two
[wawesome] it is is yours to settle: edit the line yourself, or take the attached
[wawesome] one off with
[wawesome] wawesome domains detach checkout.client.com
[wawesome] and deploy to attach the declared one.
domains detach
Section titled “domains detach”detach is the one thing that stops an App serving at a domain. It takes a signed-in person who
administers the workspace: somebody who builds in it can attach a domain and cannot
take one off. The hostname is typed out in full:
npx wawesome domains detach shop.client.com
[wawesome] Detaching 'shop.client.com' from app 'checkout':
[wawesome] • the domain stops resolving here, so anything pointed at it stops
[wawesome] reaching this app
[wawesome] • this app's own address is unaffected and keeps serving
[wawesome] • the certificate is given back, and none is held for the name
[wawesome] ✅ Detached 'shop.client.com' from app 'checkout'.
[wawesome] The domain is free to attach again by declaring it and deploying.
The name comes off its address first and the certificate is given back after. A detach that cannot
reach the vendor leaves a domain that has already stopped answering. It reads as being detached
until the vendor lets go, and we finish it on a later pass.
If the file still declares the name, the CLI says so, because the next deploy would attach it again:
[wawesome] wawesome-function.json still declares 'shop.client.com'. Remove the line,
[wawesome] or the next deploy attaches the domain again.
Deleting the line on its own detaches nothing. The line is a request to attach, not a description of what is live.
When your plan stops covering custom domains
Section titled “When your plan stops covering custom domains”Custom domains come with the paid plans. When a workspace that holds one moves to the free plan, because the subscription ended or was downgraded, we email every owner. The domain keeps answering for 30 days from that email. Then it stops, and a request to it gets the same answer as a hostname nobody attached.
We don’t detach it. The claim, the DNS records and the certificate stay where they are, and the App
keeps answering at its own address the whole time. npx wawesome domains says why it stopped:
Domain: shop.client.com: stopped, because the plan does not include custom domains
It is still attached, and its records and certificate are kept.
Move to a paid plan and it answers again within the hour.
This app's own address still answers.
Move to a paid plan and it answers again within the hour, with nothing to verify and nothing to redeploy. If the workspace moves to the free plan again later, you get a new email and the full 30 days again. You can still detach a stopped domain yourself.
What does not stop a domain serving
Section titled “What does not stop a domain serving”Once a domain reaches serving, only two things stop it: a detach you asked for, and the free plan
as described above.
Deleting "domain" from wawesome-function.json does not, and neither does deploying without it. The
deploy names the drift and leaves the domain up. A line that goes missing from a file should not take
a client’s site dark.
A failed payment does not. The domain keeps serving while the payment is being recovered. What the plan limits is attaching a new one, and a deploy that cannot attach says so and still lands:
Domain: shop.client.com: not attached
A custom domain is granted from the solo plan upwards, one per
App, and this workspace's plan grants none — so
'shop.client.com' was not attached. The address this App already
answers at keeps working either way.
Where to resolve it: https://dashboard.wawesome.io/billing
The deploy landed, and this App's other address still answers.
A check that fails does not. If the certificate vendor cannot be reached, or the name later stops resolving in public DNS, we leave the domain exactly as it was. A verified domain never expires.
A claim nobody ever verified is let go after 14 days, so a typo does not hold a name against your own retry. That is a domain that never served.
Reading this from a pipeline
Section titled “Reading this from a pipeline”npx wawesome domains is open to a deploy credential carrying read:domains, so CI reads the same
states you do. --app <slug> reaches an App other than the one in the current directory. Attaching
needs write:domains. Detaching is closed to a credential whatever it carries: it takes a signed-in
person, with the hostname typed.