Importing users
The one-click, email-matched, idempotent Import Plex Users action — and what it pointedly does not do.
Rather than making every household member register by hand, an admin can pull in everyone the Plex server is already shared with. This is an explicit, Overseerr-style action (a button) — not an automatic background sync.

Where it lives
| Layer | File |
|---|---|
| UI — the "Import Plex Users" button | apps/web/src/routes/_auth/users/index.tsx (calls trpc.plex.importUsers) |
Router — importUsers (an adminProcedure) | packages/api/src/routers/plex.ts |
Service — importPlexUsers(prisma, source) | packages/api/src/services/plex/import-users.ts |
Plex fetch — getSharedUsers(...) | packages/api/src/services/plex/client.ts |
The router picks the first Plex MediaSource (by createdAt); if there is none it throws
PRECONDITION_FAILED with "Connect a Plex server first." Connect and sync a
source before importing.
How it works
- The chosen source must carry both a
clientIdentifierand amachineIdentifier; otherwise the service throws "…missing its identifiers — reconnect it." getSharedUserscalls the classichttps://plex.tv/api/usersXML endpoint (the same one Overseerr and Tautulli use) with the decrypted owner token. Each<User>carries nested<Server>entries; the client keeps only users whose server list includes this server'smachineIdentifier. Each kept result is\{ plexId, email, username, thumb \}.- For each shared user:
- Skip anyone with no email — email is the identity key.
- Skip anyone whose email already matches an existing
User. - Otherwise create a
Userwith a fresh UUID, the Plex email, the Plex username asname,emailVerified: true, androle: "user"(Viewer).
- It returns
\{ imported, skipped, total \}, surfaced by the UI as a toast.
Idempotent by email
Skipping any email that already exists is what makes the import safe to re-run: it never duplicates accounts and never touches owner-created or env-seeded admins. Re-run it whenever you share the Plex library with someone new — only the genuinely new emails become accounts.
What import does not do
- It stores no Plex token for the imported user and does not log them in. It only makes an account exist, so that later "Sign in with Plex" (login-only) can match it by email. Playback for every viewer brokers the admin's connection — a viewer never needs a token of their own.
- It sets no explicit access. New users are created with
allAccess = true(the schema default — see Access model), so a freshly imported viewer sees the whole lineup until an admin narrows them on the Access page.
Source map
| Concern | File |
|---|---|
| Import service (idempotent create) | packages/api/src/services/plex/import-users.ts |
| Plex shared-users fetch | packages/api/src/services/plex/client.ts (getSharedUsers) |
| Import router | packages/api/src/routers/plex.ts (importUsers) |
| Import button | apps/web/src/routes/_auth/users/index.tsx |
User model + allAccess default | packages/db/prisma/schema/auth.prisma |
