yz is a single-binary CLI for instant file sharing backed by Cloudflare R2: type yz myfile.png, get a ready-to-paste public URL. Recipients just click the link; no authentication needed on their side.
- Single static binary: no AWS SDK, no runtime dependencies; under 10 MB for macOS and Linux (amd64 and arm64).
- Fast: terminal-to-link in under ~5 seconds.
- Unguessable keys: files are uploaded under
<16-char random base62>/<filename>prefixes. - Custom domains & r2.dev: automatic detection and configuration of managed
r2.devor custom domains. - Time-bounded & signed shares:
--expiresand--signedfor presigned URLs. - Local history:
yz listshows previous shares, newest first.
$ yz screenshot.png
https://pub-yourbucket.r2.dev/3kK9Xw0Lp2QmZb8N/screenshot.pngbrew install baires/tap/yzPublishing a stable release (vX.Y.Z) updates the Homebrew tap immediately, so Formula/yz.rb does not wait for the tap's hourly poll. Prereleases are skipped.
curl -fsSL https://raw.githubusercontent.com/baires/yz/master/install.sh | shThe script downloads the latest release for your OS and architecture, verifies the published SHA-256, and installs yz onto your PATH. Pin a version or install directory with --version / --bin-dir; see install.sh --help.
go install github.com/baires/yz/cmd/yz@latestDownload a static binary from Releases — names are yz_<tag>_<os>_<arch> — then:
chmod +x yz_*
mv yz_* /usr/local/bin/yzRelease builds look for a newer version at most once every 24 hours. Startup only reads a cache file, ~/.config/yz/update.json (mode 0600). The GitHub request runs in the background while the command runs, with a short timeout, and never runs for a development build, when stdout or stderr is not a terminal, when CI is set, or when YZ_NO_UPDATE_CHECK=1.
When a newer release is already known, yz prints one line to stderr after the command:
yz 1.4.0 is available, run yz update
yz update # download the asset for this OS, verify SHA-256, replace this binary
yz update --check # report onlyEach GitHub release includes checksums.txt next to assets named yz_<tag>_<os>_<arch>. yz update checks the downloaded file against that manifest before it replaces the binary. A Homebrew install prints brew upgrade yz instead of overwriting itself. A go install copy prints go install github.com/baires/yz/cmd/yz@latest.
Background installs are off by default. Turn them on with yz config auto-update on or YZ_AUTO_UPDATE=1. The next time a newer release is available, yz downloads it, verifies the checksum, and swaps the binary in after the command so the following run uses it, then prints yz updated to 1.4.0. Package-manager installs are never replaced.
R2 has to be activated once on the Cloudflare account you want to use. The free tier is enough, but Cloudflare asks you to accept the terms and add a payment method (a card) in the dashboard before the API will list buckets. Until then, bucket calls fail with HTTP 403, error code 10042, and the message Please enable R2 through the Cloudflare Dashboard.
Open the R2 overview for that account. yz setup prints this link after you pick one:
https://dash.cloudflare.com/<account_id>/r2/overview
yz setup lists buckets before it asks you to choose one. When it sees error 10042, it stops on an Enable R2 step, explains the one-time activation, and offers to open the dashboard ($BROWSER when that is set, otherwise the system browser). It checks again every few seconds and continues at the bucket step as soon as R2 is enabled. You do not need to quit and run setup again. Press ctrl+c to cancel.
The account step also reminds you that R2 must be enabled before you pick a bucket.
Cloudflare uses the same HTTP 403 for three different problems. yz setup reads the error code in the JSON body (R2 error codes) and tells them apart:
- R2 is not enabled —
10042NotEntitled, messagePlease enable R2 through the Cloudflare Dashboard.Activate R2 at the link above. Setup waits and continues on its own. - The login is missing the R2 scope —
10003AccessDenied, or10000Authentication error when the token lacks permission for the call. Runyz setupand log in again so the token includesworkers-r2.read. - The account does not match the login —
9109(unauthorized to access that account) or7003(the account id did not route). Runyz setupand choose an account this login can access.
Any other HTTP 403 still asks you to check account access and R2 permissions, then run yz setup again.
yz setupSetup walks you through: browser OAuth login to Cloudflare → picking your account (with a reminder to enable R2) and R2 bucket → choosing a public URL base (existing custom domain, managed r2.dev, or private) → pasting an R2 API token with Object Read & Write. If R2 is not enabled yet, setup waits on the dashboard step instead of failing. Credentials are validated and saved to ~/.config/yz/config.json with 0600 permissions.
$ yz presentation.pdf
https://pub-yourbucket.r2.dev/8xV1K2m9Qp4Lw0Zb/presentation.pdfOnly the URL goes to stdout; progress, clipboard notices, and errors go to stderr. On an interactive terminal the link is copied to your clipboard automatically, and the completion screen lets you press o to open it in a browser.
usage: yz [--signed] [--expires 24h] [--domain HOST] [--clipboard] [file | -]
| Flag | Description |
|---|---|
| (default) | Public URL via your bucket's custom domain or r2.dev |
--expires 2h |
Presigned URL that R2 rejects after the given duration (1s–7d) |
--signed |
Force a presigned URL even when a public base is configured |
| (no file) | Upload the image on the clipboard (macOS, or Linux with wl-paste or xclip) |
- |
Read the file from stdin instead of a path |
--clipboard |
Upload a copied file, image, or text (macOS) |
--domain HOST |
Override the configured URL base for this share |
yz site <dir|zip> |
Publish a static site at <domain>/s/<id>/ |
yz list |
Show previous shares, newest first |
yz version |
Print the version |
yz update |
Install the latest release after verifying its SHA-256 |
yz update --check |
Report whether a newer release exists |
yz config auto-update on |
Download newer releases in the background |
yz --expires=2h secret_report.pdf # expires automatically
yz --signed internal-doc.docx # presigned even with a public domain
yz --domain=cdn.example.com banner.webp
tar -c ./notes | yz - # upload stdin, no temp file of yours
yz # image currently on the clipboard
yz --clipboard # macOS: copied file, image, or text
yz list # previous shares, newest firstyz list prints each share's time, size, filename, expiry, and URL. On a terminal it is an interactive table: enter copies the selected link, d (or Delete) asks for confirmation and then deletes the share from R2 and history (a site removes everything under sites/<id>/; the Worker stays), q quits. Piped output stays plain text. History is stored locally in ~/.config/yz/shares.json (0600). Presigned links, including the private-bucket fallback, are marked with their expiry.
Watch help as MP4. See recording instructions to regenerate the demos with VHS.
$ yz site ./dist
https://files.example.com/s/3kK9Xw0Lp2QmZb8N/yz site uploads a folder (or a .zip; a single top-level folder is unwrapped) to R2 under sites/<id>/. It needs index.html at the site root and skips symlinks and OS junk like .DS_Store. A small Worker, yz-sites, is embedded in the binary and deployed to your account through the Cloudflare API (no wrangler or Node): it binds your bucket and routes <your custom domain>/s/*, serves index.html for directories, falls back to the root index.html for extension-less paths (SPA routing), and serves 404.html when present.
yz setup deploys the Worker when the bucket has a custom domain; otherwise the first yz site does. Sites require a custom domain on a zone in the same account: r2.dev hosts cannot run Workers. The login requests workers-scripts.admin, workers-routes.write, and zone.read for this; logins saved before this release need yz setup once more to grant them.
- Hand-rolled SigV4: streaming
PUTand presignedGETsigning with no AWS SDK dependency. - Streaming uploads: files stream from disk to R2 without buffering whole payloads in memory.
- Path confinement:
os.OpenRootprevents symlink escapes outside the target directory. - Strict permissions: config is saved
0600and world-readable configs are refused. - No usage telemetry: sharing talks only to Cloudflare. A release check may contact GitHub at most once a day; set
YZ_NO_UPDATE_CHECK=1to turn that off.
See SECURITY.md for how to report vulnerabilities.
YZ_CONFIG_DIR: configuration directory (default~/.config/yz)YZ_OAUTH_BASE_URL: override Cloudflare OAuth endpointsYZ_OAUTH_CLIENT_ID: override the built-in OAuth client ID (for forks that register their own Cloudflare OAuth client)YZ_API_BASE_URL: override Cloudflare Client v4 API endpointYZ_R2_ENDPOINT: override the R2 S3 endpointYZ_NO_UPDATE_CHECK: set to1to skip the background release checkYZ_AUTO_UPDATE: set to1to install a newer release in the backgroundYZ_GITHUB_BASE: release host (defaulthttps://github.com; tests may usehttp://127.0.0.1)YZ_REPO: GitHubowner/namethat publishes releases (defaultbaires/yz)
Contributions are welcome — see CONTRIBUTING.md for the development setup, test workflow, and how pull requests turn into releases. Please follow the Code of Conduct.



