references/butler-ci.md
# butler: CI/CD, full flag reference, and the update-check API
Depth for `itch-publish`. Read this for automated uploads, the complete `butler push` flag
set, and notifying players of updates. Verify against the primary docs:
[the butler manual](https://itch.io/docs/butler) (installing, login, pushing).
## 1. Authentication for CI (`BUTLER_API_KEY`)
Interactive `butler login` opens a browser — useless in CI. Instead, set the
**`BUTLER_API_KEY`** environment variable and butler uses it automatically.
Where to get the key:
- Run `butler login` once locally, then read it from the creds file:
- Linux: `~/.config/itch/butler_creds`
- macOS: `~/Library/Application Support/itch/butler_creds`
- Windows: `%USERPROFILE%\.config\itch\butler_creds`
- Or generate one on your itch.io **API keys** page (`itch.io/user/settings/api-keys`) — the
relevant key has its source set to `wharf`.
Store it as a CI secret. **Never** print it; a key leaked into a public build log is
compromised — revoke it immediately on the API keys page.
## 2. Installing butler in CI (use `broth`, not the page link)
The `itchio.itch.io/butler` download links **expire**, so they can't be hard-coded. Use the
permanent **broth** URLs, which always serve the latest stable build of a channel:
```bash
# Example: latest stable Linux amd64 butler. Substitute the channel for your runner OS.
curl -L -o butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
unzip butler.zip
chmod +x butler
./butler -V # prints version; confirms it runs
```
Common broth channels: `linux-amd64`, `linux-arm64`, `windows-amd64`, `darwin-amd64`,
`darwin-arm64`, `darwin-universal` (the list at `broth.itch.zone/butler` is authoritative).
The `-head` channels are bleeding-edge; the others are stable. `.../<channel>/LATEST`
returns the current version number as plain text. The zip also contains two 7-zip helper
libraries — harmless, and not required for `butler push`.
## 3. GitHub Actions example
```yaml
name: Publish to itch.io
on:
push:
tags: ["v*"] # publish when you tag a release
jobs:
butler:
runs-on: ubuntu-latest
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} # set in repo secrets; never echoed
steps:
- uses: actions/checkout@v4
# ... your build steps produce ./build/windows, ./build/linux, etc. ...
- name: Install butler
run: |
curl -L -o butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
unzip butler.zip && chmod +x butler
- name: Push builds
run: |
VERSION="${GITHUB_REF_NAME#v}" # tag v1.2.0 -> 1.2.0
./butler push ./build/windows leafy/my-game:windows --userversion "$VERSION"
./butler push ./build/linux leafy/my-game:linux --userversion "$VERSION"
```
GitLab CI is equivalent: set `BUTLER_API_KEY` as a masked CI/CD variable and run the same
install + `butler push` commands in a job script.
## 4. `butler push` flag reference (checked against butler v15.32.0)
| Flag | Purpose |
|---|---|
| `--userversion <v>` | Set an explicit version string instead of itch's auto-incrementing integer. |
| `--userversion-file <f>` | Read the version string from a file (single line, UTF-8, no BOM). |
| `--if-changed` | Skip the push if it would be an empty patch (contents identical to the latest build). |
| `--hidden` | Mark the upload hidden — **only** when the push creates a *new* channel; errors on an existing one. |
| `--ignore '<glob>'` | Exclude matching files; repeatable (`--ignore '*.pdb' --ignore '*.dSYM'`). Two dashes. |
| `--dry-run` | List every file that would be pushed (+ a summary); uploads nothing and needs no login. |
| `--no-auto-unzip` | Push a folder holding a single `.zip` as that one opaque file. By default (`--auto-unzip`) butler pushes the zip's contents instead. |
| `--dereference` | Follow symlinks and upload copies of their targets (larger builds; use with care). |
| `--fix-permissions` | On by default: detects Mac and Linux executables and sets their executable bit. `--no-fix-permissions` turns it off. |
| `--auto-wrap` | On by default: when the source path is a macOS `.app` bundle, keeps the bundle as the top-level folder of the build. |
| `-i, --identity <file>` | Read/save credentials from another file, to use several itch.io accounts on one machine. |
The target may also be given as a numeric game ID or a game page URL
(`butler push ./build https://leafy.itch.io/my-game:windows`).
Related commands:
| Command | Purpose |
|---|---|
| `butler login` / `butler logout` | Authorize / clear local credentials (browser flow). |
| `butler version` | Print butler's version (confirms install + PATH). |
| `butler which` | Print the full path to the running butler binary. |
| `butler status <user>/<game>` | List channels and their latest builds/versions. |
| `butler push-preview <dir> <user>/<game>:<ch>` | Show `NEW/MODIFIED/DELETED/SAME` vs the last build; add `--changes-only` to hide unchanged. Uploads nothing. |
| `butler upgrade` | Update butler itself to the latest version. |
Notes:
- Processing: after a push the build is immediately live via a fast "default" patch; itch.io
then regenerates an optimized (smaller) patch in the background — around 30 minutes for a
large game, with its status in the channel's build list. Players are never blocked on it.
- Working from a remote/SSH host: `butler login` prints a URL; open it locally, copy the
redirected (non-loading) page's address, and paste it back into the terminal.
## 5. Notify players of updates (no auth required)
Players who download directly (not via the itch app) don't auto-update. Query the latest
user-version for a channel and prompt them in-game:
```text
GET https://api.itch.io/wharf/latest?target=<user>/<game>&channel_name=<channel>
# or by numeric game_id (from the Edit game page URL):
GET https://api.itch.io/wharf/latest?game_id=<id>&channel_name=<channel>
Response: { "latest": "1.2.0" } // omitted if the latest build has no user-version
```
Compare `latest` to your build's version (push with `--userversion`) and surface an
"update available" message. Private games return an "invalid game" error to avoid leaking
unreleased titles.
SKILL.md
---
name: itch-publish
description: >
Publish and update a game on itch.io: create the project page and upload builds with the
butler CLI (butler push) to named channels. Use for itch.io publishing, butler push,
channel naming for Windows/macOS/Linux/HTML5, versioning uploads, or shipping a jam or
release build to itch.io.
---
# itch.io Publish (butler)
Get a build onto an itch.io page and keep it updated. The page is created in the browser; all
uploads go through **butler**, itch.io's command-line tool, with one command you'll use
forever: `butler push`. butler diffs against the previous build and uploads only what
changed. Deep CI/CD and flag detail lives in `references/butler-ci.md`.
## When to use
- Use when creating/updating an itch.io project page, installing or logging in to butler,
uploading a build with `butler push`, choosing channel names, versioning uploads, or
shipping a jam/demo/release build to itch.io.
- Triggers: `butler push`, `butler login`, channels, `.itch.toml`, "publish on itch",
"upload to itch".
**When *not* to use:** publishing on Steam (use `steam-publish`); jam *scope/planning* (use
`game-jam` — this skill is only the upload mechanics); building the game itself (engine
skills).
## Core workflow
1. **Create the project page** at `itch.io/game/new` — butler pushes only to a page that
already exists. Set the **Kind of project**: keep *Downloadable* for native builds, or
choose **HTML** for a browser-playable game (this is required for web builds — see
Pitfalls). Set pricing and keep visibility at **Draft** (or Restricted for testers) until
the page is finished: the first public publish puts it on *Most Recent* once, and it
cannot go back to the top later.
2. **Install butler and log in.** Download from `itchio.itch.io/butler`, add it to `PATH`,
then `butler login` (opens a browser to authorize). Verify with `butler version`. For
CI, use `BUTLER_API_KEY` instead — see the reference. (The itch desktop app also has a
graphical push screen that uses its own bundled butler, if the user prefers no CLI.)
3. **Prepare a portable build folder** — the exact files a player runs, nothing extra. Push a
**folder** (or a single `.zip` *of* that folder), **not an installer** and **not a
pre-compressed archive of archives** (hurts patching; see Pitfalls).
4. **Push to a channel:** `butler push <dir> <user>/<game>:<channel>`. `<user>/<game>` is
the lowercase page address (`finji.itch.io/overland` → `finji/overland`). The channel name
determines the platform tag (see Patterns). The first push uploads everything; later
pushes to the same channel upload only the diff.
5. **Fill in the *Edit game* page, then Save.** Fix platform tags if a channel wasn't
auto-tagged correctly — tick only platforms you have actually run the build on. For
browser games also flip the page to **HTML** and tag the channel *playable in browser*.
Answer the **AI generation disclosure** section if any shipped content was made with
generative AI (mandatory for asset pages, which are delisted without it), and mark adult
content under *Metadata → Classification*.
6. **Version your builds** (optional but recommended): `--userversion 1.2.0` or
`--userversion-file build.txt` so you control the version string players and the update
API see.
7. **Update later** by pushing to the *same* channel again. Use `butler status <user>/<game>`
to see channels/builds and `butler push-preview` to see what a push would change before
sending it.
## Patterns
### 1. The one command you need — `butler push`
```bash
# butler push <directory-or-zip> <user>/<game>:<channel>
butler push ./build/windows leafy/my-game:windows
butler push ./build/mac leafy/my-game:osx
butler push ./build/linux leafy/my-game:linux
butler push ./web leafy/my-game:html # browser build (also set page Kind = HTML)
```
### 2. Channel naming controls the platform tag (kebab-case, lowercase)
```text
Substring in channel name -> auto-applied tag:
win / windows -> Windows linux -> Linux
mac / osx -> macOS android -> Android
Multiple platforms in one channel are allowed: e.g. a Java jar:
butler push ./jar leafy/my-game:win-linux-mac
Convention: lowercase words separated by dashes (windows-beta, osx-demo, soundtrack).
Tags are only the INITIAL guess — fix them anytime on the Edit game page (then Save).
There is no tag for CPU architecture: name the channel (windows-arm64) if you ship several.
```
### 3. Version, verify, and preview
```bash
butler version # print version; confirms install + PATH
butler login # authorize this machine (opens browser)
# Set an explicit version string instead of itch's auto-incrementing integer:
butler push ./build leafy/my-game:windows --userversion 1.2.0
butler push ./build leafy/my-game:windows --userversion-file build_number.txt
butler status leafy/my-game # list channels + latest builds/versions
butler push-preview ./build leafy/my-game:windows # NEW/MODIFIED/DELETED/SAME, uploads nothing
```
### 4. First-time, hidden, and filtered pushes
```bash
# Hide a brand-new channel from the page until you're ready (NEW channels only):
butler push ./build leafy/my-game:windows-beta --hidden
# Exclude files from the upload without copying the folder (--ignore is repeatable):
butler push ./build leafy/my-game:windows --ignore '*.pdb' --ignore '*.dSYM'
# Preview exactly what would be sent, without sending it:
butler push ./build leafy/my-game:windows --dry-run
```
## Pitfalls
- **Pushing an installer.** itch.io patches *portable* builds; an installer (`.exe`/`.msi`)
defeats patching and the itch app's auto-update, and may need admin rights players don't
have. Push the extracted, runnable folder instead.
- **Pre-compressed builds.** Pushing a heavily compressed archive (or an archive of archives)
makes patches huge — a tiny change rewrites the whole compressed blob. Push uncompressed
files; itch.io compresses on its side.
- **A folder containing only one `.zip`.** butler auto-unzips it and pushes the contents
(to avoid a "zip in a zip"). Pass `--no-auto-unzip` only if you truly want the zip uploaded
as one opaque file.
- **HTML5 game shows as a download.** Two switches are required: set the page **Kind** to
*HTML* and tag the channel *playable in browser* on the *Edit game* page after the first
push — neither happens automatically from the channel name.
- **HTML5 build loads a blank page or 403s.** The upload needs `index.html` at its root and
must stay within itch.io's limits: at most 1,000 files, 500 MB extracted, 200 MB per file,
240 characters per path. The CDN is case-sensitive, serves the game from a subfolder (so
paths starting with `/` break — use relative paths), returns 403 for folder URLs, and only
allows HTTPS requests to other domains. HTML5 pages take payment only as donations; sell a
web game by making the page *Downloadable*.
- **`invalid target (bad user)` / `(bad game)`.** The target must match the page address
exactly — subdomain for the user, URL slug for the game, all lowercase — and the page must
exist already.
- **macOS `.app` loses its structure.** Push the `.app` folder itself (or the folder that
contains it); butler keeps the bundle as the top-level directory and marks Mac/Linux
executables as executable by default.
- **`--hidden` on an existing channel errors.** It only applies when the push *creates* a new
channel. Unhide later from *Edit game*.
- **Channel typos make duplicate slots.** `windows` and `win-final` are different channels and
create separate downloads. Decide your channel names up front and reuse them.
- **30 GB cap.** itch.io rejects builds whose total *uncompressed* size exceeds 30 GB.
- **Paid page delisted or payouts frozen.** Selling requires following the payment
processors' rules (itch.io's creator FAQ lists themes card networks will not process), and
adult content must be classified as such. Check before setting a price.
- **Secrets in CI logs.** A `BUTLER_API_KEY` printed in a public log is compromised — revoke
it immediately on the API keys page. See the reference for safe CI usage.
## References
- For CI/CD (GitHub Actions/GitLab) with `BUTLER_API_KEY`, automated install via `broth`, the
full flag list, and the update-check API, read `references/butler-ci.md`.
- Primary docs: the butler manual — `itch.io/docs/butler` (installing, login, pushing);
itch.io creator docs — `itch.io/docs/creators/html5` (web build limits) and
`itch.io/docs/creators/quality-guidelines` (tagging, AI disclosure, delisting rules).
## Related skills
- `steam-publish` — the same game on Steam via SteamPipe (often shipped alongside itch.io).
- `game-jam` — most jams are hosted on itch.io; this skill handles the upload step.
- `prototype-fast` — share an early prototype on a Draft/restricted itch page for playtesting.