Attach & share

Attach & share

The three commands that get media onto a PR, an issue, or a plain URL.

Attach files to a PR #

attach is the command you'll use most. From a checked-out branch with an open pull request, it needs no arguments beyond the files:

uploads attach ./screenshot.png

…which lands on the pull request as one managed comment:

Run attach again with new files and this same comment updates in place, so your PR doesn't collect a trail of stale screenshot comments.

It uses the gh CLI to find the current repo and PR. If you're not on a PR branch, or you want a different target, say so explicitly:

uploads attach ./shot.png --pr 123 # a specific PR
uploads attach ./shot.png --issue 45 # an issue instead
uploads attach ./shot.png --repo owner/name --pr 123 # a different repo

Want the URLs and ready-to-paste Markdown without touching any GitHub comment (say, to drop an image into a PR description yourself)?

uploads attach ./shot.png --no-comment # print URLs, post nothing

No PR yet? --branch stages files against your current branch instead, so you can attach screenshots while you work:

uploads attach --branch ./shot.png

Once the PR opens, the next attach promotes those staged files onto it automatically β€” or run uploads attach --promote with no files to do it explicitly (--no-promote opts out). See GitHub App: staging before a PR exists for how this works with zero CLI involvement.

Get a URL for any file #

put is the simpler building block: upload one file, get back a public URL and Markdown you can paste anywhere.

uploads put ./shot.png
# re-encoded to WebP, then hosted at a stable public URL
>> optimized 411.5 KB β†’ 94.2 KB (shot.webp)
URL: https://storage.uploads.sh/screenshots/app/2026-07-12/shot-9f2c1a.webp
MARKDOWN: ![shot.png](https://storage.uploads.sh/screenshots/app/2026-07-12/shot-9f2c1a.webp)

A few useful variations:

uploads put ./shot.png --pr 123 --comment # also post to the PR
uploads put ./shot.png --no-optimize # upload the original bytes
uploads put ./mobile.png --frame phone # add device chrome

By default, images are re-encoded to WebP (capped size, high quality) so GitHub embeds stay fast, and EXIF metadata is stripped. Use --keep-exif to keep metadata, or --no-optimize to skip all processing.

Each file also gets a shareable page on uploads.sh (under /f/…) that supports oEmbed β€” so chat apps, notes tools, and other unfurlers can embed the image when you paste the page link. Details are inReference.

Capture a screenshot #

Don't have the image yet? screenshot renders a URL or a local.html file and hosts the result in one step, with no browser install required.

uploads screenshot https://app.example --pr 123 --comment
# renders the page, then hosts the WebP in one step
>> captured via local backend
URL: https://storage.uploads.sh/gh/app/example/pull/123/app.example.webp
MARKDOWN: ![app.example](https://storage.uploads.sh/gh/app/example/pull/123/app.example.webp)

A few useful variations:

uploads screenshot ./report.html --dark --selector "main" # dark scheme
uploads screenshot ./card.html --no-upload --out ./card.png # no hosting
uploads screenshot http://localhost:4321 --reduced-motion # clean dev-server shot

If a Chrome or Chromium is already on the machine, screenshot drives it directly (nothing to download); otherwise it renders on uploads.sh servers. Force either side with --via local or --via remote. Note thatlocalhost URLs are only reachable locally.

Shooting your own dev server, it hides known framework dev toolbars (Astro, Next, Nuxt, Vite) automatically (opt out with--no-hide-dev-tools) and takes --reduced-motion to settle animations. Hide any other overlay with --hide <selector>(repeatable), or run setup JS first with --eval <js> /--init-script <file> (local backend).