---
operationId: "registerProjectBranchCustomDomain"
method: "POST"
path: "/projects/{project_id}/branches/{branch_id}/custom-domains"
tag: "functions"
stability: "beta"
interfaces: ["api", "sdk"]
---
> API Reference / Functions / Register a custom domain on a branch

## POST /projects/{project_id}/branches/{branch_id}/custom-domains

Registers a customer-owned domain (for example `dashboard.acme.com`) on the
branch and points it at a target entity, chosen by `entity_type` +
`entity_id`. In v1 only `entity_type: function` is supported (an
unsupported type is rejected with `400`), where `entity_id` is the function
slug and the function must already exist on the branch (else `404`).

The response includes the `cname_target` the customer must point their
domain at with a CNAME record; the domain goes live only once that DNS
resolves and a certificate is issued on the first request. A domain already
registered to another resource is rejected with `409` and no detail about
the owner. Re-registering the same domain for the same entity is idempotent.

**Note**: This endpoint is currently in Beta.


### Parameters

- `project_id` (string, path, required)
  The Neon project ID
- `branch_id` (string, path, required)
  The Neon branch ID

### Request body

- `domain` (string, required)
  The custom domain to register (for example `dashboard.acme.com`).
  Case-insensitive; normalized to lowercase (a trailing root dot is
  stripped, so the 254-char bound admits a fully-qualified name whose
  normalized form is 253 chars). Neon-managed and internal hostnames are
  rejected.
  
- `entity_type` (string, required)
  The kind of branch entity to point the domain at. v1 supports only
  `function`; any other value is rejected with `invalid_entity_type`.
  
- `entity_id` (string, required)
  The target entity's identifier within the branch. For `function` this
  is the function slug (which must already exist on the branch).
  

### Response (201)

- `domain` (string, optional)
  The registered custom domain (normalized, lowercase).
- `entity_type` (string, optional)
  The kind of branch entity the domain targets. Possible values:
  `function` (v1 supports only `function`). Not an `enum`: new values may
  ship in later spec versions — treat any undocumented value as unknown.
  
- `entity_id` (string, optional)
  The target entity's identifier within the branch. For `function` this
  is the function slug.
  
- `cname_target` (string, optional)
  The hostname the customer must point their custom domain at with a
  CNAME record. Empty when the serving region has no custom-domains
  front door configured. This is the activation input: point DNS here
  and the domain goes live (see `status`) once a certificate is issued
  on the first request.
  
- `status` (string, optional)
  The domain's current validity, computed by a background check:
  `pending` (still converging — point your CNAME at `cname_target` and
  wait), `active` (live: DNS resolves to the edge, the CA is authorized,
  and routing is published), or `error` (a fixable problem — see
  `status_reason`). Not an `enum`: treat any undocumented value as
  unknown. May be absent briefly right after registration.
  
- `dns_status` (string, optional)
  The DNS + CAA portion of the check: `pending` (no records yet), `ok`
  (resolves to our edge and the CA is authorized), `misconfigured` (your
  CNAME does not resolve to our edge), or `caa_blocked` (your CAA records
  forbid Let's Encrypt). Not an `enum`.
  
- `binding_status` (string, optional)
  Whether Neon's internal routing for the domain is published:
  `pending`, `present`, or `missing`. `missing` is an internal fault
  surfaced for support. Not an `enum`.
  
- `status_reason` (string, optional)
  A short, stable machine-readable reason for a non-active `status`
  (e.g. `cname-not-pointing-at-edge`, `caa-blocks-lets-encrypt`,
  `binding-missing`), suitable for keying an actionable hint. Empty when
  active or pending.
  

### Code examples

```bash
curl "https://console.neon.tech/api/v2/projects/$PROJECT_ID/branches/$BRANCH_ID/custom-domains" \
  -X POST \
  -H "Authorization: Bearer $NEON_API_KEY"
```

```typescript
import { createNeonClient, raw } from '@neon/sdk';

const neon = createNeonClient({ apiKey: process.env.NEON_API_KEY });
const { data } = await raw.registerProjectBranchCustomDomain({
  client: neon.client,
  path: {
    project_id: process.env.PROJECT_ID,
    branch_id: process.env.BRANCH_ID
  }
});
```

### Errors

**default**
General Error.

The request may or may not be safe to retry, depending on the HTTP method, response status code,
and whether a response was received.

- If no response is returned from the API, a network error or timeout likely occurred.
- In some cases, the request may have reached the server and been successfully processed, but the response failed to reach the client. As a result, retrying non-idempotent requests can lead to unintended results.

The following HTTP methods are considered non-idempotent: `POST`, `PATCH`, `DELETE`, and `PUT`. Retrying these methods is generally **not safe**.
The following methods are considered idempotent: `GET`, `HEAD`, and `OPTIONS`. Retrying these methods is **safe** in the event of a network error or timeout.

Any request that returns a `503 Service Unavailable` response is always safe to retry.

Any request that returns a `423 Locked` response is safe to retry. `423 Locked` indicates that the resource is temporarily locked, for example, due to another operation in progress.

- `request_id` (string, optional)
  Unique identifier for the request, useful for debugging.
  You can set this value manually by including an `X-Request-ID` header in the request. If not provided, the value will be generated automatically.
  
- `code` (string, required)
  Machine-readable code classifying the error type. See `message` for a human-readable explanation.
  Default: ``
- `message` (string, required)
  Error message
