Skip to content

feat: per-user parental controls (content rating limits)#2415

Open
ProgenyAlpha wants to merge 1 commit intoseerr-team:developfrom
ProgenyAlpha:feature/parental-controls
Open

feat: per-user parental controls (content rating limits)#2415
ProgenyAlpha wants to merge 1 commit intoseerr-team:developfrom
ProgenyAlpha:feature/parental-controls

Conversation

@ProgenyAlpha
Copy link

@ProgenyAlpha ProgenyAlpha commented Feb 14, 2026

Description

Adds admin-enforced per-user content rating limits (parental controls). Admins can set maximum movie (MPAA) and TV (US Parental Guidelines) ratings per user, plus a "block unrated" toggle for fail-closed filtering. Restricted users see no indication that filtering exists.

Screenshot

Parental Controls Settings

Admin view: User > Settings > Parental Controls

How It Works

Admin Sets Limits

A new Parental Controls tab appears in user settings (admin-only — restricted users cannot see or modify their own limits). Admins choose:

  • Max Movie Rating: G, PG, PG-13, R, or NC-17 (MPAA)
  • Max TV Rating: TV-Y, TV-Y7, TV-G, TV-PG, TV-14, or TV-MA (US Parental Guidelines)
  • Block Unrated: When enabled, content without a US certification is hidden

Limits are stored as new columns on UserSettings (maxMovieRating, maxTvRating, blockUnrated) with TypeORM migrations for both PostgreSQL and SQLite.

Discover Filtering (Two-Layer)

  1. Pre-filter: TMDB's native certification.lte / certification_country query params remove rated content above the limit at the API level — no extra requests needed
  2. Post-filter: For blockUnrated users, results that slipped through (unrated content TMDB doesn't filter) are caught server-side by fetching each item's US certification and checking against the hierarchy

Search Filtering

Search results don't support TMDB's certification params, so all filtering is server-side. Certifications are fetched in parallel via Promise.allSettled for each result, then filtered against the user's limits.

Backfill

When post-filtering drops a page below 15 results, the next TMDB page is automatically fetched and filtered to prevent sparse/empty pages. This applies to both discover and search routes.

Fail-Closed Design

  • If a certification lookup fails → result is blocked (not leaked)
  • Unknown ratings not in the hierarchy → treated as unrated
  • No rating at all → blocked when blockUnrated is true, allowed when false

Rating Hierarchies

Single source of truth in server/constants/contentRatings.ts:

  • Movies: G < PG < PG-13 < R < NC-17
  • TV: TV-Y < TV-Y7 < TV-G < TV-PG < TV-14 < TV-MA

Scoped to US ratings — TMDB's certification data is most complete for the US market.

Files Changed

File Purpose
server/constants/contentRatings.ts Rating hierarchies, filter functions, types, UI dropdown options
server/entity/UserSettings.ts New columns: maxMovieRating, maxTvRating, blockUnrated
server/migration/ PostgreSQL + SQLite migrations for new columns
server/routes/discover.ts Pre-filter via certificationLte, post-filter for unrated, backfill
server/routes/search.ts Parallel cert lookup, filtering, backfill
server/routes/user/usersettings.ts GET/POST endpoints for parental controls
src/.../UserParentalControlsSettings/ Admin-only settings UI (dropdowns + toggle)
src/i18n/locale/en.json 15 new translation keys

Testing

  • Built and deployed to production Seerr instance (Docker, SQLite)
  • Verified admin can set rating limits per user
  • Verified restricted user sees filtered discover/search results
  • Verified blockUnrated hides content without US certification
  • Verified backfill fills sparse pages from next TMDB page
  • Verified unrestricted users see no change in behavior
  • pnpm build passes clean (both build:next and build:server)

AI Disclosure

This PR was developed with Claude Code (Claude Opus 4.6), with human review, testing, and architectural direction at every step. All code was verified against existing codebase patterns and deployed before submission.

Checklist

  • I have read and followed the contribution guidelines
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly
  • All new and existing tests passed
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

@ProgenyAlpha ProgenyAlpha requested a review from a team as a code owner February 14, 2026 06:30
@ProgenyAlpha ProgenyAlpha force-pushed the feature/parental-controls branch 2 times, most recently from 9cbb6f4 to d4bda48 Compare February 14, 2026 06:45
Admin-enforced content rating limits per user:
- Max movie rating (MPAA: G through NC-17)
- Max TV rating (US Parental Guidelines: TV-Y through TV-MA)
- Block unrated content toggle (fail-closed)

Filtering applied to all discover routes and search with parallel
TMDB certification lookups. Backfills from next page when filtering
drops results below 15.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@ProgenyAlpha ProgenyAlpha force-pushed the feature/parental-controls branch from d4bda48 to f8ee51d Compare February 14, 2026 06:53
@fallenbagel
Copy link
Collaborator

Isnt this the same as
#2275

@ProgenyAlpha
Copy link
Author

ProgenyAlpha commented Feb 14, 2026

Isnt this the same as #2275

I wasn't aware of #2275 because it wasn't linked to any of the issues, so it didn't come up when I scoped out existing work before starting. But probably wouldn't have matter as I technically started this back in December 2025 and wanted to use it for an extensive period of time before submitting it.

That said, the implementations are architecturally different. #2275 makes live TMDB API calls per-item at request time to fetch certifications, which adds latency to every discovery and search page and scales poorly with TMDB rate limits. This PR uses a static rating hierarchy; filtering is instant with zero external API overhead.

A few other things this PR brings to the table:

  • Explicit blockUnrated toggle instead of overloading NR into the rating dropdown
  • Dedicated parental controls settings page for cleaner UX separation
  • OpenAPI spec updates for the new endpoints

Happy to collaborate or consolidate, the static rating approach could drop right into #2275 as a performance improvement if the maintainers prefer that path.

@mcfalld
Copy link

mcfalld commented Feb 17, 2026

Nice work — this PR looks cleaner and will perform better than mine, especially with the backfill. I do have a few suggestions and one gap to call out; I'd love to see a hybrid that combines your architecture with a couple of features from my branch.

High-level takeaways
• Your static hierarchies + TMDB pre‑filtering are the right move for performance.
• I think the rating fallback from #2275 is still worth keeping; it handles some edge cases better.
• If we can merge those two approaches and add bulk edit support, this would be a very complete parental‑control solution.

What I'd bring from my PR

Enhanced rating fallback (applies when blockUnrated=true)
• Exclude "NR" when choosing the most restrictive US rating so unrated director's cuts don't override theatrical ratings.
• Collect all US ratings instead of stopping at the first match.
• Stronger international fallback: filter to a known hierarchy and pick the most restrictive available.

Detailed logging
• Log blocked items with the reason for the block to make production debugging and audits much easier. (Skip this if logging verbosity is a concern.)

Bulk edit for parental controls
• Added UI in the existing BulkEditModal so admins can set maxMovieRating / maxTvRating for multiple users at once; backend endpoint included.

Architecture note
• Your static hierarchies + TMDB pre‑filtering are superior for performance. My suggestion is to keep that, and fold in the certification-fetching logic and bulk edit capability.

Missing coverage
• Certification filtering currently runs only on the main /movies and /tv routes. The specialized endpoints below aren't passing certification parameters to TMDB, so users can bypass parental controls entirely by browsing those pages:

/movies/studio/:studioId
/tv/network/:networkId
/movies/genre/:genreId
/tv/genre/:genreId
/movies/language/:language
/tv/language/:language
/movies/upcoming
/tv/upcoming
/trending
We should extend those routes to include certification filtering so parental controls behave consistently across the app. (Full transparency: my implementation covers some 5 of these 9 routes—I'm missing the genre and language ones, which I'll add before finalizing.)

I'm excited about this — I can't really let friends and family use the app until something like this is in place. If you want, I can open a follow-up PR that merges the rating fallback and bulk edit into this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adjustable Age Restrictions for viewing content per user [Feature Request] Limit Visible Requests By Jellyfin Parental Control Ratings

3 participants