# Environment variables

Override wavedash.toml fields with WAVEDASH_* environment variables: precedence, per-variable behavior, and running the CLI without a config file.

Source: https://docs.wavedash.com/cli/environment-variables

Every field the CLI reads out of `wavedash.toml` has a `WAVEDASH_*` counterpart in the environment. Set one and it wins for that run, which leaves the committed config as the default and lets a single repo push to a different game, from a different output directory, or with a different engine version per job — without editing a tracked file.

## The variables

| Variable | Overrides | Read by |
|----------|-----------|---------|
| `WAVEDASH_GAME_ID` | `game_id` | `dev`, `build push`, `publish`, `stat *`, `achievement *`, `clear-playtest-data` |
| `WAVEDASH_UPLOAD_DIR` | `upload_dir` | `dev`, `build push` |
| `WAVEDASH_ENTRYPOINT` | `entrypoint` | `dev`, `build push` — engine-less builds only |
| `WAVEDASH_GODOT_VERSION` | `[godot].version` | `dev`, `build push` |
| `WAVEDASH_UNITY_VERSION` | `[unity].version` | `dev`, `build push` |
| `WAVEDASH_TOKEN` | the stored credentials file | every command that reaches the Wavedash API |

`wavedash init` is not in that list. It *writes* a `wavedash.toml` rather than reading one, so overrides don't affect it.

<Note>
`WAVEDASH_TOKEN` is the one variable that isn't a `wavedash.toml` field — it stands in for the credentials file `wavedash auth login` writes, not for anything in your project config. It follows the same rules as the rest of this page, and [Authentication](/cli/authentication) covers how it fits into signing in.
</Note>

`[jsdos]`, `[ruffle]`, and `[renpy]` have no version override. Their versions come from `wavedash.toml` only.

## Precedence

Each field is resolved on its own, in this order:

```text
command-line flag   >   WAVEDASH_* variable   >   wavedash.toml   >   built-in default
```

Two ends of that chain are narrower than they look. `--game-id` is the only flag in it, and only `stat`, `achievement`, and `clear-playtest-data` accept it — `dev`, `build push`, and `publish` have no flag for the game, so the variable is the only way to retarget them. And `entrypoint` is the only field with a built-in default (`index.html`); every other field reports a missing-field error when no source supplied it.

So with a committed config pointing at your production game:

```toml
game_id = "prod_game_id"
upload_dir = "./dist"
```

...this run uploads to a different game and leaves the file untouched:

```bash
WAVEDASH_GAME_ID=test_game_id wavedash build push -m "smoke test"
```

...and where a flag exists, it beats both:

```bash
# Targets third_game_id, whatever WAVEDASH_GAME_ID says
WAVEDASH_GAME_ID=test_game_id wavedash stat create \
  --game-id third_game_id --identifier KILLS_TOTAL --name "Total Kills"
```

## The CLI announces the overrides it uses

When a value comes from the environment, the CLI says so on stdout before it acts:

```text
env override: WAVEDASH_GAME_ID → game_id = test_game_id
env override: WAVEDASH_UPLOAD_DIR → upload_dir = ./build/web
```

Each field is announced once, the first time the command reads it. That means a command only ever mentions the overrides that can affect it: `wavedash publish` reads `game_id` and never looks at an entrypoint, so a stray `WAVEDASH_ENTRYPOINT` left over in your shell isn't reported there — while `wavedash build push`, which does read it, reports it.

A `--game-id` flag is applied silently. You're looking at the value you just typed, and it beating a differing variable is the documented precedence rather than a surprise.

<Tip>
If a build lands somewhere you didn't expect, scroll to the top of the output. An `env override:` line naming a variable you forgot you exported is the usual explanation.
</Tip>

## Blank counts as unset

A variable that is set but empty (or only whitespace) is treated as though it weren't set at all, and the CLI falls back to `wavedash.toml`. This is aimed at the CI shape where a variable references something that doesn't exist:

```yaml
env:
  WAVEDASH_GAME_ID: ${{ vars.STAGING_GAME_ID }}   # typo'd or unset → empty string
```

Without that rule, an unpopulated variable would wipe out the `game_id` your config file supplies and the run would fail on a missing field. With it, the committed value still applies.

Values are also trimmed, so a token read from a file keeps working despite the trailing newline:

```bash
export WAVEDASH_TOKEN=$(cat key.txt)
```

The same treatment applies to values written in `wavedash.toml`: `game_id = ""`, `upload_dir = ""`, and `version = ""` all mean *unset* rather than "empty string", and produce the same missing-field error as leaving the line out. That matters most for `upload_dir`, where an empty string would otherwise point at your project root and stage the entire repo — source files, dotfiles and all.

## Running without a wavedash.toml

The config file is optional as long as the environment supplies what the command reads. If there's no file at `--config` (default `./wavedash.toml`) but at least one config override is set, the CLI carries on and only fails when a command asks for a field that neither source provided:

```bash
export WAVEDASH_TOKEN=your_api_key
export WAVEDASH_GAME_ID=your_game_id
export WAVEDASH_UPLOAD_DIR=./dist

wavedash build push -m "no config file in sight"
```

This is for ephemeral checkouts, container builds, and tools that drive the CLI programmatically — anywhere checking in a `wavedash.toml` just to satisfy the loader would be noise. For a normal project repo, commit the file: it documents the game and output directory for everyone else working on it.

<Warning>
`WAVEDASH_TOKEN` alone does not make the config file optional. It isn't a config field, so it can't stand in for one — a directory with only a token set still reports the missing `wavedash.toml`.
</Warning>

Relative `upload_dir` values — from either source — resolve against the config file's directory, not your current directory. With the default `./wavedash.toml` those are the same place, but when you pass `--config ../other/wavedash.toml`, `WAVEDASH_UPLOAD_DIR=./dist` means `../other/dist`. An absolute path avoids the question.

## Engine version overrides

When your config already declares an engine, the matching variable sets its version:

```toml
[godot]
version = "4.4-stable"
```

```bash
WAVEDASH_GODOT_VERSION=4.5-stable wavedash build push
```

```text
env override: WAVEDASH_GODOT_VERSION → [godot].version = 4.5-stable
```

When your config declares **no** engine section, the variable brings one into play — it decides the engine for the build, not just its version. The CLI calls that out separately, along with the `entrypoint` the change strands, because engine builds boot through Wavedash's own entrypoint rather than a file in your `upload_dir`:

```text
env override: WAVEDASH_GODOT_VERSION → [godot].version = 4.5-stable (config declared no engine, so [godot] is now in play)
env override: entrypoint = index.html from the config is no longer used — [godot] builds boot through wavedash's own entrypoint
```

### Combinations the CLI refuses

Some combinations have no sensible winner — switching engines behind your back would upload a build the site can't boot — so `wavedash dev` and `wavedash build push` refuse them instead of guessing:

- **Both version variables are set.** A build targets a single engine; unset whichever doesn't apply.
- **A version variable names a different engine than the config declares.** `WAVEDASH_UNITY_VERSION` against a `[godot]` config — or against `[jsdos]`, `[ruffle]`, or `[renpy]` — is refused. Remove one so the build targets a single engine.
- **`WAVEDASH_ENTRYPOINT` is set while an engine is in play.** Engine builds ignore it, and a silently discarded value is indistinguishable from success in CI. Unset the variable, or drop the engine that brought it into play — whether that's a section in the file or a version variable.

These checks run on the reads that a build performs. `publish`, `stat`, `achievement`, and `clear-playtest-data` only need a `game_id`, so a conflicting engine variable sitting in the same shell doesn't stop them — a stale export can't break commands that never look at an engine.

## Example: pushing to a test game from CI

A repo whose committed `wavedash.toml` points at the production game, with a workflow that pushes every branch build to a separate test game instead:

```yaml
name: Push test build

on:
  push:
    branches-ignore: [main]

jobs:
  push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build game
        run: npm run build

      - name: Install Wavedash CLI
        run: curl -fsSL https://wavedash.com/cli/install.sh | sh

      - name: Push build
        run: wavedash build push -m "Branch build ${{ github.sha }}"
        env:
          WAVEDASH_TOKEN: ${{ secrets.WAVEDASH_TOKEN }}
          WAVEDASH_GAME_ID: ${{ vars.TEST_GAME_ID }}
          WAVEDASH_UPLOAD_DIR: ./dist
```

Nothing in the repo changes between the two destinations — the environment decides, and the run's output names the override it used.

## Next steps

- [Configuration](/cli/configuration) — the `wavedash.toml` fields these variables override
- [Authentication](/cli/authentication) — signing in, and where `WAVEDASH_TOKEN` fits
- [CI/CD pipeline](/tutorials/ci-cd) — a full pipeline that uploads on every merge
