# Custom domain

> Point a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs.

- Repository: MaxLeiter/vcup
- GitHub: https://github.com/MaxLeiter/vcup
- Human docs: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb
- Complete Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms-full.txt

## Source Files

- `README.md`
- `api/upload.ts`
- `cli.ts`
- `vercel.json`

---

---
title: "Custom domain"
description: "Point a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs."
---

vcup serves inline proxy links at `{base}/f/{slug}` on whatever host fronts the Vercel deployment; optional custom domains attach to the same project, while client `url` / `VCUP_API_URL` targets the API and server `VCUP_BASE_URL` (or request `Host` headers) shapes the proxy URL returned from `POST /api/upload`.

## Two configuration surfaces

Custom domains touch **client** and **server** settings independently. Both must agree on the hostname you want in shareable links when you are not relying on automatic header inference.

| Surface | Setting | Scope | Effect |
| --- | --- | --- | --- |
| Client | `~/.vcuprc` → `url` or `VCUP_API_URL` | CLI upload/delete | Base URL for `POST {url}/api/upload` and `DELETE {url}/api/delete` |
| Server | `VCUP_BASE_URL` (optional) | Vercel project env | Overrides the origin used in the JSON `url` field after upload |
| Server | (default) | Upload handler | `${x-forwarded-proto}://${host}` from the incoming upload request |

The `raw` field in the upload response is always the Vercel Blob store URL from `put()`; it is not rewritten by `VCUP_BASE_URL` or custom domains.

<Info>
Proxy file serving (`GET /f/:slug` → `api/f`) is defined in `vercel.json` and runs on every domain attached to the deployment, not only the default `*.vercel.app` hostname.
</Info>

## Attach a subdomain on Vercel

<Steps>
<Step title="Add the domain to the project">

From the project directory (or any directory linked to the deployment), register the hostname:

```bash
vercel domains add files.yourdomain.com
```

Replace `files.yourdomain.com` with your chosen subdomain.

</Step>
<Step title="Create the DNS record">

Add a CNAME for the subdomain label (e.g. `files`) pointing at `cname.vercel-dns.com`, as documented in the repository README.

</Step>
<Step title="Wait for Vercel to provision TLS">

Confirm the domain shows as active in the Vercel project **Settings → Domains** before testing uploads or opening `/f/` links on the new host.

</Step>
</Steps>

<Warning>
These steps configure DNS and Vercel routing only. Blob storage (`BLOB_STORE_URL`, `BLOB_READ_WRITE_TOKEN`) and auth (`VCUP_TOKEN`) are unchanged; see [Deploy on Vercel](/deploy-vercel) for the base deployment checklist.
</Warning>

## Point the CLI at the custom host

After the domain is live, set the client base URL to the custom origin (no trailing path). The CLI posts uploads and deletes to `{url}/api/...`.

<CodeGroup>
```json title="~/.vcuprc"
{
  "url": "https://files.yourdomain.com",
  "token": "your-vcup-token"
}
```

```bash title="Environment variables"
export VCUP_API_URL="https://files.yourdomain.com"
export VCUP_TOKEN="your-vcup-token"
```
</CodeGroup>

<ParamField body="url" type="string" required>
Deployment origin used as the API base. Must match the host where `/api/upload` and `/api/delete` are reachable (custom domain or `*.vercel.app`).
</ParamField>

Environment variables take precedence when **both** `VCUP_API_URL` and `VCUP_TOKEN` are set; otherwise `~/.vcuprc` supplies missing fields. See [Configure the CLI](/configure-cli) for the full precedence rules.

<Check>
Verify: `vcup /path/to/small.txt` prints a proxy URL whose host is `files.yourdomain.com` when client and server settings are aligned (next section).
</Check>

## How VCUP_BASE_URL shapes upload proxy URLs

`POST /api/upload` returns JSON with `url` (proxy) and `raw` (blob store). The proxy path is built server-side:

```text
url  = {baseUrl}/f/{slug}
raw  = {blob.url from @vercel/blob put}
```

`baseUrl` is chosen in this order:

1. `process.env.VCUP_BASE_URL` when set on the Vercel project
2. Otherwise `` `${req.headers["x-forwarded-proto"] || "http"}://${req.headers.host}` `` from the upload request

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>API: POST /api/upload (Host: files.example.com)
  API->>Blob: put(filename, stream)
  Blob-->>API: blob.url
  Note over API: slug = pathname of blob.url
  alt VCUP_BASE_URL set
    API-->>CLI: { url: VCUP_BASE_URL/f/slug, raw }
  else infer from request
    API-->>CLI: { url: proto://host/f/slug, raw }
  end
```

### When to set VCUP_BASE_URL

| Scenario | Set `VCUP_BASE_URL`? | Typical result |
| --- | --- | --- |
| CLI `url` is the custom domain; uploads hit that host | Optional | Inferred `baseUrl` matches custom domain via `Host` / `x-forwarded-proto` |
| CLI still uses `*.vercel.app` but shared links should use the custom domain | **Yes** — `https://files.yourdomain.com` | Proxy `url` uses custom host even though API requests go to `vercel.app` |
| Multiple domains on one deployment; force one canonical link host | **Yes** | Avoids whichever `Host` header arrived on upload |
| Local `vercel dev` without trustworthy forwarded headers | Often helpful | Explicit base avoids `http://localhost` in returned URLs |

Set `VCUP_BASE_URL` in the Vercel project **Environment Variables** (Production / Preview as needed). It is not listed in `.env.example` but is read directly in `api/upload.ts`. Use a full origin with scheme, no trailing slash, e.g. `https://files.yourdomain.com`.

<ResponseField name="url" type="string">
Proxy link: `{baseUrl}/f/{slug}` where `slug` is the blob pathname (leading `/` stripped) from the stored object URL.
</ResponseField>

<ResponseField name="raw" type="string">
Direct `https://…public.blob.vercel-storage.com/…` URL from Vercel Blob; unchanged by custom domain or `VCUP_BASE_URL`.
</ResponseField>

<Tip>
Use `vcup --raw` when you need the blob URL in scripts; use the default proxy `url` for browser-inline sharing. See [Proxy and raw URLs](/proxy-and-raw-urls).
</Tip>

## End-to-end hostname alignment

```text
  DNS CNAME  files ──► cname.vercel-dns.com
                              │
                              ▼
                    Vercel project (same as *.vercel.app)
                    ├── /api/upload, /api/delete  ◄── CLI config.url
                    └── /f/:slug ──► api/f        ◄── proxy url host
```

- **Opening** a printed `url` only requires the proxy host to route to this deployment; `api/f` fetches content via `BLOB_STORE_URL` + slug, not via the link hostname’s path alone.
- **Uploading** requires `config.url` to reach the authenticated API on that deployment.
- **Printing** the desired host in `url` requires either uploading through that host (header inference) or setting `VCUP_BASE_URL`.

## Mismatches and verification

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Printed link uses `*.vercel.app` but you use a custom domain | Uploads sent to default deployment URL; `VCUP_BASE_URL` unset | Set client `url` to custom host and/or set `VCUP_BASE_URL` |
| Upload 401 | `VCUP_TOKEN` mismatch | Match CLI token to project env (see [Authentication](/authentication)) |
| Proxy link 500 on GET | `BLOB_STORE_URL` missing on server | Set in Vercel env per [Deploy on Vercel](/deploy-vercel) |
| `vcup rm` on proxy URL fails locally | `BLOB_STORE_URL` unset in **client** env | Pass raw blob URL or set client `BLOB_STORE_URL` ([Upload and delete](/upload-and-delete)) |

<RequestExample>
```bash
# Upload through custom domain (client config)
vcup screenshot.png
```

```text
https://files.yourdomain.com/f/myfile-abc123.png
```
</RequestExample>

Open the printed URL in a browser; a `200` response with `Content-Disposition: inline` confirms the rewrite and `api/f` proxy path on that host.

## What custom domains do not change

- **Blob storage location** — Objects remain in the configured Vercel Blob store; `raw` URLs stay on `*.public.blob.vercel-storage.com`.
- **Auth** — `VCUP_TOKEN` Bearer checks on upload/delete are unchanged.
- **Public proxy route** — `GET /f/:slug` stays unauthenticated; only upload/delete require the token.
- **Delete API** — Still `DELETE {config.url}/api/delete` with `X-Blob-Url`; custom domain does not remove the client `BLOB_STORE_URL` requirement for resolving proxy URLs to blob URLs.

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema, `VCUP_API_URL` / `VCUP_TOKEN` precedence, and matching client settings to your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server `VCUP_BASE_URL`, `BLOB_STORE_URL`, and client overrides in one reference.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
`url` vs `raw`, `/f` rewrites, and inline serving behavior.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Initial deploy, Blob store wiring, and required server env vars before adding a domain.
</Card>
</CardGroup>
