# Environment variables

> The six variables a generated app reads, their dev defaults, what production insists on, and how to load them.

> Agents: search these docs with the `search_docs` tool on the MCP server at https://createbsvapp.vercel.app/mcp, or read everything at https://createbsvapp.vercel.app/llms-full.txt.

All configuration lives in one file per app, `client/src/bsv/config.ts` and `server/src/bsv/config.ts`. Each reads the environment once, validates it, and exports typed constants. The rest of the code imports those constants instead of reading `process.env` directly.

## The variables

### Client (Vite)

| Variable | Default in dev | Production | What it does |
| --- | --- | --- | --- |
| `VITE_API_URL` | `http://localhost:3000` | **required**, must be `https://` | where the API lives; every `apiFetch` goes here |
| `VITE_BSV_NETWORK` | `test` | optional | `main`, `test` or `ttn` |

### Server (Node)

| Variable | Default in dev | Production | What it does |
| --- | --- | --- | --- |
| `SERVER_PRIVATE_KEY` | random on every start | **required** | the server's identity key, used to verify proofs |
| `CLIENT_ORIGIN` | `http://localhost:5173` | **required**, must be `https://` | the one browser origin CORS allows |
| `PORT` | `3000` | optional | 1–65535 |
| `BSV_NETWORK` | `test` | optional | `main`, `test` or `ttn` |

"Production" means `NODE_ENV=production` on the server, and `vite build` on the client (Vite sets `import.meta.env.PROD`). In production the app **refuses to start** with a missing or invalid value, instead of quietly falling back to a dev default:

```text [what you'll see]
Error: VITE_API_URL is required in production
Error: SERVER_PRIVATE_KEY is required in production
Error: CLIENT_ORIGIN is required in production
Error: CLIENT_ORIGIN must be credential-free HTTPS (or exact HTTP localhost development) origin
```

That's deliberate. A server that silently picks a random identity, or a client that silently calls `localhost`, is a bug you'd rather find at deploy time than from your users.

::: warning `VITE_*` values are public
Vite inlines `VITE_` variables into the JavaScript bundle at build time, and anyone can read them. They're fine for URLs and network names. Never put a secret in one. `SERVER_PRIVATE_KEY` belongs on the server only.
:::

## Generate a server key

Run this from the `server/` folder, where `@bsv/sdk` is installed:

```bash
node --input-type=module -e "import { PrivateKey } from '@bsv/sdk'; console.log(PrivateKey.fromRandom().toString())"
```

It prints 64 hex characters. Treat the key like a password: it *is* your server's identity. If it changes, clients see a different server identity. If it leaks, someone else can impersonate your server.

::: deep Why does a dev restart change the server's identity?
With no `SERVER_PRIVATE_KEY`, the server makes a fresh random key on each start. Clients fetch the identity again (`GET /api/identity`), so dev keeps working. Anything that *pinned* the old key, or proofs signed for it in the last two minutes, stops matching. Set a key in dev too once you start [pinning](https://createbsvapp.vercel.app/docs/security#server-identity-is-trusted-via-your-api-origin).
:::

## Load them

**Client:** Vite loads `client/.env`, `client/.env.local` and `client/.env.production` for you. Only `VITE_` variables reach the browser.

```dotenv [client/.env.production]
VITE_API_URL=https://api.example.com
VITE_BSV_NETWORK=main
```

**Server:** nothing loads `server/.env` automatically, and that catches people out. Either export the variables in your shell or host, or tell Node to read the file:

::: code-group
```bash [dev]
# server/package.json → "dev": "tsx watch --env-file=.env src/index.ts"
npm run dev
```
```bash [production]
npm run build
NODE_ENV=production node --env-file=.env dist/index.js
```
:::

```dotenv [server/.env]
SERVER_PRIVATE_KEY=7b28…c8
CLIENT_ORIGIN=https://app.example.com
PORT=3000
BSV_NETWORK=main
```

::: danger Keep `.env` out of git
The client's `.gitignore` comes from create-vite. It ignores `*.local` files but not `.env`, and the server and project root have no `.gitignore` at all. Add one at the project root before your first commit:

```bash
printf 'node_modules\n.env\n.env.*\n!.env.example\ndist\n' > .gitignore
```
:::

## Networks

`--network` sets the default baked into both configs, and the env variables override it per deployment.

| Value | Network | Use it for |
| --- | --- | --- |
| `test` | testnet | development. Coins are free and worthless |
| `ttn` | Teratestnet | testing against the newer Teranode network |
| `main` | mainnet | production. Real money |

Login and signed requests are pure signatures, so they work identically on every network. The network matters once you start creating transactions. See [BSV for web devs](https://createbsvapp.vercel.app/docs/bsv-primer#networks).
