Note: This project is under development. Feedback, suggestions, and issues are highly appreciated!
Program to synchronize your AniList and MyAnimeList accounts.
- Bidirectional sync between AniList and MyAnimeList (anime and manga)
- Favorites synchronization (MAL → AniList with add-only, AniList → MAL report-only)
- OAuth2 authentication
- CLI interface
- Manual ID mappings and ignore rules via
mappings.yaml - Duplicate target detection with automatic conflict resolution
- Unmapped entries tracking with interactive management (
unmappedcommand) - Offline ID mapping using anime-offline-database (prevents incorrect season matches)
- Optional ARM API integration for online ID lookups
For each entry in your list the following fields are synchronized from source to target:
| Field | Synced |
|---|---|
| Status (watching / completed / on-hold / dropped / plan to watch) | ✅ |
| Score (automatically normalized between AniList and MAL score formats) | ✅ |
| Progress (episodes watched / chapters + volumes read) | ✅ |
| Start date | ✅ (nil source date never overwrites a set target date) |
| Finish date | ✅ (only when status is Completed) |
| Favorites | ✅ optional, via --favorites flag |
Conflict rule: the source service always wins. In the default direction (AniList → MAL) your AniList data overwrites MAL. Use --reverse-direction to flip.
See docs/date-sync.md for detailed date synchronization rules.
| Deployment | Requirements |
|---|---|
| Docker | Docker + docker compose (v2, built-in) or legacy docker-compose (v1) |
| Binary (go install) | Go 1.25+ |
| Local build | Go 1.25+, git |
AniList:
- Go to AniList Developer Settings
- Create New Client with redirect URL:
http://localhost:18080/callback - Save Client ID and Client Secret
MyAnimeList:
- Go to MAL API Settings
- Create Application with redirect URL:
http://localhost:18080/callback - Save Client ID and Client Secret
Download and rename the example compose file, then fill in your credentials:
cp docker-compose.example.yaml docker-compose.yamlEdit docker-compose.yaml with your credentials:
services:
sync:
image: ghcr.io/bigspawn/anilist-mal-sync:latest
command: ["watch", "--once"]
ports:
- "18080:18080"
environment:
# User/Group ID for volume permissions (run: id -u / id -g)
- PUID=1000
- PGID=1000
# Required: API credentials
- ANILIST_CLIENT_ID=your_anilist_client_id
- ANILIST_CLIENT_SECRET=your_anilist_secret
- ANILIST_USERNAME=your_anilist_username
- MAL_CLIENT_ID=your_mal_client_id
- MAL_CLIENT_SECRET=your_mal_secret
- MAL_USERNAME=your_mal_username
# Watch mode interval (min: 1h, max: 168h / 7 days)
- WATCH_INTERVAL=12h
# Optional: Manual mappings file path
# - MAPPINGS_FILE_PATH=/home/appuser/.config/anilist-mal-sync/mappings.yaml
# Optional: ID Mapping settings
# - OFFLINE_DATABASE_ENABLED=true # Enable offline DB (default: true)
# - HATO_API_ENABLED=true # Enable Hato API (default: true)
# - HATO_API_URL=https://hato.malupdaterosx.moe # Hato API base URL
# - HATO_API_CACHE_DIR=/home/appuser/.config/anilist-mal-sync/hato-cache # Cache directory
# - HATO_API_CACHE_MAX_AGE=720h # Cache max age (default: 720h / 30 days)
# - ARM_API_ENABLED=false # Enable ARM API (default: false)
# - ARM_API_URL=https://arm.haglund.dev # ARM API base URL
# - JIKAN_API_ENABLED=false # Enable Jikan API for manga ID mapping
# - JIKAN_API_CACHE_DIR=/home/appuser/.config/anilist-mal-sync/jikan-cache
# - JIKAN_API_CACHE_MAX_AGE=168h
# - FAVORITES_SYNC_ENABLED=false # Enable favorites sync (requires Jikan API)
volumes:
- tokens:/home/appuser/.config/anilist-mal-sync
restart: unless-stopped
volumes:
tokens:docker-compose run --rm --service-ports sync loginThe tool will print two URLs — one for MyAnimeList and one for AniList. For each:
- Copy the URL and open it in your browser
- Authorize the application on the website
- Your browser will redirect to
http://localhost:18080/callback— the tool captures this automatically - Repeat for both services (MAL first, then AniList)
Note: The
--service-portsflag is required here so that the OAuth redirect to port 18080 reaches the container. Make sure port 18080 is free on your host.
Tokens are saved into the tokens Docker volume and persist across restarts.
Recommended before the first real sync. On a large list the tool may update hundreds of entries at once — dry run lets you see exactly what will change without touching anything.
docker-compose run --rm sync sync --dry-run --alldocker-compose up -d starts the container in watch mode (--once flag causes an
immediate first sync, then it repeats every WATCH_INTERVAL hours in the background).
docker-compose up -dDone! The service will sync your lists every 12 hours (or whatever you set in WATCH_INTERVAL).
To check that the service started correctly and view sync output:
docker-compose logs -f syncNote:
WATCH_INTERVALaccepts values between1hand168h(7 days). To run a one-off sync instead of the daemon use:docker-compose run --rm sync sync
| Command | Description |
|---|---|
login |
Authenticate with services |
logout |
Remove authentication tokens |
status |
Check authentication status |
sync |
Synchronize anime/manga lists |
watch |
Run sync on interval |
unmapped |
Show and manage unmapped entries from last sync |
Global options (available for all commands):
| Short | Long | Description |
|---|---|---|
-c |
--config |
Path to config file (optional, uses env vars if not specified) |
Login/Logout options:
| Short | Long | Description |
|---|---|---|
-s |
--service |
Service: anilist, myanimelist, all (default: all) |
Sync options:
| Short | Long | Description |
|---|---|---|
-f |
--force |
Force sync all entries |
-d |
--dry-run |
Dry run without making changes |
--manga |
Sync manga instead of anime | |
--all |
Sync both anime and manga | |
--verbose |
Enable verbose logging | |
--reverse-direction |
Sync from MyAnimeList to AniList | |
--offline-db |
Enable offline database for anime ID mapping (default: true, ignored for --manga) |
|
--offline-db-force-refresh |
Force re-download offline database | |
--arm-api |
Enable ARM API for anime ID mapping (default: false, ignored for --manga) |
|
--arm-api-url |
ARM API base URL | |
--jikan-api |
Enable Jikan API for manga ID mapping (default: false, ignored for anime) |
|
--favorites |
Sync favorites between services (requires Jikan API for MAL favorites) |
Watch options:
| Short | Long | Description |
|---|---|---|
-i |
--interval |
Sync interval: 1h-168h (overrides config) |
--once |
Run one sync immediately, then start the interval loop. Without this flag the first sync is delayed by the full interval. |
Interval can be set via --interval flag or in config.yaml under watch.interval.
Unmapped options:
| Short | Long | Description |
|---|---|---|
--fix |
Interactively fix unmapped entries (ignore or map to MAL ID) | |
--ignore-all |
Add all unmapped entries to ignore list |
For backward compatibility, running anilist-mal-sync [options] without a command will execute sync.
Full config.yaml example:
oauth:
port: "18080"
redirect_uri: "http://localhost:18080/callback"
anilist:
client_id: "your_client_id"
client_secret: "your_secret"
auth_url: "https://anilist.co/api/v2/oauth/authorize"
token_url: "https://anilist.co/api/v2/oauth/token"
username: "your_username"
myanimelist:
client_id: "your_client_id"
client_secret: "your_secret"
auth_url: "https://myanimelist.net/v1/oauth2/authorize"
token_url: "https://myanimelist.net/v1/oauth2/token"
username: "your_username"
token_file_path: "" # Leave empty for default: ~/.config/anilist-mal-sync/token.json
mappings_file_path: "" # Leave empty for default: ~/.config/anilist-mal-sync/mappings.yaml
watch:
interval: "24h" # Sync interval for watch mode (1h-168h), can be overridden with --interval flag
http_timeout: "30s" # HTTP client timeout for API requests (default: 30s)
offline_database:
enabled: true
cache_dir: "" # Default: ~/.config/anilist-mal-sync/aod-cache
auto_update: true
arm_api:
enabled: false
base_url: "https://arm.haglund.dev" # Default: https://arm.haglund.dev
hato_api:
enabled: true # Enable Hato API for ID mapping (default: true)
base_url: "https://hato.malupdaterosx.moe" # Hato API base URL
cache_dir: "" # Leave empty for default: ~/.config/anilist-mal-sync/hato-cache
cache_max_age: "720h" # Cache max age (default: 720h / 30 days)
jikan_api:
enabled: false # Enable Jikan API for manga ID mapping (default: false)
cache_dir: "" # Default: ~/.config/anilist-mal-sync/jikan-cache
cache_max_age: "168h" # Cache max age (default: 168h / 7 days)
favorites:
enabled: false # Enable favorites synchronization (default: false)The tool uses different ID mapping strategies for anime and manga, and the chain differs by direction.
Anime (sync or sync --all):
- Manual Mapping - User-defined AniList↔MAL mappings from
mappings.yaml - Direct ID lookup - If the entry already exists in your target list
- Offline Database (optional, enabled by default) - Local database from anime-offline-database
- Hato API (optional, enabled by default) - Online API for anime/manga ID mapping
- ARM API (optional, disabled by default) - Online fallback to arm-server
- Title matching - Match by title similarity
- API search - Search the MAL API
Manga (sync --manga or sync --all):
- Manual Mapping - User-defined AniList↔MAL mappings from
mappings.yaml - Direct ID lookup - If the entry already exists in your target list
- Hato API (optional, enabled by default) - Online API for manga ID mapping
- Title matching - Match by title similarity
- Jikan API (optional, disabled by default) - Online API for manga ID mapping via Jikan (unofficial MAL API)
- API search - Search the MAL API
Anime (sync --reverse-direction):
- Manual Mapping - User-defined AniList↔MAL mappings from
mappings.yaml - Direct ID lookup - If the entry already exists in your target list
- Offline Database (optional, enabled by default)
- Hato API (optional, enabled by default)
- ARM API (optional, disabled by default)
- Title matching
- MAL ID lookup - Find AniList entry by MAL ID directly
- API search - Search the AniList API
Manga (sync --manga --reverse-direction):
- Manual Mapping
- Direct ID lookup
- Hato API (optional, enabled by default)
- Title matching
- Jikan API (optional, disabled by default)
- MAL ID lookup - Find AniList entry by MAL ID directly
- API search - Search the AniList API
Notes:
- The offline database and ARM API are anime-only and automatically disabled when using
--mangaflag (without--all) to improve startup performance. - Hato API supports both anime and manga and is enabled by default.
You can define manual ID mappings and ignore rules in a YAML file (mappings.yaml):
manual_mappings:
- anilist_id: 12345
mal_id: 67890
comment: "Season 2 mapped manually"
ignore:
anilist_ids:
- 99999 # Title Name : reason for ignoring
titles:
- "Some Title to Ignore"Default location: ~/.config/anilist-mal-sync/mappings.yaml
You can also manage ignore rules interactively:
# Show unmapped entries from last sync
anilist-mal-sync unmapped
# Interactively fix unmapped entries (ignore or map to MAL ID)
anilist-mal-sync unmapped --fix
# Add all unmapped entries to ignore list
anilist-mal-sync unmapped --ignore-allConfiguration can be provided entirely via environment variables (recommended for Docker):
Required:
ANILIST_CLIENT_ID- AniList Client IDANILIST_CLIENT_SECRET- AniList Client Secret (also acceptsCLIENT_SECRET_ANILIST)ANILIST_USERNAME- AniList usernameMAL_CLIENT_ID- MyAnimeList Client IDMAL_CLIENT_SECRET- MyAnimeList Client Secret (also acceptsCLIENT_SECRET_MYANIMELIST)MAL_USERNAME- MyAnimeList username
Required for watch mode:
WATCH_INTERVAL- Sync interval (e.g.,12h,24h); range1h–168h. Without this (or--intervalflag) the watch command fails.
Optional:
HTTP_TIMEOUT- HTTP client timeout for API requests (default:30s, e.g.,10s,1m)OAUTH_PORT- OAuth server port (default:18080)OAUTH_REDIRECT_URI- OAuth redirect URI (default:http://localhost:18080/callback)TOKEN_FILE_PATH- Token file path (default:~/.config/anilist-mal-sync/token.json)MAPPINGS_FILE_PATH- Path to manual mappings YAML file (default:~/.config/anilist-mal-sync/mappings.yaml)PUID/PGID- User/Group ID for Docker volume permissionsOFFLINE_DATABASE_ENABLED- Enable offline database for anime ID mapping (default:true, not used for manga-only sync)OFFLINE_DATABASE_CACHE_DIR- Cache directory (default:~/.config/anilist-mal-sync/aod-cache)OFFLINE_DATABASE_AUTO_UPDATE- Auto-update database (default:true)HATO_API_ENABLED- Enable Hato API for ID mapping (default:true, supports both anime and manga)HATO_API_URL- Hato API base URL (default:https://hato.malupdaterosx.moe)HATO_API_CACHE_DIR- Hato API cache directory (default:~/.config/anilist-mal-sync/hato-cache)HATO_API_CACHE_MAX_AGE- Hato API cache max age (default:720h/ 30 days)ARM_API_ENABLED- Enable ARM API for anime ID mapping (default:false, not used for manga-only sync)ARM_API_URL- ARM API base URL (default:https://arm.haglund.dev)JIKAN_API_ENABLED- Enable Jikan API for manga ID mapping (default:false, not used for anime sync)JIKAN_API_CACHE_DIR- Jikan API cache directory (default:~/.config/anilist-mal-sync/jikan-cache)JIKAN_API_CACHE_MAX_AGE- Jikan API cache max age (default:168h/ 7 days)FAVORITES_SYNC_ENABLED- Enable favorites synchronization (default:false)
Favorites sync is an optional feature that synchronizes your favorited anime and manga between AniList and MyAnimeList. It runs as a separate phase after the main status/progress synchronization.
| Direction | Read | Write | Behavior |
|---|---|---|---|
| MAL → AniList | ✅ via Jikan API | ✅ via ToggleFavourite mutation | Full sync (add missing favorites) |
| AniList → MAL | ✅ via isFavourite field | ❌ MAL API v2 has no favorites endpoint | Report only |
Via CLI flag:
# Sync with favorites enabled
anilist-mal-sync sync --favorites
# Reverse sync (MAL → AniList) with favorites
anilist-mal-sync sync --favorites --reverse-directionVia environment variable:
export FAVORITES_SYNC_ENABLED=true
anilist-mal-sync syncVia config file:
favorites:
enabled: trueVia Docker:
environment:
- FAVORITES_SYNC_ENABLED=trueNote: The --favorites flag automatically enables Jikan API (required for reading MAL favorites).
- Reads your MAL favorites via Jikan API (public user profile)
- Compares with your AniList list entries
- Adds missing favorites on AniList
- Does not remove favorites that exist only on AniList (you may have intentionally favorited different items)
Example output:
★ [Favorites] Added "Cowboy Bebop" to AniList favorites
★ [Favorites] Added "Monster" to AniList favorites
★ Favorites sync complete: +2 added on AniList (15 skipped)
- Reads your AniList favorites from list entries (via
isFavouritefield) - Reads your MAL favorites via Jikan API
- Reports differences (cannot write to MAL)
Example output:
★ [Favorites] anime "Cowboy Bebop" is only on AniList
★ [Favorites] manga "Berserk" is only on MAL
★ Favorites: 2 mismatches (AniList→MAL, report only)
For detailed documentation, see docs/favorites-sync.md.
Requires Go 1.25+ (download).
Option A — install from registry:
go install github.com/bigspawn/anilist-mal-sync@latestOption B — clone and build locally:
git clone https://github.com/bigspawn/anilist-mal-sync.git
cd anilist-mal-sync
go build -o anilist-mal-sync .First run:
# 1. Create config file
cp config.example.yaml config.yaml
# Edit config.yaml with your AniList and MAL credentials
# 2. Authenticate (opens OAuth flow on port 18080)
anilist-mal-sync -c config.yaml login
# 3. Preview changes before syncing
anilist-mal-sync -c config.yaml sync --dry-run --all
# 4. Run sync
anilist-mal-sync -c config.yaml syncTokens are saved to ~/.config/anilist-mal-sync/token.json by default.
docker compose vs docker-compose: Examples use
docker-compose(CLI v1). If your system has Docker Compose v2 (bundled with modern Docker Desktop / Engine), replacedocker-composewithdocker compose(no hyphen).
See Quick Start for the recommended setup.
Using config file instead of environment variables:
docker run --rm -p 18080:18080 \
-e PUID=$(id -u) -e PGID=$(id -g) \
-v $(pwd)/config.yaml:/etc/anilist-mal-sync/config.yaml:ro \
-v $(pwd)/tokens:/home/appuser/.config/anilist-mal-sync \
ghcr.io/bigspawn/anilist-mal-sync:latest -c /etc/anilist-mal-sync/config.yaml syncEnable continuous sync by setting WATCH_INTERVAL environment variable:
environment:
- WATCH_INTERVAL=12h # Sync every 12 hoursOr run watch command manually:
docker-compose run --rm sync watch --interval=12hInterval limits: 1h - 168h (7 days). WATCH_INTERVAL (or --interval) is required for watch mode — without it the command exits with an error.
Use your system's scheduler for periodic sync:
# Linux/macOS cron (daily at 2 AM)
0 2 * * * /usr/local/bin/anilist-mal-sync sync"Required environment variables not set"
- Set required env vars:
ANILIST_CLIENT_ID,ANILIST_CLIENT_SECRET,ANILIST_USERNAME,MAL_CLIENT_ID,MAL_CLIENT_SECRET,MAL_USERNAME - Or use config file with
-c /path/to/config.yaml
Authentication fails
- Check redirect URL matches exactly:
http://localhost:18080/callback - Verify client ID and secret are correct
- Ensure port 18080 is not already in use
Sync appears frozen
- Both services have rate limits. Wait a few minutes and try again
- Use
--verboseto see progress
Token expired
- Run
anilist-mal-sync statusto check - Run
anilist-mal-sync loginto reauthenticate (re-authenticates both services)
This project is not affiliated with AniList or MyAnimeList. Use at your own risk.
- Sync rewatching and rereading counts
- anime-offline-database for JSON based anime dataset
- arm-server for API anime dataset
- Hato for JSON API anime and manga
- Jikan for unofficial MyAnimeList API