Nexview is a self-hosted media discovery dashboard for a household — family, flatmates, a circle of friends. It shows new releases from TMDB, marks what is already in your library, and hands requests to whatever procures for you: Radarr (movies) and Sonarr (shows), or nexcrate, which does both in one. Everyone gets their own account, with roles, approvals and quotas.
You pick one of the two when you set Nexview up, and you can switch later — an assistant walks through it and writes a backup first, because there is no other way back.
It downloads nothing itself and stores no media. It is the front door to a setup you already run — it does not replace any part of it.
Without a TMDB key Nexview starts with sample data, so you can look around before deciding anything.
The home page. What each person marks as a favourite shapes what they get suggested.
Requesting a title, and the same installation seen by a child.
Finding something
- Separate areas for movies and shows, each with its own logic
- Filter by period, language, region, genre, rating and studio; cards or list
- Detail pages with cast photos, directing and writing credits, studios, keywords, similar titles and the trailer in a window
- Ratings from IMDb, Rotten Tomatoes and Metacritic on the title itself
- Where a title is currently streaming in your region, split into subscription, free, rent and buy (data from JustWatch)
- Browse people — actors, directors, writers — and mark them as favourites; your favourites, titles and people alike, shape what gets suggested on your home page
- A release calendar, one week at a time: your own titles kept apart from new releases, with cinema and digital dates read for your region
Requesting
- Pick quality profile and target folder, or let the approver pick them instead
- Request whole shows or single seasons, optionally following future ones
- With nexcrate there is one connection instead of four, and quality profiles, naming, folders and the recycle bin live over there. Nexview then shows what it keeps: the versions of a title, why something has not arrived yet, and what can be restored
- Optionally a second Radarr and Sonarr instance for 4K: the same title once in 1080p and once in 4K, with separate folders, profiles and per-user permissions
- The state sits on the poster — not requested, requested, searching, already downloaded, in library, blocked — and updates itself as Radarr and Sonarr work
Who may do what
- Three roles: administrator, approver, user. An approver decides on requests without ever getting near your API keys
- Approve automatically or by hand, set separately for movies, shows and 4K
- Rules decide before that setting applies: an ordered list where every rule is a handful of conditions joined by AND and one consequence, approve at once or decline. The first rule that matches wins; if none does, the account setting decides as it always did. Conditions read the type, genre, rating, number of votes, year, runtime, original language, age rating, the requested tier, and whether the title is already here in the other tier. Approving may book it to house stock, where it counts against nobody's storage quota
- A decline is not a shrug: the request is created in the declined state, carries the reason you wrote and names the rule that decided. Per rule the requester may send it to an approver anyway, once, and never past a decision a person made. Nothing overrides the age filter, a parent's decision or the requester's own quota, and administrators and approvers are exempt from rules altogether
- Quotas per day, week or month — or, instead of counting titles, by disk space in gigabytes, which is the thing that actually runs out
- Child accounts: a parent creates a login for their child, with an age, a set of categories and a language. The child gets a separate, simpler app and does not request but wishes — nothing happens until the parent approves, and the request then runs in the parent's name
- A block list for titles that should stay out: visible, but not requestable
- Sign in through Authentik, Keycloak, Pocket ID, Authelia, Zitadel, Google or any other OpenID Connect provider, several side by side, with authentik set up in one step and Microsoft Entra ID for several tenants. Roles and quotas stay in Nexview; no group from the provider changes them
- One account owns the installation: no other administrator can switch it off, delete it, demote it or set its password. Handed on by its holder alone
With a media server
Plex is optional. Connect one and four things arrive:
- Sign in with a Plex account, checked against your server — no second password
- Titles already in the library are recognised even if they never came through Radarr or Sonarr, and cannot be requested twice
- Your Plex watchlist appears inside the catalogue, requestable with one click. Nothing happens on its own; every title takes a click
- What each person has already watched carries a marker. Everyone sees their own; an administrator sees who watched what, under Statistics & analysis → Watching
- Live playback across every connected server: who is watching what, on which device, and whether the server is transcoding the video for it — the one that costs CPU
Staying informed
- A bell in the app, and per-event e-mail if you want it
- Web push to your own phone, tablet and laptop, with the same per-event switches as e-mail: encrypted end to end, signed with a key Nexview generates once, arriving even while Nexview is closed. On iPhone and iPad via the Home Screen; needs https
- Seven notification channels for the installation as a whole: ntfy, Gotify, Telegram, Discord, a plain webhook, Apprise and e-mail. Each inbox picks its own events, language and urgency
- A ticket centre where people report problems and get an answer, with state and history
- Statistics and an error log for the administrator
- A Home Assistant integration: everybody enters their own key and gets their own figures and their own notifications, requests can be decided from a phone, and every Radarr, Sonarr and media server turns up as its own device
Running it
- An admin dashboard: twenty background checks across services, disk, supply, library, source reconciliation and Nexview itself. Every finding says what follows from it, and its button lands on the page that fixes it
- Statistics & analysis in six tabs — including where Radarr, the media server and Nexview's own books disagree, which is where the errors nobody looks for live
- A tile for Homepage or Homarr: one call, ready-made snippets
- House rules you write yourself, with images: reachable through a § button and the footer, tickable, and an overview of who accepted, declined or has not decided
And
- German and English throughout, including titles and descriptions
- A dark interface that works on a phone
- Frontend: React 19, Vite, Tailwind CSS 4
- Backend: Python, FastAPI
- Database: SQLite — accounts, settings and requests only, never media files
- Sessions: JWT, passwords hashed with bcrypt
- Deployment: a single Docker container serving both the API and the interface
Nexview runs as one container: FastAPI serves the API and the built interface together. No extra web server is needed.
The image is on the GitHub Container Registry and is built for Intel/AMD and ARM, so nothing has to be compiled:
docker compose up -dNexview is then reachable at http://<your-server>:5173.
To build from source instead, replace image: ghcr.io/derkezorm/nexview:latest in
docker-compose.yml with build: . and add --build when starting.
Everything that matters lives in the directory mapped to /data:
| File | Contents |
|---|---|
nexview.db |
accounts, requests, feedback, settings |
secret.key |
the key your stored API keys are encrypted with |
avatars/ |
profile pictures |
trash/ |
the fetched TRaSH guides your quality profiles were built against |
hausordnung/ |
the images used in the house rules |
logs/ |
error log |
secret.key belongs with the database. Without it the stored TMDB, Radarr, Sonarr
and SMTP credentials cannot be decrypted. Back both up together — and never put that
backup in a public repository.
trash/ matters for a subtler reason: each quality profile records which TRaSH snapshot
it was written against. Restore a database without it and Nexview measures those profiles
against a different snapshot, reporting drift on profiles nobody touched.
hausordnung/ has the same catch on a smaller scale: the database holds only the name
of each image in the house rules, never the file. Restore without the folder and the text
survives with holes in it.
Nexview's own backups (Settings → System → Backups) already contain all of this: database, key, avatars, TRaSH snapshot and house-rules images. The list above is for backing up the directory by hand.
Only the download is encrypted. The copies Nexview keeps for itself, the automatic
one before every schema change and anything you create with Back up now, sit in
sicherungen/ inside the data directory as plain SQLite files. Whoever can read that
directory can read every account out of them, and out of every older state still being
kept. The password you type when downloading protects the archive you take away, not the
copies that stay behind. If that gap matters in your setup, the answer is to protect the
directory, not the file: a backup that Nexview could decrypt on its own would have to keep
the key next to it, which protects nobody.
secret.key is created with mode 0600 and tightened to it on every start, so an older
installation gets there too. That is hardening, not a wall: it helps when the database
travels somewhere without the directory around it.
In Container Manager under Project → Create, pick the project folder and use the
docker-compose.yml. Two adjustments are worth making:
ports:
- "5173:8000" # change the left number if 5173 is taken
volumes:
- /volume1/docker/nexview/data:/dataA fixed path instead of ./data makes backing up through Hyper Backup easier.
With network_mode: host there is no port mapping — the port inside the container is
the port on your server, and if something already sits on 8000, Nexview will not come up.
NEXVIEW_PORT moves it:
network_mode: host
environment:
NEXVIEW_PORT: 8123Everyone else can ignore this. Without the variable Nexview listens on 8000 as before, and
the left number in ports: is what you reach it at.
You do not need to sort out permissions on that folder — the container sets them on
start. If you want the files to belong to a particular user, put their numbers in
PUID/PGID (find them over SSH with id username).
docker compose pull
docker compose up -dThe database is kept. Nexview brings it up to date itself on start: missing tables,
columns and indexes are added. Before anything is changed it writes a copy to
/data/sicherungen/, keeping the five most recent — so if something does go wrong, the
previous state is still there. The latest copy taken before an update is never rotated
out: it is the way back to the version you came from.
Do not simply start the older image on the updated data directory. An older Nexview cannot read what a newer one has written, and it changes data without saying so: it cancels open requests, drops 4K rights and rebuilds storage entries. From 1.0.0 on, Nexview guards against the worst of it:
- An older 1.x refuses to start, before it changes anything.
- A version older than 1.0.0 starts, but cannot write to accounts and their rights, requests and storage entries. Signing in fails, and its log says why.
That guard is deliberately narrow. Everything else can still change under a version older than 1.0.0: favourites, settings, invitations, tickets, the blocklist, children's wishes. A session that was already open keeps working there too. So if a version older than 1.0.0 was started on the updated database, even briefly, always go back with the backup below instead of carrying on with that database.
The way back is the backup taken before the update. It sits in sicherungen/ inside the
data directory and carries the comment Before update to … in its name, for example
nexview-automatisch-0.35.2-or-later-2026-09-26_091050-before-update-to-1-0-0.db. The
version in the name is the one the data came from; before 1.0.0 no version wrote itself
into the database, so for those it is the oldest it can be ("or later"). The start log of
the update names the exact file.
nexview-automatisch-0.35.2-….db without
the comment. It already holds the new data and does not take you back.
Going back works by swapping the file while Nexview is stopped, not through Restore in the settings:
- Stop the container:
docker compose down. - In the data directory, copy that backup over
nexview.db. - Delete
nexview.db-walandnexview.db-shmif they exist. They belong to the newer database and would be replayed onto the old one. - Set the image to the older version (for example
ghcr.io/derkezorm/nexview:0.35.2) and start it:docker compose up -d.
Everything done since the update (new requests, changed settings, new accounts) is not in that backup and is gone on the old version. Updating again later starts from the restored state and runs the update once more.
Nexview follows the usual MAJOR.MINOR.PATCH numbering:
| Image | Contents |
|---|---|
ghcr.io/derkezorm/nexview:latest |
the latest released version — the recommendation |
ghcr.io/derkezorm/nexview:0.20.0 |
exactly that one version, never changes |
ghcr.io/derkezorm/nexview:main |
the current development state, may be broken |
The running version is in the footer and in detail under About Nexview, which also reports when a newer one exists. For that it asks GitHub at most once a day. The check sends nothing out of Nexview and can be switched off on the same page.
Every setting is optional — Nexview runs without a configuration file. For production,
copy .env.example to .env:
| Variable | Meaning |
|---|---|
NEXVIEW_SECRET_KEY |
Secret for sessions and for encrypting the API keys. Generated automatically and stored in data/secret.key if unset. |
NEXVIEW_DATA_DIR |
Directory for the database and key file (default: ./data) |
NEXVIEW_ACCESS_TOKEN_MINUTES |
How long a sign-in stays valid (default: 30) |
NEXVIEW_REFRESH_TOKEN_DAYS |
How long you stay signed in (default: 30) |
NEXVIEW_CORS_ORIGINS |
Allowed origins in development |
NEXVIEW_STATIC_DIR |
Folder with the built frontend (the container sets this itself) |
NEXVIEW_LOG_LEVEL |
Overrides the log level chosen in the app (quiet, normal, detailed, trace). Emergency exit for when Nexview does not start. |
NEXVIEW_PORT |
Port Nexview listens on inside the container (default: 8000). Read by the container's start script, not by the backend itself. Only needed on host networking — see below. |
NEXVIEW_CLIENT_IP |
How Nexview learns the caller's address (for rate limiting sign-ins) and, behind a proxy, the scheme (X-Forwarded-Proto): unset (count per account only), direct, proxy, or proxy:2. See below. |
NEXVIEW_COOKIE_SECURE |
Whether the sign-in cookie is marked Secure: auto (default), on, or off. See below. |
NEXVIEW_URL_BASE |
Serve Nexview under a sub-path behind a reverse proxy, e.g. /nexview for https://example.com/nexview/. Unset means the root, exactly as before. See below. |
NEXVIEW_CSP |
Content security rules: on (default), report-only, or off. See below. |
NEXVIEW_FRAME_ANCESTORS |
Who may put Nexview in a frame: none (default), self, or an origin. See below. |
NEXVIEW_IMG_SOURCES |
Extra image hosts, space separated. Only needed if calendar posters stay blank. See below. |
NEXVIEW_BETREIBER |
Emergency exit: the username of the account that owns this installation. Normally unset — the account from the setup wizard owns it. Only needed if the owner locks themselves out. See below. |
No TMDB, Radarr or Sonarr keys belong in .env — you enter those in the app.
The setup wizard offers a third way on its front page: from a running Seerr. Paste Seerr's address and API key, and Nexview reads the installation through Seerr's API, area by area, and shows what it found. You tick what should come along: Radarr and Sonarr with their keys and folders, the mail server including its password, region and language, the house quota default, the block list, and the notification channels. Nothing is preselected; the key is never stored, and Nexview only reads from Seerr.
Accounts come along ready to use. One of Seerr's accounts becomes the owner, with a password you set; for everyone else you tick who comes and pick a role per row (default: user). Each account arrives with display name, a username shaped from it, its count quota, its personal region and language, its profile picture and its Plex, Jellyfin or Emby identity. Plex people sign in through Plex once the server is connected; everyone else sets a password via "Forgot password" or gets one from you.
Everything is written in one go at the last step, or not at all. The media server is connected afterwards, with the owner's session: Plex through the code at plex.tv, Jellyfin and Emby through address and administrator sign-in. What does not come along, the wizard says on the spot: the request history (what sits in Radarr and Sonarr, Nexview shows anyway), passwords, watch lists, per-person notification addresses and Seerr's override rules. The wizard only runs while the installation is empty.
Exactly one account owns the installation. It carries an Owner badge in Settings → Users, and no other administrator can switch it off, delete it, demote it, change its quotas or set its password. The buttons are greyed out for them, with a sentence explaining why.
The flag grants no extra rights. It only says what others may not do with that account. The owner approves requests, spends quota and manages keys exactly like any other administrator.
Who gets it. On a new installation, the account you create in the setup wizard. On an existing installation being upgraded, the oldest active administrator — usually that same account. If there is no active administrator, the flag stays unassigned and says so in the user list.
Handing it over. Only the current owner can, under Profile → Security → Hand over ownership. The target must be an active administrator. Afterwards the previous owner is an ordinary administrator again and cannot take the flag back — only the new owner can hand it back.
If you lock yourself out — password gone with no mail delivery set up, login
provider dead, account unusable — set NEXVIEW_BETREIBER to your username and
restart the container:
environment:
NEXVIEW_BETREIBER: yourusernameThat account carries the flag again on the next start, with one line in the log. Then remove the line: the variable is read on every start, so while it is set, Nexview refuses to hand ownership over in the app rather than silently undoing it on the next restart. A username that does not exist is ignored with a warning — no account is created from this variable.
After a few wrong passwords Nexview slows the next attempt down, and after ten it closes the door for fifteen minutes. The lock opens again by itself — nobody has to unlock anything. It covers signing in to Nexview, signing in with a media-server password, and the links sent by mail.
Counting always happens per account, which needs no configuration. Counting per
address is extra, and it needs NEXVIEW_CLIENT_IP, because Nexview cannot tell on its
own whether it sits behind a reverse proxy:
| Value | When |
|---|---|
| unset | You are not sure. Only accounts are counted. This is always safe. |
direct |
Nexview is reachable directly, with no proxy in front |
proxy |
Exactly one reverse proxy in front (Nginx, Caddy, Nginx Proxy Manager, Traefik) |
proxy:2 |
Two in front, for example Cloudflare and then your own |
⚠️ Leave it unset if you are unsure. Sayingdirectwhile a proxy sits in front makes every request look like it comes from the same address — one typo would then lock out the whole household, you included.
Your sign-in is kept in an HttpOnly cookie. The browser sends it back on its own, and
no script on the page can read it — which is the point: a script that gets onto the page
can no longer carry your sign-in away with it.
NEXVIEW_COOKIE_SECURE decides whether that cookie is marked Secure, meaning the
browser only ever sends it over HTTPS:
| Value | When |
|---|---|
auto |
Default. Secure is set whenever the request itself arrived over HTTPS. Works everywhere, including plain HTTP. |
on |
Always set it. Use this when a reverse proxy terminates HTTPS and forwards plain HTTP to Nexview and you do not tell Nexview about the proxy with NEXVIEW_CLIENT_IP (see below); Nexview then sees http and would leave it off. |
off |
Never set it. |
⚠️ Do not setonif Nexview is meant to be reachable overhttp://. A browser throws aSecurecookie away over plain HTTP, and nobody would be able to sign in.
X-Forwarded-Proto counts only when NEXVIEW_CLIENT_IP says a proxy is in front
(proxy or proxy:2). The header can be faked just like X-Forwarded-For, so without
that setting Nexview ignores it. With it, Nexview takes the rightmost value, the one
your nearest proxy added, and with auto the cookie is then marked Secure as soon as
that value is https. With proxy:2 the rightmost value is still the nearest proxy's:
it names the scheme the request had when it reached that proxy, not necessarily the one
the browser used. If the outer proxy speaks HTTPS and passes plain HTTP inwards, set
NEXVIEW_COOKIE_SECURE=on.
The same setting governs the short-lived cookie of a sign-in through an external
provider, which holds the state, the nonce and the PKCE verifier for the ten
minutes you spend at the provider. Until 0.26.0 that one never carried Secure at all,
not even with on set.
Nexview can hang off sign-in services you already run — anything that speaks OpenID Connect: authentik, Microsoft Entra ID, Keycloak, Authelia, Pocket ID, Zitadel, Google. Several at once if you like; each gets its own button on the sign-in page. It all lives under Settings → System → Sign-in, in three cards:
- Sign-in — whether people may still sign in with a password, and the public address.
- Sign-in providers — the list, in the order of the buttons (drag to reorder), with Add a provider, Edit and Remove.
- authentik in one step — sets up provider and application in authentik for you.
| Field | What goes there |
|---|---|
| Name on the button | What the button says: “Sign in with Company SSO”. |
| Short name | Part of the redirect address; suggested from the name, fixed once saved. |
| Issuer | The base address of your provider. Nexview reads its /.well-known/openid-configuration itself and checks it before anything is saved; you never enter single endpoints. A slash at the end makes no difference. |
| Client ID, Client secret | Created when you register Nexview at the provider. Leave the secret empty for a public client. A stored secret never leaves the server; an empty field on Edit keeps it. |
| On | Switched off, the button disappears and the provider lets nobody in; links stay. |
| New people get an account | Off by default. Off: only invited people and accounts that linked themselves come in. On: whoever has an account at the provider gets one here, as an ordinary user whose requests need approval. |
| The provider checks the second factor itself | On by default. Nexview has no second factor of its own yet, so the switch changes nothing today. |
| More → Scopes | openid profile email unless you need something else. |
Your provider needs one thing from you — the redirect address, shown in the form ready to copy before you save:
https://your-nexview/api/oidc/<short name>/callback
⚠️ The public address has to be right. The redirect address is built from it, and a provider rejects anything that does not match its registration exactly. If you run Nexview under a sub-path, the address includes it. Changing the public address later asks first when a provider knows the old redirect address.
Providers you set up with Nexview 1.0.1 or earlier keep working without a change. Their
providers know https://your-nexview/api/auth/oidc/<slug>/callback; that address stays
valid for those entries, and the form shows it as theirs.
Nexview recognises a person only by the identity at the provider — issuer plus subject
(sub) — never by the e-mail address, not even when the provider says it is confirmed.
Rename the person at your provider, change their address there: the link holds.
- Existing accounts link themselves, under Profile → Account → Security → Sign-in providers: Link asks for the account's own password once more, then goes to the provider and back. Unlink removes it; an account without a password keeps its last link, or it would be locked out.
- Invited people pick Continue with … on the invitation page instead of choosing a password. The invitation travels with them to the provider and back (never through the address) and is used up exactly once, even when two people return at the same moment. An invitation that also gives media-server access works through the invitation form only, because only that form creates the accounts on the server.
- Everybody else gets in only if New people get an account is on for that provider. Otherwise the sign-in page says that an invitation is needed or that the account can be linked in the profile.
- Administrators see each account's providers as small marks in Settings → Users and can remove a link there, with their own password. Nobody but the operator can touch the operator's own account.
Groups are not read. Your provider vouches for who somebody is; what they may do here — role, quotas, blocked quality profiles, auto-approval — stays in Nexview. An invitation accepted through a provider brings its rights; an account that already exists gets the invitation's rights in addition to its own, never fewer.
Deleting an account blocks its identities, so that New people get an account does not let the same person straight back in. The block list sits under the three cards; lifting a block lets the identity in again.
Once at least one provider is on, Sign-in with a password can be switched off. The sign-in page then shows only the provider buttons, with a small Sign in as the operator with a password underneath: the operator can always sign in with the password — that is the way back if the provider fails. If the last provider goes or is switched off, passwords work again for everyone.
A different issuer is a different provider: the links of that entry go, and the people concerned link again. The form says how many accounts that is before it saves. The same issuer written with or without a slash at the end changes nothing. Removing an entry takes its links with it; the question before names how many accounts lose it and for how many it was the only way in.
Give the card the address of your authentik and an API token that may create
applications (Directory → Tokens and App passwords, an API token of an administrator).
Set up then creates — or updates, when you press it again — the signing key, the scope
mappings, an OAuth2 provider with grant_types: authorization_code, and the application,
and fills in the entry here. Every step is listed with a tick or a cross, and a failed one
says why: authentik not reachable, the token refused, another application already using
the name. The token is used for this run only and never stored.
- A second Nexview against the same authentik gets its own provider (
nexview (<host>)), the first one stays untouched. - Moving Nexview to another address and pressing the button again finds its own provider by the client ID and keeps every link.
- Pointing the button at a different authentik is a different issuer: it asks first, with the number of accounts that would have to link again.
- Download blueprint instead gives you
nexview-authentik.yamlto apply in authentik yourself (Customization → Blueprints). - Without a public address the card suggests the address your browser uses right now; Use it and set up saves it first. Nexview never registers an address it guessed from the request.
Behind a reverse proxy that ends TLS Nexview sees plain
http. If the card warns that the server receives the request under another address, tell Nexview it sits behind a proxy withNEXVIEW_CLIENT_IP=proxy(orproxy:2for two). That is the setting for trusted proxies: with it Nexview takes the scheme fromX-Forwarded-Proto(the rightmost value, the one your nearest proxy added; withproxy:2that is the scheme the request had when it reached that proxy). The sign-in cookie inautomode then follows it and becomesSecure, see The sign-in cookie. Without the setting the header is ignored, as before.
Register an application (Entra admin center → App registrations), add the redirect address
as a Web platform redirect (Entra accepts only https, localhost aside) and create a
client secret. The issuer is https://login.microsoftonline.com/<tenant-id>/v2.0 for your own
tenant, or …/common/v2.0 / …/organizations/v2.0 for several: Nexview then accepts a token
of any tenant whose iss and tid agree. Entra sends the e-mail address only as the optional
claim email.
- Keycloak — Clients → Create client, type OpenID Connect, Client authentication on,
Standard flow; the redirect address under Valid redirect URIs. Issuer:
https://keycloak.example.com/realms/<realm>. - Pocket ID — OIDC Clients → Add client. Issuer: your Pocket ID address.
- Authelia — a client under
identity_providers.oidc.clientswith the redirect address andscopes: [openid, email, profile]; Nexview gets the plaintext secret you hashed for Authelia. Issuer: your Authelia address. Authelia leaves the address out of the ID token; Nexview reads it fromuserinfo, also when that answer is signed. - Zitadel — New application, type Web, authentication method Basic. Issuer: your instance address.
- Google — APIs & Services → Credentials → OAuth client ID, type Web application.
Issuer:
https://accounts.google.com. Google has an account for everybody: leave New people get an account off unless the client is limited to your own workspace.
The ID token: signature with the provider's published keys (never a shared secret), issuer,
audience, azp, expiry, nonce, a non-empty sub. The attempt itself: PKCE, a state that
is used once, a short-lived cookie with a key of its own. userinfo is asked on every
sign-in; its answer counts only when its sub matches the token, and a failure there breaks
nothing.
Look in Settings → System → Log. Every refused sign-in leaves one line with the reason
(OIDC sign-in refused: oidc_no_account (…)); addresses appear shortened, tokens and secrets
never. The person signing in gets a short sentence that tells them what to do, not what the
log knows.
Coming from Nexview 1.0.1: the update moves your providers and links into the new tables by itself (a backup is taken first). Links stay; a person whose old link pointed at a provider you had since removed or changed links again. Nexview no longer links an account by a confirmed e-mail address: whoever signed in that way until now is already linked and keeps working, and anybody new links in the profile or comes with an invitation.
With its own (sub)domain — https://nexview.example.com — Nexview needs no
configuration at all. To serve it under a sub-path of a shared domain instead,
set one variable and restart the container:
NEXVIEW_URL_BASE=/nexview
Now Nexview lives at https://example.com/nexview/. Any prefix works, /tools/nexview
too. Leave the variable unset and everything behaves exactly as before.
With the prefix set, Nexview answers both forms of address: with the prefix (for proxies that pass the path through unchanged — the recommended setup) and without it (for proxies that strip the prefix before forwarding, and for the container's own health check). Whichever way your proxy is configured, it works.
nginx
location /nexview {
proxy_pass http://127.0.0.1:5173;
proxy_set_header Host $host;
}Caddy
handle /nexview* {
reverse_proxy 127.0.0.1:5173
}handle_path (which strips the prefix) works just as well.
Traefik (labels on the Nexview container)
- traefik.http.routers.nexview.rule=PathPrefix(`/nexview`)
- traefik.http.services.nexview.loadbalancer.server.port=8000A stripprefix middleware works just as well.
Nginx Proxy Manager
On your proxy host, add a Custom Location /nexview and forward it to the host and
port Nexview listens on. No advanced configuration needed.
⚠️ Set the public address with the prefix included. The address under Settings → Addresses goes into every link Nexview sends — invitations, password resets. With a prefix it must readhttps://example.com/nexview. Nexview suggests it correctly and shows a warning when the prefix is missing, but it cannot stop you from saving without it.
Nexview tells your browser where it may load things from, and where it may send things to. Anything not on the list is refused. The one that matters most is that no script on the page can send data to any address other than Nexview's own — which is exactly the gap the sign-in cookie leaves open: the cookie stops a stolen pass being carried away, not being used.
You should not need to touch any of this. Three switches exist for the cases where you do:
| Variable | Values | When |
|---|---|---|
NEXVIEW_CSP |
on (default), report-only, off |
report-only reports breaches in the browser console without blocking anything — useful to look before you commit. off turns the header off entirely. |
NEXVIEW_FRAME_ANCESTORS |
none (default), self, or an origin |
Set this if you embed Nexview in a dashboard such as Organizr. Without it the frame stays empty, with no error message. |
NEXVIEW_IMG_SOURCES |
space separated origins | Set this if calendar posters stay blank. |
⚠️ Why posters can stay blank. Calendar posters are not built by Nexview — it passes on whatever address Radarr or Sonarr stored, and that depends on your metadata provider. The usual ones (TMDB, TheTVDB, fanart.tv) are allowed already. If yours is something else, the posters simply do not appear; the browser only says so in its console.NEXVIEW_IMG_SOURCES=https://your.hostfixes it.
Nexview has one HTTP interface, and the web interface uses it too. Anyone who can reach
your instance can call it — the endpoints themselves are protected, but the map is open:
the browsable documentation lives at /docs, the machine-readable version at
/openapi.json.
Scripts, dashboards and anything else that talks to Nexview without a browser use a personal API token. You create one in Profile → Account → Security, and it is shown exactly once — afterwards only a checksum remains, and not even an administrator can look it up.
curl -H "Authorization: Bearer nxv_…" https://your-nexview/api/v1/requests/mineA token has exactly the rights of the account it belongs to — no more. Role, quota, approval and blocklist apply to it just as they apply to its owner, so a token belonging to an ordinary account still goes through approval instead of around it. If you want a service account with few rights, create a user with those rights and give it a token.
One switch on top: may only read. Such a token can fetch data but not create, change or delete anything — right for dashboards and monitoring.
Tokens can carry an expiry date, the list shows when each was last used, and revoking one takes effect immediately. Child accounts do not get tokens.
Administrators see every token in the installation under Settings → System → API tokens — owner, name, age and last use. They can look, not revoke: only the owner switches off their own token. Deactivating an account locks its tokens along with it.
Sixteen endpoints live under /api/v1, and for those there is a promise: as long as
v1 is in the address, nothing disappears from their answers. If something has to break,
/api/v2 will appear beside it and v1 will keep running.
GET /api/v1/search/{media_type} |
find a title |
GET /api/v1/media/{media_type}/{tmdb_id} |
details for one |
POST /api/v1/requests |
request it |
GET /api/v1/requests/mine |
how your own requests are doing |
GET /api/v1/requests/quota |
how much you may still request |
POST /api/v1/requests/{request_id}/cancel |
withdraw one |
GET /api/v1/home/recent |
what was recently downloaded |
GET /api/v1/tickets/open-count |
open tickets |
GET /api/v1/admin/requests/pending/count |
requests waiting for approval |
GET /api/v1/notifications/unread/count |
unread notifications |
GET /api/v1/storage/me |
your own storage use |
GET /api/v1/about |
which version is running |
GET /api/v1/health |
whether it is up |
GET /api/v1/me |
who is asking, and what this request may do |
GET/PUT/POST/DELETE /api/v1/me/push |
where Nexview should notify this key's owner |
GET /api/v1/dashboard |
one tile for your home dashboard |
Everything else under /api/… is an inside part of the application. You may use it,
but it can change with any version — field names included. Nothing there is promised.
GET /api/v1/home/recent also carries requested_by and requester_avatar. Putting
that on a dashboard shows who asked for what. In a household that is usually the point;
on a screen other people see, it may not be.
GET /api/v1/dashboard answers with everything a tile needs in one call: how many findings
are open, what is waiting, how full the library is, and whether the instances are answering.
Ready-made snippets for both are in docs/dashboard-tile.md.
GET, but an administrator's read-only token can still read the user list, the
log and the settings — worth knowing before you pin it to a screen somebody else can see.
Requirements: Python 3.12+ and Node.js 20+
cd backend
python -m venv .venv
.venv/Scripts/activate # Linux/macOS: source .venv/bin/activate
pip install -r requirements-dev.txt
python -m uvicorn app.main:app --reload --port 8000The API then runs on http://127.0.0.1:8000, with interactive documentation at
http://127.0.0.1:8000/docs.
In a second terminal:
cd frontend
npm install
npm run devThe interface opens at http://localhost:5173. Requests to /api are forwarded to the
backend automatically.
A one-time wizard appears on first launch:
| Step | Required | For |
|---|---|---|
| Account | yes | administrator with username, e-mail address and password |
| Picture | no | profile picture — without one, Nexview shows initials |
| TMDB | no | titles, posters, descriptions. Without a key you get sample data |
| Radarr | no | movies. Without Radarr, movies cannot be requested |
| Sonarr | no | shows. Without Sonarr, no show requests |
| nexcrate | no | movies and shows in one, instead of Radarr and Sonarr |
| Address | yes | the address Nexview is reachable at; it goes into every link |
| yes | SMTP server for invitations and password recovery |
The last two cannot be skipped, and Continue only unlocks after a successful connection test: every invitation and every password reset goes out through them.
The address of the account you create here counts as confirmed right away, and the owner can always sign in, even with an address that is not confirmed yet (after changing it without a mail server, say). Every other account, further administrators included, cannot sign in with its password until its address is confirmed. The confirmation mail says so.
If the confirmation never arrives, the sign-in page helps: with the right password it offers Resend confirmation and Correct address, so a typo is not fatal. The link needs a working mail server.
Further accounts come from invitations — or through the media server. The administrator sets only the address and the role; the invitee picks their own username, display name and password through the link in the mail. Nobody else learns that password, not even the administrator.
One check deliberately runs before the push rather than in CI: it searches every versioned file for personal data, meaning names, account identifiers, tokens, e-mail addresses with a real domain, public IP addresses and home directory paths. CI would find them too, but only once they are already in the history, and getting them out of there means rewriting it. Activate the hook once per clone:
git config core.hooksPath .githooksTo run the same check by hand:
cd backend
python tools/personendaten_pruefen.pyThe backend suite — the big one, and the one that has to be green before a release:
cd backend
.venv/Scripts/python.exe -m pytestThe interface is tested on two levels. The fast ones run without a browser against a replaced API layer, in seconds:
cd frontend
npm testAnd one runs in a real Chromium, against a real server: it signs in, reloads, and signs out again. That is the only way to prove that the browser really keeps the sign-in across a reload — the session lives in a cookie that JavaScript cannot read, so nothing below a real browser can tell you. It starts backend and interface itself, on their own ports and on an empty database:
cd frontend
npx playwright install chromium # once
npm run e2eAll three run in CI on every push and every pull request.
To discard all accounts and settings, delete the data/ directory. The next start runs
the wizard again.
Metadata comes from TMDB. This product uses the TMDB API but is not endorsed or certified by TMDB.
Sign-in, library matching and the watched state run through Plex, when a server is connected. Nexview is neither endorsed by nor affiliated with Plex.
Downloads are handled by Radarr and Sonarr, or by nexcrate; ratings come from IMDb, Rotten Tomatoes and Metacritic, streaming availability from JustWatch. Notifications can go through ntfy, Gotify, Telegram, Discord, a plain webhook or Apprise.
Quality profiles, custom formats and the file naming scheme are built from the TRaSH Guides (MIT). Nexview ships a snapshot of their JSON data so the first setup works without internet access, and fetches newer versions from their repository when you ask it to. The scoring itself — which release is worth more than which, and why — is their work.
Seerr, which grew out of Overseerr and Jellyseerr, was the model: the idea of what a request interface for Radarr and Sonarr can look like comes from there, from the second instance for 4K down to matching against the media server. Nexview is an independent implementation and takes no source code from it, but owes it a great deal.
Nexview is under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later) — see LICENSE. The source may be used, modified and passed on, commercially too. What the AGPL asks in return: if you change Nexview and let other people use your changed version — including over a network, without handing out any files — you have to make your changed source available to them too. Running Nexview unchanged asks nothing of you. There is no warranty.
Versions up to and including 0.17.0 were released under the MIT licence. That grant is perpetual and is not being withdrawn — anyone who received those versions keeps them under MIT. From 0.18.0 on, the AGPL applies.
The licence covers this project's source. It does not cover:
- Metadata, posters and images from TMDB. Those fall under the TMDB terms of use; running Nexview needs your own API key.
- Plex, Radarr, Sonarr, Docker, Synology, IMDb, Rotten Tomatoes, Metacritic, JustWatch, ntfy, Gotify, Telegram, Discord and Apprise. Trademarks of their respective owners, named here descriptively only.
- The dependencies in
frontend/package.jsonandbackend/requirements.txt, which carry their own licences.