Jellyfin plugin for tracking manually-validated monthly subscriptions.
No pay, no play — without ever deleting the account.
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.
- What is it for?
- Installation
- Quick start
- Screenshots
- Features
- How it works
- User guide
- REST API
- Localization
- Development
- Documentation
- FAQ
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.
| Requirement | Details |
|---|---|
| Jellyfin | Version 10.11.x or compatible |
| File Transformation (recommended) | Required for the user UI (banner, modal, header button) — install from GitHub |
-
In Jellyfin, go to Dashboard → Plugins → Repositories → ➕
-
Add this repository:
Field Value Repository Name NoPayNoPlayRepository URL https://raw.githubusercontent.com/alexisometric/nopaynoplay/main/manifest.json -
Go to Catalog → NoPayNoPlay → Install
-
Restart Jellyfin
-
(Recommended) Install the File Transformation plugin — it powers the user UI (💳 button, banner, payment modal)
- Download the latest
nopaynoplay_*.zipfrom the releases page - Extract the folder into Jellyfin's
plugins/directory - 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
After installing and restarting Jellyfin:
- Go to Dashboard → Plugins → NoPayNoPlay
- Set your monthly price and currency
- Add your PayPal.me and/or Lydia links
- Configure grace days, trial days, and warning window to your preference
- Click Save
- (Optional) Go to the Tiers tab to create subscription packages
- (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
Live captures from a real Jellyfin server (v10.11.11) — each user was signed in with their own account.
The subscription banner is injected into each member's home screen; the 💳 header button opens the payment modal.
| ⏰ Expiring soon | ⏳ Grace period | 🚫 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 |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
Members — 12-month revenue chart, attention cards and the member table:
Configuration tabs:
| ⚙️ Settings | 🎁 Promo codes | 🗂️ Tiers |
|---|---|---|
![]() |
![]() |
![]() |
| 🏷️ Tags | 📈 Activity | 🕵️ Audit log |
|---|---|---|
![]() |
![]() |
![]() |
| 🧪 Test mode | 🔬 Diagnostics |
|---|---|
![]() |
![]() |
| 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 |
| 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.
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/]
First sign-in → Free trial (default: 7 days)
↓
Ok ──(warning window)──→ WarningSoon ──(expired)──→ InGrace ──(grace over)──→ Blocked
│ │
└────────────────── Payment received ──────────────────────────────────────────┘
- 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
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
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
The plugin ships with 8 languages:
🇬🇧 English · 🇫🇷 French · 🇪🇸 Spanish · 🇩🇪 German · 🇮🇹 Italian · 🇵🇹 Portuguese · 🇷🇺 Russian · 🇨🇳 Chinese
The active language is resolved from (in order):
- Admin override (
UiCultureOverridesetting) ?lang=query parameterAccept-LanguageHTTP header- Jellyfin server UI culture
- English fallback
Adding a new language: just drop a
strings.{code}.jsoninsrc/Localization/and add anEmbeddedResourceentry in the.csproj.
# 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
| 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 |
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 | 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.
If you find a vulnerability, please do not open a public issue. See SECURITY.md for the responsible disclosure procedure.
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!
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
MIT © alexisometric
















