Drop images into GitHub issues, PRs, and READMEs, straight from the command line.
GitHub has no public API for image uploads. The web UI uses an internal endpoint that produces user-attachments URLs whose visibility is scoped to the repository they were uploaded to. gh-image replicates that flow as a gh CLI extension, so you can drop a screenshot into a bug report, README, or Slack thread without leaving the terminal — and images on private repos stay private.
$ gh image screenshot.png
gh extension install drogers0/gh-imageThat's it. The gh CLI auto-detects your platform and downloads the prebuilt binary. Pre-built releases ship for macOS (arm64, amd64), Linux (amd64, arm64), and Windows (amd64).
Build from source
git clone https://github.com/drogers0/gh-image
cd gh-image
go build -o gh-image
gh extension install .Requires Go 1.26+.
# Upload an image (infers repo from the current git workspace)
gh image screenshot.png
# Upload multiple images at once
gh image hero.png diagram.png chart.png
# Target a specific repository
gh image screenshot.png --repo owner/repoEach successful upload prints a ready-to-paste markdown reference on its own line:


If any upload fails, the error is printed to stderr and the process exits non-zero — other images in the batch still upload.
From inside the repo's working directory, both gh image and gh issue create infer the target repository automatically:
gh issue create \
--title "Login button stuck in loading state" \
--body "Repro on staging:
$(gh image bug.png)
Happens consistently after the third click."gh-image authenticates with your existing GitHub session — no tokens to provision, no OAuth scopes to configure for everyday local use. The tool reads the user_session cookie from your browser's encrypted cookie store.
Supported browsers: Chrome · Brave · Chromium · Edge · Firefox · Opera · Safari
Supported platforms: macOS · Linux · Windows
On macOS, a Keychain prompt may appear on first use to authorize access to your browser's cookie encryption key. Click Always Allow to skip future prompts.
Note
Windows + Chrome 127+: Some versions of Chrome on Windows are not yet supported by the underlying cookie library. Use another browser or investigate potential workarounds.
For CI, headless environments, or shared machines, you can supply the session token explicitly. Resolution order (first match wins):
| Priority | Source | When to use |
|---|---|---|
| 1 | --token <value> flag |
One-off invocations |
| 2 | GH_SESSION_TOKEN env var |
CI/CD, shared machines |
| 3 | Browser cookie store | Local interactive use (default) |
# Flag (visible in process listings like `ps aux` — avoid on shared machines)
gh image --token "$MY_TOKEN" screenshot.png --repo owner/repo
# Environment variable (preferred — not visible to `ps aux`)
GH_SESSION_TOKEN="$MY_TOKEN" gh image screenshot.png --repo owner/repoWarning
user_session cookies grant full account access — they are not scoped like personal access tokens. Treat them with the same care as a password. If leaked, sign out of GitHub on the machine that holds the session; if you are not on that machine, revoke it through Settings → Sessions, or change your password (which kills every session in one action).
gh-image runs unattended in GitHub Actions when given a session token via GH_SESSION_TOKEN.
Caution
Use a dedicated bot account for CI/CD on shared repos. GitHub hides secret values in the UI and masks log emissions, but a determined collaborator with write access can craft a workflow that exfiltrates the value through channels masking doesn't cover. Storing your personal user_session means such a leak compromises your account; a bot account scopes the blast radius to that bot. Decide whose token to extract in step 1 below accordingly.
Setup
- Run
gh image extract-tokenlocally to capture the token (token → stdout, status → stderr), then rungh image check-token --token <token>to confirm it authenticates as the intended user (username → stdout on success, exit code0= valid). - Create a GitHub environment (Settings → Environments → New environment), e.g.
gh-image, and restrict deployment branches to a trusted set (e.g.mainonly). - Add the token as an environment secret named
GH_SESSION_TOKENon that environment.
jobs:
upload:
runs-on: ubuntu-latest
environment: gh-image # binds this job to the scoped environment
steps:
- name: Upload screenshots
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for gh CLI auth
GH_SESSION_TOKEN: ${{ secrets.GH_SESSION_TOKEN }} # for the upload itself
run: |
gh extension install drogers0/gh-image
gh image check-token # optional: fail fast if the session expired
gh image screenshot.png --repo ${{ github.repository }}Note
user_session cookies expire when GitHub invalidates the session. A scheduled check-token job is the cleanest way to detect expiry before it breaks a real run.
- Resolves a
user_sessioncookie from the configured source (flag → env → browser). - Fetches the target repository's page to obtain an
uploadTokenfrom the embedded JS payload. - Requests an S3 upload policy from
/upload/policies/assets. - Uploads the file directly to S3 using the presigned form fields.
- Calls back to GitHub to finalize the asset.
- Prints
to stdout.
The final URL is the standard https://github.com/user-attachments/assets/<uuid> format — visibility inherits from the target repository, so a private-repo upload requires authentication to view.
For the full architecture, see documentation/architecture.md. For the reverse-engineered upload protocol, see documentation/github-image-upload-flow.md.
- A supported browser with an active GitHub session — or a
GH_SESSION_TOKENfor CI. - Write access to the target repository (uploads require it).
- A target repository — pass
--repo owner/repo, or run from a git workspace whoseoriginremote is on GitHub. - The
ghCLI must be installed and authenticated (used for repository ID lookup).
- Uses an undocumented internal GitHub API that may change without notice.
uploadTokenis only issued to users with write access on the target repository.- Session cookies are not scoped credentials; they expire when GitHub invalidates the session.
Issues and pull requests are welcome. For bug reports, please include:
- Your OS and browser
- The exact
gh imageinvocation - The error output (with any session token values redacted)
Before opening a PR, run go test ./... and go vet ./....
If gh-image saves you a few drag-and-drops, a ⭐ helps others find it:
gh api --method PUT user/starred/drogers0/gh-image(or just click the star at the top of this page)
MIT © 2025-2026 drogers0
