Skip to content

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.

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.

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.

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.

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.

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.

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.

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.

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.