apps.header.allAppsCount
AccessFlow
apps.detail.types.app from Pantanet96's Repository
apps.detail.sections.overview
Readme
View on GitHubAccessFlow
Self-hosted portal to manage users, subscriptions, and invites for a personal Plex server.
FastAPI + Jinja2/HTMX + SQLite, shipped as a single Docker container that sits behind your reverse proxy.
Screenshots
| Dashboard | Users |
|---|---|
![]() |
![]() |
| Collect | Reports |
|---|---|
![]() |
![]() |
Rame (copper) theme by default; Inchiostro and Muschio variants also available from Settings:

Contents
- Features
- How it works
- Deploy
- Security
- Operational scripts
- Local development
- Configuration
- i18n
- Background workers
Features
- Role-based access — SuperAdmin, Admin, Moderator, User, each scoped to only what they need.
- Multi-manager support — every user has a manager who collects their payments; multiple people can run their own client base under the same server.
- Flexible plans — free/trial plans out of the box, custom paid plans created on demand.
- Two-step renewals — a renewal stays pending until the payment is actually collected, then extends the expiration.
- Automatic reminders — expiration notices via email and Telegram, deduplicated, configurable schedule.
- Financial reports — revenue collected, pending, and projected, straight from recorded payments.
- Telegram bot — users link their account for reminders; admins can broadcast.
- 3 color themes — Rame, Inchiostro, Muschio — switchable per-user from Settings.
- Audit log, soft-delete, nightly backups — out of the box.
- i18n — English source strings, translatable via
.pocatalogs.
How it works
Manages the lifecycle of a Plex server's users: who has access, what plan they're on, when it expires, who collects the payment. The idea is to stop having to track "who owes me and when" by hand.
Roles
- SuperAdmin — full access, local login (username/password). Configures the system.
- Admin — manages users, plans, reports, settings.
- Moderator — manages only the users assigned to them (their "clients") and collects their payments. Cannot touch free plans or global settings.
- User — sees only their own subscription.
Every regular user has a manager (an Admin or Moderator): the person who brought them in and collects their payments. This way multiple people can manage their own users under the same server, each one seeing only their own.
Plans
By default only two special plans exist:
- Family & Friends — free, never expires.
- Trial — timed trial period (max 30 days), not renewable.
Paid plans (with custom price and duration) are created by the SuperAdmin as needed from the Plans page — there are no predefined ones.
Typical workflows
1. Adding a new paying user
- The admin/moderator invites the person on Plex from the portal (sends the Plex invite).
- The person accepts and logs in with their own Plex account (PIN flow, no password to manage).
- They're assigned a paid plan → the subscription starts and the initial payment is recorded right away (it goes into the reports as revenue).
- It's also possible to pay several months in advance: you set the number of periods and the expiration is calculated accordingly.
2. Renewal (two-step)
- At expiration the manager creates a renewal → it stays pending until collected.
- When the client pays, the manager marks it as paid, indicating the payment method (e.g. "PayPal", "cash"). Only then is the expiration extended and the revenue counted.
- Multiple periods can be renewed at once.
3. Expiration reminders (automatic)
- A daily job checks who's about to expire and sends reminders via email and Telegram (default: 7/3/1 days before, and 0/3 days after for overdue follow-ups).
- Reminders are deduplicated (they don't repeat on the same day).
- The manager receives a "to be collected" notice.
4. Reports
- User count per plan + revenue summary: previous month / collected this month / to be collected this month / next month's projection.
- Every euro in the reports comes from a recorded payment (initial setup or a paid renewal).
5. Telegram
- Users link their own Telegram account to the portal to receive reminders.
- Admin can send manual broadcasts.
Other automations: audit log of every action, soft-delete with orphan protection, nightly backup of the SQLite database.
Deploy
The image is published on Docker Hub (public repo): pantanet96/accessflow.
cp .env.example .env # fill in the secrets, never commit the real .env
docker compose up -d
App at http://localhost:8000, behind your reverse proxy (NPM / Traefik / Caddy).
Health check: GET /healthz.
docker-compose.yml:
services:
app:
image: pantanet96/accessflow:latest
env_file: .env
ports:
- "8000:8000"
volumes:
- appdata:/data
restart: unless-stopped
volumes:
appdata:
Upgrading: docker compose pull && docker compose up -d.
The image honors
X-Forwarded-Proto(--proxy-headers), so behind an HTTPS proxy URLs come out ashttps.
Unraid
Ships a Community Applications template → docs/UNRAID.md.
Security
APP_SECRET_KEYis mandatory (random, ≥32 chars) — the app won't start without one.- SuperAdmin password auto-generates on first boot if left blank; change it from
/profile. - Set
FORWARDED_ALLOW_IPSto your reverse proxy's subnet — never*on a directly exposed app. - Runs as an unprivileged container user; secrets are encrypted at rest.
Full details → docs/SECURITY.md.
Operational scripts
In scripts/. They must be run inside the container, and since they aren't packages
you need to put /app on the path:
backfill_setup_payments.py
Records the initial setup payment for paid subscriptions that were created before the portal started recording it automatically (otherwise that first revenue would be missing from the reports). Idempotent: skips subscriptions that already have setup recorded.
# dry-run: shows what it would do, without writing
PYTHONPATH=/app python scripts/backfill_setup_payments.py
# apply for real
PYTHONPATH=/app python scripts/backfill_setup_payments.py --apply
Local development
python -m venv .venv
. .venv/Scripts/activate # Windows: .venv\Scripts\activate
pip install -r requirements-dev.txt # prod deps + pytest (prod uses requirements.txt)
export DATABASE_PATH=./data/app.db # avoids the container's /data path
uvicorn app.main:app --reload
pytest
Configuration
Everything via environment / .env — see .env.example.
i18n
Source strings are in English. Translations live in app/translations/<locale>/LC_MESSAGES/messages.po.
After changing templates/strings:
pybabel extract -F babel.cfg -o messages.pot .
pybabel update -i messages.pot -d app/translations
# edit the .po files, then:
pybabel compile -d app/translations
The Docker build compiles the catalogs automatically.
Background workers
The web container also runs, in-process: a daily APScheduler job (expiration scan
at the NOTIFY_HOUR hour, nightly DB backup) and the Telegram bot in polling mode.
They're toggled with ENABLE_SCHEDULER / ENABLE_BOT.
apps.marketingCta.appInstallTitle
apps.marketingCta.appInstallDescription
apps.detail.sections.categories
apps.downloadStats.title
apps.detail.sections.related
apps.detail.related.exploreCategories
apps.detail.related.exploreAllapps.detail.sections.links
apps.detail.sections.details
pantanet96/accessflowapps.detail.sections.runtime
- apps.detail.details.webui
http://[IP]:[PORT:8000]/- apps.detail.details.network
bridge- apps.detail.details.shell
sh- apps.detail.details.privileged
- false
apps.detail.sections.configuration
Container Port: 8000
- apps.detail.config.target
- 8000
- apps.detail.config.default
- 8000
- apps.detail.config.value
- 8000
SQLite DB + backups + secrets. Container Path: /data
- apps.detail.config.target
- /data
- apps.detail.config.default
- /mnt/user/appdata/accessflow
- apps.detail.config.value
- /mnt/user/appdata/accessflow
REQUIRED. Random string >=32 chars, the app refuses to start without it. Generate: python -c "import secrets;print(secrets.token_urlsafe(48))"
- apps.detail.config.target
- APP_SECRET_KEY
Your public https:// URL (through the reverse proxy). Needed for Secure session cookies.
- apps.detail.config.target
- PUBLIC_BASE_URL
- apps.detail.config.default
- http://localhost:8000
- apps.detail.config.value
- http://localhost:8000
Reverse proxy source IP/subnet uvicorn trusts for X-Forwarded-*. Never "*" if exposed directly. Unraid docker bridge is typically 172.17.0.0/16.
- apps.detail.config.target
- FORWARDED_ALLOW_IPS
- apps.detail.config.default
- 127.0.0.1
- apps.detail.config.value
- 127.0.0.1
Timezone for schedules/reminders/backups.
- apps.detail.config.target
- TZ
- apps.detail.config.default
- Europe/Rome
- apps.detail.config.value
- Europe/Rome
UI language (it/en).
- apps.detail.config.target
- DEFAULT_LOCALE
- apps.detail.config.default
- it
- apps.detail.config.value
- it
Local login username, seeded on first boot.
- apps.detail.config.target
- SUPERADMIN_USERNAME
- apps.detail.config.default
- admin
- apps.detail.config.value
- admin
Leave blank: a random password is generated and logged once at first boot (change it later from /profile).
- apps.detail.config.target
- SUPERADMIN_PASSWORD
Admin Plex token, used for invites + OAuth. https://support.plex.tv/articles/204059436
- apps.detail.config.target
- PLEX_TOKEN
Name of the Plex server to manage.
- apps.detail.config.target
- PLEX_SERVER_NAME
Optional: connect straight to this Plex baseurl (plex.direct dashed-host form), skipping plex.tv discovery. Leave blank for normal discovery.
- apps.detail.config.target
- PLEX_DIRECT_URL
Optional: stable Plex client identifier. Derived from the secret key if left blank.
- apps.detail.config.target
- PLEX_CLIENT_ID
Revoke the user's Plex server access when their AccessFlow account is deleted.
- apps.detail.config.target
- PLEX_REVOKE_ON_DELETE
- apps.detail.config.default
- false
- apps.detail.config.value
- false
Optional: enables the Telegram reminder bot. From @BotFather.
- apps.detail.config.target
- TELEGRAM_BOT_TOKEN
Bot username (without @), used for deep links.
- apps.detail.config.target
- TELEGRAM_BOT_USERNAME
Optional: enables email reminders.
- apps.detail.config.target
- SMTP_HOST
- apps.detail.config.target
- SMTP_PORT
- apps.detail.config.default
- 587
- apps.detail.config.value
- 587
- apps.detail.config.target
- SMTP_USER
- apps.detail.config.target
- SMTP_PASS
From address for reminder emails.
- apps.detail.config.target
- SMTP_FROM
Display name in the From header.
- apps.detail.config.target
- SMTP_FROM_NAME
- apps.detail.config.target
- SMTP_TLS
- apps.detail.config.default
- true
- apps.detail.config.value
- true
Hour (0-23, server TZ) the daily expiration scan + reminders run.
- apps.detail.config.target
- NOTIFY_HOUR
- apps.detail.config.default
- 9
- apps.detail.config.value
- 9
Comma-separated days-before-expiry to send reminders. Admin-editable in Settings too.
- apps.detail.config.target
- REMINDER_DAYS_BEFORE
- apps.detail.config.default
- 7,3,1
- apps.detail.config.value
- 7,3,1
Comma-separated overdue days for dunning follow-ups. Admin-editable in Settings too.
- apps.detail.config.target
- REMINDER_DAYS_AFTER
- apps.detail.config.default
- 0,3
- apps.detail.config.value
- 0,3
Manager weekly-digest lookahead window in days. Admin-editable in Settings too.
- apps.detail.config.target
- DIGEST_LOOKAHEAD_DAYS
- apps.detail.config.default
- 14
- apps.detail.config.value
- 14
How many nightly SQLite backups to keep.
- apps.detail.config.target
- BACKUP_KEEP
- apps.detail.config.default
- 14
- apps.detail.config.value
- 14
Run the daily reminder/backup scheduler in-process.
- apps.detail.config.target
- ENABLE_SCHEDULER
- apps.detail.config.default
- true
- apps.detail.config.value
- true
Run the Telegram bot (polling) in-process.
- apps.detail.config.target
- ENABLE_BOT
- apps.detail.config.default
- true
- apps.detail.config.value
- true



