Scrape today's top 10 from FlixPatrol and sync them to Trakt.tv lists.
Supports 72+ streaming platforms, 200+ countries, and is compatible with Kometa.
Getting Started • Configuration • Supported Platforms • Scheduling • Daemon Mode • Troubleshooting • Development
Warning Running at your own risk of being IP banned from FlixPatrol.
Due to FlixPatrol limitations, titles are matched on Trakt by name and release year. This may occasionally cause bad matching.
- Sync Top 10 lists from 72 streaming platforms (Netflix, Disney+, HBO Max, Amazon Prime, etc.)
- Sync Top 10 Kids lists from Netflix (country-specific)
- Sync Popular lists from 2 sources (Wikipedia and Youtube)
- Sync Netflix Most Watched annual rankings
- Sync Netflix Most Hours rankings (total, first week, first month)
- Support for 200+ countries/regions
- Intelligent caching to reduce API calls (7-day TTL by default)
- Automatic Trakt list management (create, update, sync)
- Dry-run mode for safe testing
- Compatible with Kometa (formerly Plex Meta Manager)
docker run --rm -v "/path/to/config:/app/config" ghcr.io/navino16/flixpatrol-top10-on-trakt:latestEdit ./config/default.json, then schedule periodic runs with cron.
- Download the latest release for your platform
- Make the binary executable and run it:
chmod +x flixpatrol-top10-linux-x64 ./flixpatrol-top10-linux-x64
- Edit
./config/default.jsonand run again
- Download the latest release for Windows
- Run the binary from the command line (double-clicking will close the window automatically)
- Edit
./config/default.jsonand run again
| Name | Description | Values | Default |
|---|---|---|---|
| LOG_LEVEL | How verbose the log will be | error, warn, info, debug, silly | info |
| DRY_RUN | Run without making changes to Trakt | true, false | false |
| LIST_NAME_PREFIX | String prepended to every list name (useful for dev/testing) | Any string, e.g. [TEST] |
(none) |
Run the tool without modifying Trakt lists. Useful for testing your configuration:
# Linux/macOS
DRY_RUN=true ./flixpatrol-top10-linux-x64
# Docker
docker run --rm -e DRY_RUN=true -v "/path/to/config:/app/config" ghcr.io/navino16/flixpatrol-top10-on-trakt:latestIn dry-run mode:
- FlixPatrol scraping runs normally
- Trakt search for ID conversion runs normally
- OAuth authentication runs normally
- List creation, item addition/removal, and updates are logged but not executed
Prepend a fixed string to every list name. Useful when running the tool against your real Trakt account during development or testing — the prefixed lists stay separate from your real lists and can be deleted in bulk afterwards.
# Linux/macOS — produces lists like "[TEST]netflix-world-top10-without-fallback"
LIST_NAME_PREFIX='[TEST]' ./flixpatrol-top10-linux-x64
# Docker
docker run --rm -e LIST_NAME_PREFIX='[TEST]' -v "/path/to/config:/app/config" ghcr.io/navino16/flixpatrol-top10-on-trakt:latestThe prefix is applied verbatim, after the normalization step (so brackets, spaces, and special characters in the prefix are preserved as-is). When active, a warning is emitted at startup so you don't forget it is set.
Send a notification at the start of a run, at the end (with a summary), and on terminal errors. The block is optional — omit it entirely to disable notifications.
{
"Notifications": {
"run_start": [
{ "type": "webhook", "url": "https://discord.com/api/webhooks/..." }
],
"run_end": [
{ "type": "webhook", "url": "https://discord.com/api/webhooks/..." },
{ "type": "apprise", "url": "http://apprise:8000", "key": "flixpatrol" }
],
"error": [
{ "type": "gotify", "url": "https://gotify.example.com", "token": "AbCdEf123" },
{ "type": "ntfy", "url": "https://ntfy.sh", "topic": "flixpatrol-alerts" }
]
}
}Each event takes a list of destinations. A destination has a type and the fields required by that type:
| Type | Fields | Notes |
|---|---|---|
webhook |
url |
Sends generic JSON. If url matches discord.com/api/webhooks/... a Discord-shaped payload is sent automatically. |
gotify |
url, token |
POSTs to {url}/message with the token sent via the X-Gotify-Key header (never in the query string, so it cannot leak into reverse-proxy access logs). |
ntfy |
url, topic |
POSTs JSON to {url} with the topic in the body. Use https://ntfy.sh for the public service. |
apprise |
url, key |
POSTs to {url}/notify/{key} against an Apprise API sidecar (see below). |
Notifications are best-effort: a failing destination is logged at warn level but never blocks the main sync. Each adapter has a 5-second HTTP timeout and the manager caps total wait at 6 seconds. Failure logs include the adapter name and the destination's host (e.g. WebhookAdapter[discord.com]: HTTP 401) — never the full URL or any path / query secrets, so webhook tokens (Discord bearer tokens in the path, Apprise routing keys, etc.) cannot leak into log files or container stdout.
Every url field is validated as a full URL with scheme at config-load time — typos like discord.com/... (missing https://) are rejected at startup with a clear error rather than silently failing at runtime.
DRY_RUN=true does not suppress notifications (useful for testing the setup). Title and body are both prefixed with [DRY-RUN] so they cannot be mistaken for a real run, and the run_end body says items "would be added" rather than "added".
The apprise destination talks to an Apprise API instance you host yourself. Once it is running, you configure your downstream services (Discord, Telegram, Email, etc.) inside Apprise — flixpatrol-top10 only needs to know the Apprise URL and a config key.
# docker-compose.yml excerpt
services:
flixpatrol:
image: ghcr.io/navino16/flixpatrol-top10-on-trakt:latest
volumes:
- ./config:/app/config
depends_on:
- apprise
apprise:
image: caronc/apprise:latest
ports:
- "8000:8000"
volumes:
- ./apprise-config:/configIn the Apprise web UI (default http://localhost:8000), create a configuration key (for example flixpatrol) and add your downstream Apprise URLs (discord://..., tgram://..., mailto://..., etc.) under that key. Then reference it from config/default.json:
{ "type": "apprise", "url": "http://apprise:8000", "key": "flixpatrol" }The configuration file is stored at ./config/default.json (auto-generated on first run).
If there is any configuration error, the tool will exit with information about the error.
FlixPatrolTop10 — Top 10 list configuration
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| platform | Which platform to get from Flixpatrol | Yes | Any Flixpatrol platform (see this) | |
| location | Which location to get from Flixpatrol | Yes | Any Flixpatrol location (see this) | |
| fallback | Fallback to another location if no results? | Yes | False or any Flixpatrol location (see this) | false |
| privacy | The privacy of the generated Trakt list | Yes | private, link, friends, public | private |
| limit | How many movie/show to get | Yes | Number >= 1 | 10 |
| type | Movies, shows or both? | Yes | movies, shows, both | both |
| name | Optional custom list name | No | Any valid string | A generated name based on the top10 config |
| normalizeName | Normalize the list name to kebab-case? | No | true, false | true |
| kids | Get Kids Top 10 (Netflix only, requires specific country) | No | true, false | false |
FlixPatrolPopular — Popular list configuration
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| platform | Which popular source to get from Flixpatrol | Yes | Any Flixpatrol popular platform (see this) | |
| privacy | The privacy of the generated Trakt list | Yes | private, link, friends, public | private |
| limit | How many movie/show to get | Yes | Number between 1 and 100 | 100 |
| type | Movies, shows or both? | Yes | movies, shows, both | both |
| name | Optional custom list name | No | Any valid string | A generated name based on the popular config |
| normalizeName | Normalize the list name to kebab-case? | No | true, false | true |
FlixPatrolMostWatched — Netflix Most Watched configuration
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| enabled | Enable this most watched list? | Yes | true, false | true |
| privacy | The privacy of the generated Trakt list | Yes | private, link, friends, public | private |
| type | Movies, shows or both? | Yes | movies, shows, both | both |
| limit | How many movie/show to get | Yes | Number between 1 and 50 | 50 |
| year | Year of the most watched list | Yes | Number between 2023 and current year | current year |
| name | Optional custom list name | No | Any valid string | most-watched |
| normalizeName | Normalize the list name to kebab-case? | No | true, false | true |
| premiere | Filter by premiere year | No | Year between 1980 and current year | All |
| country | Filter by release country | No | Any Flixpatrol location (see this) | All |
| original | Netflix originals only? | No | true, false | false |
| orderByViews | Order by views instead of hours? | No | true, false | false |
FlixPatrolMostHours — Netflix Most Hours configuration
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| enabled | Enable this most hours list? | Yes | true, false | true |
| privacy | The privacy of the generated Trakt list | Yes | private, link, friends, public | private |
| type | Movies, shows or both? | Yes | movies, shows, both | both |
| limit | How many movie/show to get | Yes | Number between 1 and 100 | 50 |
| period | Which ranking period | Yes | total, first-week, first-month | total |
| language | Filter by language (first-week and first-month only) | No | all, english, non-english | all |
| name | Optional custom list name | No | Any valid string | netflix-most-hours-{period} |
| normalizeName | Normalize the list name to kebab-case? | No | true, false | true |
Trakt & Cache — Authentication and caching
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| Trakt.saveFile | Where to save the Trakt session file | Yes | Any valid path | ./config/.trakt |
| Trakt.clientId | Your clientId from Trakt (get one here) | Yes | A valid string | |
| Trakt.clientSecret | Your clientSecret from Trakt (get one here) | Yes | A valid string | |
| Cache.enabled | Enable caching? (recommended) | Yes | true, false | true |
| Cache.savePath | Where to save the cache files | Yes | Any valid path | ./config/.cache |
| Cache.ttl | Cache validity duration in seconds | Yes | Number > 0 | 604800 (7 days) |
Schedule — Daemon mode / built-in scheduling configuration
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| enabled | Enable the built-in scheduler (daemon mode)? | No | true, false | false |
| crons | Cron expression(s) the app runs on, 5-field format (min hour day month weekday) |
Yes, when enabled: true |
Array of valid cron strings, e.g. ["0 6 * * *"] |
[] |
| runOnStart | Also run immediately at startup, in addition to the schedule? | No | true, false | false |
The whole Schedule block is optional — omit it entirely (or leave enabled: false) to keep the classic one-shot behaviour: the app runs once and exits, exactly like today. See Daemon Mode below for details.
FlareSolverr — Cloudflare challenge bypass (optional)
FlixPatrol is behind a Cloudflare managed challenge that returns HTTP 403 to non-browser clients. FlareSolverr solves that challenge and proxies the request.
This block is entirely optional. If it is absent, or enabled is false, the
tool behaves exactly as before and never contacts FlareSolverr.
| Name | Description | Mandatory | Values | Default |
|---|---|---|---|---|
| enabled | Route FlixPatrol requests through FlareSolverr | No | true, false | false |
| url | FlareSolverr v1 API endpoint | If enabled | Any valid URL | |
| maxTimeout | Challenge solving timeout in milliseconds | No | Number | 60000 |
When enabled, a browser session is created once at the start of each run and destroyed at the end. The first request solves the challenge (around 12s); later requests reuse the session and take 1-3s each.
Run FlareSolverr alongside the tool:
# docker-compose.yml
services:
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
# Published on loopback only: FlareSolverr has no authentication and must not
# be reachable from the network.
ports:
- "127.0.0.1:8191:8191"
environment:
- LOG_LEVEL=info
restart: unless-stoppedIf the tool itself runs in Docker on the same Compose network, use the service name
instead of localhost: "url": "http://flaresolverr:8191/v1".
Example configuration
{
"FlixPatrolTop10": [
{
"platform": "netflix",
"location": "world",
"fallback": false,
"privacy": "private",
"limit": 10,
"name": "Netflix Top 10 Movies",
"type": "movies"
},
{
"platform": "disney",
"location": "world",
"fallback": false,
"privacy": "private",
"limit": 10,
"name": "Disney Plus Top 10 Shows",
"type": "shows"
},
{
"platform": "amazon-prime",
"location": "world",
"fallback": false,
"privacy": "private",
"limit": 10,
"name": "Amazon Prime Top 10",
"type": "both"
},
{
"platform": "netflix",
"location": "united-states",
"fallback": false,
"privacy": "private",
"limit": 10,
"name": "Netflix Top 10 Kids",
"type": "both",
"kids": true
}
],
"FlixPatrolPopular": [
{
"platform": "wikipedia",
"privacy": "private",
"limit": 100,
"type": "both"
}
],
"FlixPatrolMostWatched": [
{
"enabled": true,
"privacy": "public",
"year": 2023,
"limit": 50,
"type": "both"
}
],
"FlixPatrolMostHours": [
{
"enabled": true,
"privacy": "public",
"limit": 50,
"type": "both",
"period": "total"
},
{
"enabled": true,
"privacy": "public",
"limit": 50,
"type": "both",
"period": "first-week"
},
{
"enabled": true,
"privacy": "public",
"limit": 50,
"type": "movies",
"period": "first-month",
"language": "english"
}
],
"Trakt": {
"saveFile": "./config/.trakt",
"clientId": "You need to replace this client ID",
"clientSecret": "You need to replace this client secret"
},
"Cache": {
"enabled": true,
"savePath": "./config/.cache",
"ttl": 604800
},
"Notifications": {
"run_start": [],
"run_end": [],
"error": []
},
"Schedule": {
"enabled": false,
"crons": ["0 6 * * *"],
"runOnStart": false
},
"FlareSolverr": {
"enabled": false,
"url": "http://localhost:8191/v1",
"maxTimeout": 60000
}
}The Notifications block is fully optional — leave the arrays empty (or omit the block entirely) to disable notifications. See Notifications above for the supported destination types and a worked example.
The Schedule block is fully optional and disabled by default — omit it (or leave enabled: false) to keep the classic one-shot behaviour. See Daemon Mode below.
The FlareSolverr block is fully optional and disabled by default — omit it (or leave enabled: false) to keep the classic behaviour of talking directly to FlixPatrol.
To run this application you need a Trakt account and a Client ID / Client Secret.
Warning Trakt free accounts are limited to 5 lists. If you configure more, the tool will fail. Remove some or upgrade to Trakt VIP.
- Create an account or login
- Create a new application with:
- Redirect uri:
urn:ietf:wg:oauth:2.0:oob - Other fields are optional
- Redirect uri:
- Set the Client ID / Client Secret in
./config/default.json - Run the app and follow the on-screen instructions
9now, abema, amazon, amazon-channels, amazon-prime, amc-plus, antenna-tv, apple-tv, bbc, canal, catchplay, cda, chili, claro-video, coupang-play, crunchyroll, discovery-plus, disney, francetv, friday, globoplay, go3, google, hami-video, hayu, hbo-max, hrti, hulu, hulu-nippon, itunes, jiocinema, jiohotstar, joyn, lemino, m6plus, mgm-plus, myvideo, neon-tv, netflix, now, oneplay, osn, paramount-plus, peacock, player, pluto-tv, raiplay, rakuten-tv, rtl-plus, sbs, shahid, skyshowtime, stan, starz, streamz, telasa, tf1, tod, trueid, tubi, tv-2-norge, u-next, viaplay, videoland, vidio, viki, viu, vix, voyo, vudu, watchit, wavve, wow, zee5
wikipedia, youtube
world, united-states, france, united-kingdom, germany, canada, australia, japan, and 191 more countries...
For the complete list, see the source code: Config.types.ts
# Run daily at 6 AM
0 6 * * * /path/to/flixpatrol-top10-linux-x64
# Run every 12 hours
0 */12 * * * /path/to/flixpatrol-top10-linux-x64# Run daily at 6 AM
0 6 * * * docker run --rm -v "/path/to/config:/app/config" ghcr.io/navino16/flixpatrol-top10-on-trakt:latest- Open Task Scheduler
- Create a new task
- Set the trigger (e.g., daily at 6 AM)
- Set the action to run the executable
Instead of relying on an external scheduler (cron, Task Scheduler, docker run on a timer), the app can run as a long-lived
process with its own built-in scheduler. Add a Schedule block to config/default.json and set enabled: true:
{
"Schedule": {
"enabled": true,
"crons": ["0 6 * * *"],
"runOnStart": false
}
}| Field | Description |
|---|---|
enabled |
Turns daemon mode on. When false or omitted, the app runs once and exits — the current behaviour is completely unchanged. |
crons |
One or more cron expressions, standard 5-field format (min hour day month weekday), e.g. "0 6 * * *" for daily at 6 AM. Must contain at least one entry when enabled is true. |
runOnStart |
When true, triggers an immediate run at startup, then continues to follow the configured schedule. |
The Schedule block is entirely optional. If it is absent, or enabled is false, the app behaves exactly as before:
it runs once and exits with the appropriate code. Your existing external cron job or docker run on a timer keeps
working without any change.
The scheduler follows the system clock. There is no timezone field in the config — set the TZ environment variable
(e.g. TZ=Europe/Paris) on the host or container so cron expressions are evaluated in the timezone you expect.
- If a scheduled trigger fires while a run is still in progress, it is skipped (logged as a warning) rather than queued or run concurrently.
- A failed run is logged and sent through the Notifications system (the
errorevent), but it does not stop the daemon — the scheduler keeps waiting for the next trigger. - On
SIGTERM(e.g.docker stop) orSIGINT(Ctrl-C), the app performs a graceful shutdown: it stops accepting new triggers and waits for the current run to finish its Trakt write before exiting, so lists are never left half-updated.
Run the container as a long-lived daemon instead of a one-shot job:
# docker-compose.yml
services:
flixpatrol:
image: ghcr.io/navino16/flixpatrol-top10-on-trakt:latest
restart: unless-stopped
environment:
- TZ=Europe/Paris
volumes:
- ./config:/app/configWith Schedule.enabled: true in config/default.json, this container stays up, runs on the configured cron
schedule(s), and survives restarts (restart: unless-stopped). This replaces the external-cron pattern shown above
(docker run triggered by a host cron entry) — you no longer need a cron job on the host, since the schedule now
lives inside the app itself.
| Problem | Solution |
|---|---|
| "Rate limit exceeded" | Increase time between runs. The cache helps reduce API calls. |
| "List limit reached" | Trakt free accounts are limited to 5 lists. Upgrade to VIP or reduce configured lists. |
| "No items found" | Verify the platform/location combination exists on FlixPatrol. |
| "Bad matching" | This is a FlixPatrol/Trakt limitation. Titles are matched by name and year. |
| "Authentication failed" | Delete ./config/.trakt and re-authenticate. |
| "Permission denied" on config folder (Docker) | The Docker image runs as a non-root user (flixpatrol, UID 1000). Fix permissions with: sudo chown -R 1000:1000 /path/to/config |
"Unable to get FlixPatrol ... page" with HTTP 403 (cf-mitigated: challenge) |
Cloudflare is challenging the request. Enable the optional FlareSolverr block (see Configuration File). |
# Install dependencies
npm install
# Run in development mode (hot reload)
npm run start:dev
# Build
npm run build
# Run after build
npm run start
# Lint
npm run lint
# Lint and auto-fix
npm run lint-and-fix
# Create cross-platform binaries
npm run package