pixa
A terminal client for Pixabay — search images and videos,
preview them in place, and pull down exactly the ones you want.
Built for filling an app with realistic test assets: point it at a directory,
grab twenty photos, run it again next week and it only fetches what's new.

Install
go install github.com/fdddf/pixa@latest
Requires Go 1.24 or newer. Prebuilt binaries for macOS, Linux and Windows are on
the releases page.
Set up your API key
Pixabay keys are free. Sign in and copy yours from
pixabay.com/api/docs, then:
pixa config set-key # prompts, keeps the key out of your shell history
The key is verified against the API before it is saved, to
~/.config/pixa/config.json (~/Library/Application Support/pixa on macOS) with
mode 0600. pixa also accepts --key or $PIXABAY_API_KEY, in that order of
precedence — use the environment variable in CI.
Use it
Browse and pick. No arguments opens the interactive browser:
pixa
pixa search "cat orientation:vertical"
Move with ↑↓, space to select several, d to download them. Press ? for
the full key list.
Or skip the UI. pixa get is the scripted form:
# 20 wide landscape photos into ./assets
pixa get sunset --count 20 --min-width 1920 --orientation horizontal -o ./assets
# short clips at 1280x720
pixa get ocean --video --max-duration 15 --variant medium -o ./assets/video
# specific IDs
pixa get --id 1045782 --id 2536662 -o ./assets
# see what it would do, as JSON
pixa get cat --count 5 --dry-run --json
Re-running a command only downloads what isn't already there — see
Repeat runs.
Filters
Every filter works as a flag on pixa get, and as an inline key:value token
in the search box or a quoted query. These two are the same request:
pixa get cat --orientation vertical --min-width 1920 --category animals --no-ai
pixa get "cat orientation:vertical w:1920 category:animals -ai"
| Flag |
Inline |
Values |
--type |
type: |
photo illustration vector all — with --video: film animation all |
--orientation |
orientation: o: |
horizontal vertical all |
--category |
category: c: |
nature people animals food travel business … (20 total) |
--colors |
colors: |
red blue grayscale transparent … comma-separated |
--min-width |
w: |
pixels |
--min-height |
h: |
pixels |
--lang |
lang: l: |
en de fr zh ja … (26 total) |
--order |
order: |
popular latest |
--editors-choice |
editors:1 |
Editor's Choice only |
--safesearch |
safe:1 |
suitable for all ages |
--min-duration / --max-duration |
mins: / maxs: |
video seconds |
--no-ai |
-ai |
skip AI-generated media |
Invalid values are rejected before a request is spent, and the error lists what
would have been accepted.
Sizes
Pixabay offers each item at several sizes. --variant picks one:
- Images —
original, fullhd, large (1280px), webformat (640px), preview (150px)
- Videos —
large, medium, small, tiny
best (the default) and worst resolve to the largest and smallest the item
actually has. In the TUI, v cycles through the sizes the item under the cursor
offers.
Standard API keys top out at 1280px. original, fullhd and vectorURL
need full API access, which Pixabay grants on request. Note that --min-width
filters on the original dimensions, so --min-width 1920 still hands you
1280px files on a standard key. pixa get prints a note when that happens.
Repeat runs and deduplication
Each download directory gets a .pixa-manifest.json recording every file: its
Pixabay ID, size variant, SHA-256, contributor and source page. A later run
checks the manifest before fetching, so:
pixa get cat --count 20 -o ./assets # downloads 20
pixa get cat --count 20 -o ./assets # downloads 0, all already there
pixa get cat --count 30 -o ./assets # downloads only the 10 new ones
Delete a file and it's fetched again — the manifest is checked against what's
actually on disk. --overwrite refetches everything.
Attribution
Pixabay asks you to show users where media came from. The manifest already holds
what you need:
pixa credits ./assets # plain text
pixa credits ./assets --format markdown -o ./assets/CREDITS.md
pixa credits ./assets --format csv
Previews in the terminal
pixa preview <id> and the TUI's side panel draw the actual image:
- iTerm2 — inline images protocol
- Kitty, Ghostty, WezTerm — kitty graphics protocol
- everything else — half-block characters (
▀ with a foreground and
background colour, two pixels per cell), which needs only 24-bit colour
- no colour — ASCII
Detection is automatic; override with --preview iterm|kitty|blocks|ascii|none
or $PIXA_PREVIEW. Inside tmux and screen, pixa stays on half-blocks, since
graphics passthrough is unreliable there. The TUI always uses half-blocks, which
are ordinary text and so survive redraws intact.
Filenames
The default is {id}-{tags}-{width}x{height}{ext}, e.g.
1045782-cat-animal-cat-portrait-1280x874.jpg. Change it with --name:
pixa get cat --count 5 --name "{kind}-{id}{ext}" -o ./assets
Placeholders: {id} {tags} {user} {kind} {type} {variant} {width}
{height} {duration} {ext}.
Scripting

--json writes a machine-readable report to stdout; progress goes to stderr, so
the two never mix.
pixa get cat --count 10 --json -o ./assets | jq -r '.files[] | "\(.id)\t\(.path)"'
Exit status is non-zero if any download failed. --quiet silences everything but
errors.
All commands
| Command |
What it does |
pixa [query] |
interactive browser (same as pixa search) |
pixa get [query] |
non-interactive download |
pixa info <id> |
sizes, tags and attribution for specific media |
pixa preview <id> |
draw a preview in the terminal |
pixa credits [dir] |
attribution list from a download directory |
pixa config set-key / show / set / path |
API key and defaults |
pixa cache path / clear |
inspect or clear the local cache |
pixa version |
version, commit, build date |
Defaults you'd otherwise retype:
pixa config set output_dir ~/dev/assets
pixa config set variant large
pixa config set safesearch true
Playing by Pixabay's rules
- Responses are cached for 24 hours, as the API terms require. Repeating a
search reads from
~/.cache/pixa instead of the network. pixa cache clear
empties it; --no-cache bypasses it for one run.
- Requests are rate limited to Pixabay's documented 100 per 60 seconds,
tracked per key from the
X-RateLimit-* headers, with backoff on 429.
- Media is downloaded, not hotlinked — Pixabay's CDN URLs expire and the
terms forbid linking to them from a live app.
- Any query returns at most 500 results, a Pixabay limit. The TUI shows
500 of N hits reachable when a search is larger than that; narrow the query
to reach different media.
- Pixabay asks that you not run systematic mass downloads. This is a tool for
picking assets, not for mirroring the library.
Media is provided under the
Pixabay Content License.
Development
go test ./...
go build -o pixa .
The recordings in demo/ are made with vhs.
The tapes read the API key from the environment, so they never contain one:
PIXABAY_API_KEY=<your key> vhs demo/demo.tape
PIXABAY_API_KEY=<your key> vhs demo/get.tape
Layout:
cmd/ cobra commands
internal/pixabay/ API client, query model, 24h response cache
internal/download/ concurrent downloader, manifest, dedupe
internal/preview/ image decoding, half-blocks, iTerm2/kitty protocols
internal/tui/ bubbletea browser
internal/config/ key resolution and user defaults
Licence
MIT — see LICENSE. This project is not affiliated with Pixabay.