
Flick for Plex
2026 · iOS app · On the App StoreLive
A Plex client with no backend at all — the phone talks to plex.tv and to your own server directly, so there is nothing to host and no user data to lose.
- React Native
- Expo
- TypeScript
- Plex API
- RevenueCat
Live
The problem
A media library grows faster than anyone watches it, and the tools for triaging one are all lists. Lists are the wrong shape for a decision you make in half a second. The web prototype needed a server-side proxy purely because browsers enforce CORS and Plex sends no permissive headers — which meant hosting, and hosting meant an account, and an account meant holding somebody's media history.
What I built
- A native client with genuinely no backend. A phone has no CORS restriction, so it talks to plex.tv and to the user's own server directly — nothing to host, nothing to pay for, and no user data in existence to lose.
- PIN sign-in rather than a password field: the app opens Plex's own page and polls for a token, so no password ever reaches the app and there is no client secret to ship. The redirect targets the app root, because an unmatched route unmounts the sign-in screen underneath the user.
- Four swipe modes that change what a gesture means — tidy up, build a watchlist, fix watch history, fill a playlist — while keeping the same four gestures, so there is nothing to relearn.
- No swipe can delete media. Binned titles go to a Plex playlist to review later; permanent deletion is off by default, has to be enabled deliberately, and confirms per file.
- Tests cover what fails silently rather than loudly: ad cadence, and deck bookkeeping where a double-fired gesture must not count a title twice.
What I took from it
Shipping revealed a bug that only existed in the real build. Plex rejects a custom URL scheme as a forward target — it abandons sign-in and lands the user on its own web app instead. It never reproduced in development because that parameter was suppressed in Expo Go, so every simulator run tested a path the shipped binary did not take. The contract check now asserts the parameter is absent.


