Skip to content

Repository files navigation

NoPayNoPlay — No pay, no play

💳 NoPayNoPlay

Jellyfin plugin for tracking manually-validated monthly subscriptions.

No pay, no play — without ever deleting the account.

Stars Downloads CI Release Jellyfin ElegantFin License: MIT PRs welcome

NoPayNoPlay helps you split your Jellyfin server costs with your family, friends or housemates. Each account gets a free trial, then needs to pay once a month — otherwise playback is paused until the payment is recorded (the account is never deleted). You validate payments manually (PayPal, Lydia…), users can click "I just paid", and promo codes grant free months.


📋 Table of Contents


✨ What is it for?

You self-host Jellyfin for your family / friends / housemates and you'd like them to chip in for the bills — without plugging in Stripe, opening a company and spending Friday nights chasing payments on WhatsApp.

NoPayNoPlay automates the tedious parts:

What it does How
📅 Tracks subscriptions Automatically assigns a free trial on first sign-in and tracks expiry dates
🔔 Reminds users Sends Jellyfin bell notifications at J-3, J-1, J0, and when grace expires
🚫 Blocks playback Disables playback on expiry — without deleting the account
Manual payment validation You check PayPal/Lydia, then click "Record payment" in the admin panel
🙋 Self-service Users click "I just paid" — admins get a pending badge to confirm or reject
🎁 Promo / referral codes Grant free months via shareable codes with configurable limits and expiry
🆓 Free trial Configurable trial days (default: 7) on every new account
🛡️ Exemption Manual exemption for family, VIPs, or admins (admins are always exempt)

No external database. No outbound calls. No Stripe. No subscription to manage.

Everything lives in Jellyfin's standard XML configuration — simple, transparent, reversible.


📦 Installation

Prerequisites

Requirement Details
Jellyfin Version 10.11.x or compatible
File Transformation (recommended) Required for the user UI (banner, modal, header button) — install from GitHub

Method 1 — from the plugin catalog (recommended)

  1. In Jellyfin, go to Dashboard → Plugins → Repositories → ➕

  2. Add this repository:

    Field Value
    Repository Name NoPayNoPlay
    Repository URL https://raw.githubusercontent.com/alexisometric/nopaynoplay/main/manifest.json
  3. Go to Catalog → NoPayNoPlay → Install

  4. Restart Jellyfin

  5. (Recommended) Install the File Transformation plugin — it powers the user UI (💳 button, banner, payment modal)

Method 2 — manual ZIP

  1. Download the latest nopaynoplay_*.zip from the releases page
  2. Extract the folder into Jellyfin's plugins/ directory
  3. Restart Jellyfin

💡 Why File Transformation? NoPayNoPlay uses it to inject the user-facing UI (💳 button, banner, payment modal) into the Jellyfin web app. The plugin still works without it — enforcement, notifications and the admin dashboard all run server-side — but your users won't see the payment interface.

📖 Full installation guide (all methods + troubleshooting) → docs/INSTALL.md


🚀 Quick start

After installing and restarting Jellyfin:

  1. Go to Dashboard → Plugins → NoPayNoPlay
  2. Set your monthly price and currency
  3. Add your PayPal.me and/or Lydia links
  4. Configure grace days, trial days, and warning window to your preference
  5. Click Save
  6. (Optional) Go to the Tiers tab to create subscription packages
  7. (Optional) Go to the Tags tab to create member groups with custom pricing

⚠️ The user UI (💳 button, banner, modal) requires the File Transformation plugin. Without it, only the server-side features (enforcement, notifications, admin dashboard) work.

📖 Full configuration reference → docs/CONFIGURATION.md


� Screenshots

Live captures from a real Jellyfin server (v10.11.11) — each user was signed in with their own account.

👤 User experience

The subscription banner is injected into each member's home screen; the 💳 header button opens the payment modal.

⏰ Expiring soon ⏳ Grace period 🚫 Blocked
Tom Harris — expiring soon Lucas Moore — grace period Lea Wilson — playback blocked

The payment modal — subscription status, tier plans (with the highlighted best deal), payment reference, promo code and payment history:

⏰ Expiring soon ⏳ Grace period 🚫 Blocked 🎉 Free access ✅ Up to date
Tom — modal Lucas — modal Lea — modal Hugo — free access Mary — up to date

🛠️ Admin dashboard

Members — 12-month revenue chart, attention cards and the member table:

Members dashboard

Configuration tabs:

⚙️ Settings 🎁 Promo codes 🗂️ Tiers
Settings Promo codes Tiers
🏷️ Tags 📈 Activity 🕵️ Audit log
Tags Activity Audit log
🧪 Test mode 🔬 Diagnostics
Test mode Diagnostics

�🚀 Features

👤 For admins

Feature Description
Members dashboard Members shown as cards on every screen (responsive grid: 4 columns on desktop → 1 on phones) with the same one-click actions
Enriched dashboard Attention cards (blocked, grace, expiring ≤7d, pending claims, active), expiring-soon list, payment-method breakdown, revenue stats + 12-month SVG chart
Search, filters, sort Search by name, filter by state / payment method, and a dedicated sort control (field + ascending/descending)
Record payments One-click payment recording with transaction history (edit/delete)
Pending claims Badge when a user says "I just paid" — confirm or reject in one click
Bulk actions Select multiple users to pay, exempt, reset, or notify in batch
Promo codes Create codes with configurable months, max uses, and expiry
Subscription tiers Define packages (1/3/6/12 months) with automatic savings display
Member tags Group members with per-tag price overrides
Activity log Transaction history with date range filters and CSV export
Audit log Last 500 admin actions recorded with timestamp and details
Notifications Automated bell notifications at configurable milestones
Exemption Mark users as exempt (never blocked)
Auto-backup Configuration backed up automatically on every save (retention: 10)
Skeleton loading Animated shimmer placeholders while tables load
Live theme preview Settings page shows live swatches of detected colours
ElegantFin support Automatic adaptation (accent, radius, blur, glassmorphism)
Theme test selector Preview user UI with Jellyfin or ElegantFin theme via URL param

👤 For users

Feature Description
💳 Header button Opens the subscription modal from anywhere in Jellyfin
Subscription banner Sticky banner on warning, grace, and blocked states with a live countdown
Hero card Visual status with a live countdown (days·hours·min·sec) and progress gauge
Tier picker Choose a plan with per-month savings shown
Payment links Clickable PayPal.me / Lydia with pre-filled amount + "Copy payment link" button on every card
"I just paid" Notify the admin (rate-limited to once per 30 min)
Promo redemption Enter a code directly in the modal
Payment history Full transaction log with "Show all" expand
Notifications Bell notifications at J-3, J-1, J0, and grace expired
QR codes QR payment links on tablets & desktop (vendored generator, no CDN) — hidden on phones
Hash deeplink #!/npnp opens the modal directly
Test mode Preview any state with ?npnpTest=STATE

ℹ️ Administrators are always automatically exempt from enforcement.


🧠 How it works

flowchart LR
    A[User signs in] -->|Authentication event| B(SubscriptionService)
    B -->|New account| C[Free trial]
    B -->|Existing account| D[Check state]

    E["Scheduled task\nevery 12h"] --> F{Evaluate states}
    F -->|Warning window| G[Bell notification]
    F -->|Grace expired| H[UserPolicyEnforcer]
    H --> I[Save policy snapshot]
    H --> J[Disable playback]
    H --> K[Stop active sessions]

    L[Admin records payment] --> B
    M[User says I just paid] --> N[Pending claim]
    N -->|Admin confirms| B
    O[User redeems code] --> B

    B --> P[(Jellyfin XML config)]
    P -->|Auto-backup| Q[config/NoPayNoPlay.backups/]
Loading

Subscription lifecycle

First sign-in → Free trial (default: 7 days)
    ↓
Ok ──(warning window)──→ WarningSoon ──(expired)──→ InGrace ──(grace over)──→ Blocked
    │                                                                              │
    └────────────────── Payment received ──────────────────────────────────────────┘

Key design principles

  • No external database — everything is in Jellyfin's XML configuration
  • Reversible — policy snapshots are saved before blocking, restored as-is on unblock
  • Anti-spam — notifications are deduped per milestone, at most one per cycle
  • No outbound calls — all assets are served by the plugin itself
  • Thread-safe — all config mutations go through a static lock

📖 Full architecture documentation → docs/ARCHITECTURE.md


🙋 User guide

If you're a user of this plugin (not an admin), here's what you need to know:

Topic Summary
Header button 💳 Click to open your subscription modal
Banner Sticky bar appears when your subscription is about to expire or has expired
Paying Click PayPal/Lydia links in the modal, send the money, then click "I just paid"
Promo codes Enter a code in the modal to get free months
Notifications You'll get Jellyfin bell reminders at J-3, J-1, J0
Blocked Playback stops but your account stays — pay to restore immediately
Deeplink #!/npnp opens the modal from any page

📖 Full user guide → docs/USER_GUIDE.md


🔌 REST API

All routes under /NoPayNoPlay/.

Method URL Auth Description
GET /NoPayNoPlay/Me user Current user state, payment info, translations
POST /NoPayNoPlay/Me/MarkPaid user Declare a payment (30 min rate limit)
POST /NoPayNoPlay/Me/RedeemCode user Redeem a promo code
GET /NoPayNoPlay/Strings public Translation bundle
GET /NoPayNoPlay/Users admin Subscription list
POST /NoPayNoPlay/Users/{id}/Pay admin Record a payment
POST /NoPayNoPlay/Users/{id}/ConfirmPending admin Confirm pending claim
POST /NoPayNoPlay/Users/{id}/RejectPending admin Reject pending claim
POST /NoPayNoPlay/Users/{id}/Exempt admin Toggle exemption
POST /NoPayNoPlay/Users/{id}/Reset admin Reset to fresh trial
GET /NoPayNoPlay/Users/Export.csv admin Members CSV export
POST /NoPayNoPlay/Users/BulkPay admin Bulk record payment
GET /NoPayNoPlay/Activity admin Payment activity log
GET /NoPayNoPlay/Activity/Export.csv admin Activity CSV export
GET /NoPayNoPlay/Stats admin Revenue statistics
GET /NoPayNoPlay/Settings admin Global settings
POST /NoPayNoPlay/Settings admin Update settings
GET /NoPayNoPlay/PromoCodes admin List promo codes
POST /NoPayNoPlay/PromoCodes admin Create promo code
DELETE /NoPayNoPlay/PromoCodes/{id} admin Delete promo code
GET /NoPayNoPlay/Status admin Plugin runtime status
GET /NoPayNoPlay/Health public Health probe
GET /NoPayNoPlay/Diagnostics admin FT registration diagnostics
POST /NoPayNoPlay/Diagnostics/Retry admin Retry FT registration

📖 Full API reference with request/response schemas → docs/API.md


🌍 Localization

The plugin ships with 8 languages:

🇬🇧 English · 🇫🇷 French · 🇪🇸 Spanish · 🇩🇪 German · 🇮🇹 Italian · 🇵🇹 Portuguese · 🇷🇺 Russian · 🇨🇳 Chinese

The active language is resolved from (in order):

  1. Admin override (UiCultureOverride setting)
  2. ?lang= query parameter
  3. Accept-Language HTTP header
  4. Jellyfin server UI culture
  5. English fallback

Adding a new language: just drop a strings.{code}.json in src/Localization/ and add an EmbeddedResource entry in the .csproj.


🛠️ Development

# Clone & restore
git clone https://github.com/alexisometric/nopaynoplay.git
cd nopaynoplay
dotnet restore

# Build
dotnet build src/Jellyfin.Plugin.NoPayNoPlay.csproj -c Release

# Test
dotnet test tests/Jellyfin.Plugin.NoPayNoPlay.Tests.csproj

# Package
./scripts/build.sh 1.4.0.0

📖 Full development guide → docs/DEVELOPMENT.md


📚 Documentation

File Description
docs/INSTALL.md Step-by-step installation guide (all methods, troubleshooting)
docs/CONFIGURATION.md Full admin configuration reference
docs/USER_GUIDE.md What users see and how to use it
docs/API.md Complete REST API reference with schemas
docs/ARCHITECTURE.md Architecture overview, state machine, design decisions
docs/DEVELOPMENT.md Build, test, package, and contribute

❓ FAQ

Does the plugin delete accounts?

No. Never. On expiry, only the playback permissions (EnableMediaPlayback, transcoding…) are set to false. The original policy is snapshotted and restored when the user is unblocked.

What happens if I uninstall the plugin while a user is blocked?

Their UserPolicy stays in the modified state. Make sure to unblock everyone before uninstalling (Reset or Exempt button).

What if the server is offline for several days?

The scheduled task runs every 12 h; on next startup it catches up. No data is lost.

Can I change the price without breaking active subscriptions?

Yes. The price is read at the time a payment is recorded. Already-computed expiries do not move.

Is it compatible with Jellyfin 10.10?

No, the API has changed. This release targets Jellyfin 10.11.x (targetAbi 10.11.11.0).


📜 Data storage

Data Location
Plugin configuration <jellyfin-data>/plugins/configurations/f3b4d2c1-7e9a-4b1e-9c6d-9a1b2c3d4e5f.xml
Backups <jellyfin-data>/plugins/configurations/NoPayNoPlay.backups/
Logs standard Jellyfin logs

No outbound calls. No telemetry. Everything stays on your server.


🛡️ Security

If you find a vulnerability, please do not open a public issue. See SECURITY.md for the responsible disclosure procedure.


💖 Support the project

If NoPayNoPlay saves you time or helps you manage your server, consider supporting its development:

The best way to help is to ⭐ star the repo — it takes one click and makes a huge difference in visibility!


🤝 Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines.

  • Bug report → open a GitHub issue
  • Feature request → open a GitHub issue
  • Pull request → fork, branch, commit, PR against main

📄 License

MIT © alexisometric


If this plugin saves you time, drop a star on the repo — it really helps!


About

Stop chasing friends for the Jellyfin bill. NoPayNoPlay tracks monthly subscriptions, sends smart reminders, and blocks playback on expiry — without ever deleting an account. Self-hosted · Zero external calls · Free forever

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages