Postr
active · since 2026A self-hosted web app to manage poster artwork in a Plex or Jellyfin library: upload, queue, push, restore.
- ▸Go 1.27, Echo v5
- ▸Vue 3, TypeScript, Nuxt UI, Pinia
- ▸SQLite (pure Go, no cgo), sqlc, goose
- ▸Server-Sent Events
- ▸Docker (single binary image)
- ▸source ↗
- ▸license: MIT


What it does
Plex and Jellyfin both let you replace a poster, one item at a time, through a dialog nobody enjoys. Postr puts the whole library on one page: filter by type (movies, series, seasons, collections), sort, search, then drag an image in or paste a URL, including from poster sites such as ThePosterDB. Changes go to a queue, not straight to the server, so you review a batch and push it when you're happy with it. Anything can be restored back to what the server currently holds.
It talks to one server at a time, picked with an environment variable, and every label and error message follows the one you configured. Items that vanish from the server are flagged as orphaned rather than silently dropped, and end up in their own tab.
The part that took the most care is migration between servers. Plex and Jellyfin use different identifiers, so the posters you collected on one don't follow you to the other for free. Postr pairs titles through the TMDB / IMDB / TVDB ids both servers store, falls back to the title when there is none, and drops the matches into the queue instead of pushing them. Nothing is guessed at silently: what couldn't be matched with confidence is listed at the end.
How it's built
One Go binary with the Vue app embedded in it (go:embed), so the Docker image
is a single process and the whole thing runs off a SQLite file. No cgo: the
modernc.org/sqlite driver is pure Go, which keeps the image small and the
cross-compilation boring. Queries are generated by sqlc
from
plain SQL, migrations run at startup with goose.
Import and sync walk an entire library and download hundreds of images, so they report progress over Server-Sent Events: one long-lived connection, a progress bar that actually moves, and a recap at the end (added, skipped, deleted). SSE rather than WebSockets because the traffic only goes one way.
Why
A homelab problem: my library's artwork was a mix of whatever each server had scraped, and fixing it through the Plex UI one poster at a time was not a real option. The project was also a good excuse to build a small, complete Go service (config, migrations, background work, streaming progress, auth) in a shape I could reuse. Watchue is the same skeleton, pointed at something else entirely.