Emby server plugin for reading and updating the current user's anime watch status in MyAnimeList through the official MyAnimeList API v2.
This plugin is 100% vibecoded with the help of Codex. I have no developer experience, but I wanted to try filling a niche that, in my opinion, still existed in the Emby plugin ecosystem.
The plugin is inspired by iankiller77/MyAnimeSync, which provides similar MyAnimeList sync functionality for Jellyfin only.
- OAuth2 Authorization Code with PKCE setup for MyAnimeList.
- Emby plugin configuration page for Client ID, Client Secret and Redirect URI.
- Per-Emby-user MyAnimeList account authorization.
- Configurable sync direction: MyAnimeList to Emby, Emby to MyAnimeList, or both directions.
- Scheduled sync task that runs every 4 hours by default.
- Daily token refresh and failed-update retry tasks.
- Playback stop trigger that pushes the watched item to MyAnimeList when the item has a MyAnimeList metadata id.
- Manual
Sync NowandRetry Failed Updatesbuttons in the plugin settings. - Plugin page registration for both the user menu and main menu, improving access from Emby mobile apps when the client exposes plugin web pages.
- API endpoints for authorization, connection test, anime search, current status lookup and status updates.
- Targets
netstandard2.0and referencesMediaBrowser.Server.Core, matching Emby's current plugin guidance.
dotnet build Emby.MyAnimeList.Sync.Plugin.slnTo copy the plugin and local dependencies into an Emby plugin folder after build:
dotnet build Emby.MyAnimeList.Sync.Plugin.sln -p:EmbyPluginPath="$HOME/.config/emby-server/plugins"Restart Emby Server after copying the plugin.
Open https://myanimelist.net/apiconfig, sign in and create a new API client.
Suggested MyAnimeList app fields:
| MyAnimeList field | Suggested value |
|---|---|
| App Name | Emby MyAnimeList Sync Plugin |
| App Type | Web if available, otherwise the closest non-mobile/non-native option |
| App Description | Emby plugin to read and update the current users anime watch status. |
| App Redirect URL | The Redirect URI shown in the Emby plugin settings, for example http://your-emby-server:8096/emby/MyAnimeList/Auth/Callback |
| Homepage URL | Your Emby server URL or project URL |
| Commercial / Non-Commercial | Non-Commercial |
| Name / Company Name | Your name or private project name |
| Purpose of Use | hobbyist |
| License agreement | Accept the MyAnimeList API license agreement if you agree with its terms |
Notes:
- The App Description must be between 50 and 500 characters.
- MyAnimeList says special characters are not supported, so use plain ASCII text.
- The redirect URL must match exactly between MyAnimeList and the Emby plugin settings.
- Do not use
http://localhostunless Emby is running on the same computer and you want to handle the code manually. The plugin works best with its callback URL:/emby/MyAnimeList/Auth/Callback.
After saving the app in MyAnimeList, copy the generated values into the Emby plugin settings:
| Emby plugin field | Value |
|---|---|
| MyAnimeList Client ID | The Client ID from MyAnimeList. Required. |
| MyAnimeList Client Secret (optional) | The Client Secret from MyAnimeList, if one was generated. Leave empty if there is none. |
| Redirect URI | The same value as App Redirect URL in MyAnimeList. The plugin detects its callback URL automatically. |
| Sync direction | Choose Both directions, MyAnimeList to Emby, or Emby to MyAnimeList. |
The MyAnimeList app settings are global for the Emby server. The MyAnimeList login itself is stored per Emby user, so each Emby user should open the plugin settings while logged in with their own Emby account and run the OAuth connection flow once.
OAuth connection flow:
- Save the Emby plugin settings.
- Click
Create and Open Authorization Page. - Emby creates the authorization URL in the background and sends you straight to MyAnimeList. Approve access on the MyAnimeList page.
- Approve access in MyAnimeList.
- MyAnimeList redirects back to the Emby callback URL and the plugin stores the tokens automatically.
- Return to the Emby plugin settings. The page shows whether this Emby user is connected;
Test Connectioncan still be used for a live check.
If the test succeeds, Emby should show the connected MyAnimeList username.
The Authorization Code (manual fallback) field is not filled during the normal callback flow. It is only needed if you use a manual redirect URL and want to paste a code=... value yourself.
The plugin registers a scheduled Emby task named Sync MyAnimeList anime status in the MyAnimeList category. Emby runs it every 4 hours by default, and you can also start or adjust it from Emby's scheduled tasks screen.
The plugin also registers two daily maintenance tasks:
Refresh MyAnimeList user tokensrefreshes OAuth tokens for connected users.Retry failed MyAnimeList updatesretries updates that failed during playback-triggered or scheduled sync.
Playback events are handled separately: when playback stops, the plugin checks the played movie or episode. If the item, or the episode's parent series, has a MyAnimeList provider id, the plugin pushes the current Emby watched progress to MyAnimeList for the current Emby user. Items without a MyAnimeList id are skipped.
For series, the MyAnimeList id is read from season metadata first, then episode metadata, then series metadata. If Emby only has the series-level id for season 1, the plugin resolves later Emby seasons through MyAnimeList sequel relations so season 2, 3 and later entries can be written to their own MyAnimeList anime ids. For movies, a watched Emby movie is treated as 1/1 and marked completed in MyAnimeList.
When the sync direction is Both directions, the plugin uses the higher watched episode count from Emby or MyAnimeList. It marks missing episodes as played in Emby or updates MyAnimeList, but it does not remove watched state from Emby.
The plugin settings page has a Sync Now button for the current Emby user and a Retry Failed Updates button for failed updates belonging to the current Emby user. Failed updates are listed on the same page.
GET /emby/MyAnimeList/Auth/UrlGET /emby/MyAnimeList/Auth/UrlJsonPOST /emby/MyAnimeList/Auth/Tokenwith{ "Code": "..." }GET /emby/MyAnimeList/Auth/TestGET /emby/MyAnimeList/Search/Anime?Query=one%20piece&Limit=10GET /emby/MyAnimeList/Anime/{AnimeId}/StatusPOST /emby/MyAnimeList/Anime/{AnimeId}/Status
Example status update body:
{
"Status": "watching",
"Score": 8,
"EpisodesWatched": 12,
"IsRewatching": false
}Valid status values are watching, completed, on_hold, dropped and plan_to_watch.