Time-based parental controls for Plex Media Server. Guardarr enforces per-user content-rating and label filters on a schedule, so the right restrictions apply automatically at the right times — and lift when they should.
- Per-user restriction scheduling — rules activate automatically by time window (overnight windows like
20:00 → 06:00supported) - Multiple rules per user — weekdays, weekends, custom day/time combinations, ordered by priority
- Rule presets — one-click Little Kids / Tweens / Teens starting points you can tweak
- Bulk apply — assign or remove a rule across all managed users at once
- Auto-default rule — automatically apply a chosen rule to new Plex users as they're discovered
- Timezone-correct schedules — windows are evaluated in your configured timezone, not the server's or container's clock
- Rating filters — allow or block specific movie & TV ratings per rule
- Block unrated content — toggle to also exclude NR / "Not Rated" titles
- Label filters — use Plex labels as include/exclude restriction criteria
- Parent PIN — require a PIN to grant temporary bypasses
- Per-library access editor — view and change which Plex libraries each shared user can access, PIN-gated, with a confirmation step before revoking all access
- Plex OAuth — sign in with your Plex account
- Self-healing enforcement — every cycle reconciles against the live filters on plex.tv, so restarts, restored backups and out-of-band edits correct themselves
- Durable applied-state — what's currently applied is recorded in the database, not process memory, so a restart can never strand a user in a restriction
- Bypass-aware — an active temporary bypass suppresses re-application and survives restarts; protection returns automatically the moment it expires
- Enforcer health — dashboard banner shows last run / last success / consecutive failures / token validity
- Token validation — proactively checks the Plex admin token and surfaces failures instead of silently doing nothing
- Daily database backups — online SQLite backup + WAL checkpoint, last 7 retained
- Activity retention — the activity log is pruned to a configurable window (default 90 days) during the nightly pass
- Versioned schema migrations — ordered, recorded migrations shared by the app and the enforcer, replacing ad-hoc
ALTER TABLEattempts - Activity insights — 7-day rollup of restriction changes, with an optional weekly digest to a webhook
- Failure alerts — optional webhook notification on repeated enforcement failures
- Authenticated API — middleware gates all API routes and protected pages behind NextAuth
- Chip-based dark UI — shadcn/ui + Tailwind, mobile-responsive, customizable accent colors
- 12-hour times — rule windows and activity timestamps render as am/pm in your configured timezone
| Dashboard | Rules |
|---|---|
![]() |
![]() |
- Next.js 14 + React 18 + TypeScript
- shadcn/ui + Tailwind CSS
- better-sqlite3 (local SQLite, WAL mode)
- NextAuth (Plex OAuth, JWT sessions)
- Standalone enforcer process + Next.js server sharing one enforcement core (
lib/enforcement.js) node:testsuite, run in CI on every push and pull request- Docker
cd guardarr
cp .env.example .env.local
# Edit: PLEX_SERVER_URL, PLEX_ADMIN_TOKEN, NEXTAUTH_SECRET, NEXTAUTH_URL
docker-compose up -d --buildAccess at: http://localhost:4600
Required environment variables:
PLEX_SERVER_URL— your Plex server URL (e.g.,http://192.168.x.x:32400)PLEX_ADMIN_TOKEN— your Plex admin token (fallback; the token is normally stored in the DB and refreshed on each Plex sign-in)NEXTAUTH_SECRET— random string for auth/session encryptionNEXTAUTH_URL— your domain (e.g.,https://guardarr.yourdomain.com)
Optional:
TIMEZONE— IANA timezone used to evaluate rule schedules, e.g.America/Los_Angeles(fallback; normally set in Settings and stored in the DB). Defaults toAmerica/Los_Angeles.ACTIVITY_RETENTION_DAYS— how long to keep activity log entries;0disables pruning (default90)ALERT_WEBHOOK_URL— webhook for enforcement-failure alerts and the weekly digest (also configurable in Settings)
In-app settings:
- Timezone — the timezone rule windows are interpreted in; set this to your household's timezone
- Plex admin token — re-paste after a rotation; every Plex sign-in persists the current token to the DB
- Parent PIN — set/clear the bypass PIN (stored hashed)
- Default rule — choose the rule auto-applied to new users
- Notifications webhook — powers failure alerts and the weekly digest
Normally you don't need to find this manually — signing in with Plex stores the token automatically. If you need it directly:
- Sign in to the Plex Web app
- Open browser DevTools → Network
- Look for any request to
plex.tv - Find the
X-Plex-Tokenheader value
See: https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
A standalone enforcer process runs every minute alongside the web app. Each cycle it:
- Reads the current day/time in the configured timezone (
settings.timezone, falling back to theTIMEZONEenv var, defaultAmerica/Los_Angeles) usingIntl— never the container's clock, so a container running in UTC still enforces a14:00–19:00rule at 2pm–7pm local. - Picks the winning rule per user. Plex stores a single filter per user, so when
several rules match, the highest
prioritywins (ties broken by rule id). - Fetches every managed user's live filters from plex.tv and compares them with what the winning rule wants.
- Writes only on real divergence — a
PUThappens when a rule starts, ends, or is edited, or when the live state has drifted from what Guardarr applied. Steady state makes no writes at all.
What is currently applied is recorded in the applied_restrictions table, not in
process memory, and each cycle is reconciled against plex.tv. This means a restart,
a crash or a restored backup cannot strand a user inside a restriction that never
lifts — the next cycle notices the mismatch and corrects it. Filters that Guardarr
did not set, and that match none of the user's rules, are deliberately left alone
rather than clobbered.
An unexpired temporary bypass outranks every rule: restrictions are cleared and re-application is suppressed for as long as it lasts, across restarts. When it expires, protection is restored automatically on the next cycle.
The loop self-schedules with capped exponential backoff (60s → 5 min) on failure, validates the Plex admin token periodically, records its health to the database, and can alert a webhook after repeated failures. If plex.tv is unreachable, it falls back to the state recorded in the database rather than guessing.
npm install
npm test # node:test suite — no database or network required
npm run buildEnforcement logic lives in one place and is shared by every caller:
lib/enforcement.js— filter construction, schedule evaluation, andplanUserAction(), the pure function that decides what should happen to a user this cycle. No database, no network, no clock reads except what is passed in, which is what makes it directly testable.lib/plex-api.js— all plex.tv access and XML parsing (fast-xml-parser).lib/migrations.js— ordered schema migrations, applied once and recorded inschema_version.
The standalone enforcer (enforcer.js) is an I/O shell around that core: read
state, ask for a decision, perform it, record the result. The Next.js API routes
import the same modules, so a bypass being cancelled applies byte-identical filters
to what the enforcer would apply.
Enforcement runs only in the standalone enforcer process. API routes report on it; they never start their own loop.
MIT

