-
Notifications
You must be signed in to change notification settings - Fork 16
Label Sync
Label Sync mirrors a tag from one service onto a tag or label in another. Pick a source service and tag (e.g., a "kids" tag in Sonarr), pick a destination service and tag (e.g., a "kids" label in Plex), and Label Sync keeps the destination in sync. Works any-to-any across Sonarr, Radarr, Plex, Jellyfin, and Emby — including same-service rules.
A Label Sync rule is a directed mapping:
[Source] [Destination]
Sonarr "kids" tag ──→ Plex "kids" label
Plex "favorites" ──→ Sonarr "favorites" tag
Sonarr (instance A) ──→ Sonarr (instance B)
The workflow is:
- Configure — Define a rule with source service + source tag and destination service + destination instance + destination tag
- Match — On a 5-minute scheduler tick (with a 60-minute per-rule cooldown), Label Sync reads tagged items from the source and resolves them to items on the destination
- Apply — The destination tag/label is created if absent and merged additively onto matched items
Label Sync is the propagation half of the auto-tag/sync pipeline. Pair it with Auto-Tagger if you want criteria-based tagging at the source plus mirroring to other services.
You need at least one source instance and one destination instance configured in Settings:
- Sonarr / Radarr — for *arr-side tags
- Plex / Jellyfin / Emby — for media-server-side labels
A rule can use any service as either source or destination, including same-service rules (e.g., Sonarr → Sonarr to mirror tags between two Sonarr instances).
- Log in to Arr Dashboard
- Click Label Sync in the sidebar (Maintenance section)
- The page lists your existing rules with last-run status and a "Run now" button per rule
Each rule declares:
| Field | Description |
|---|---|
| Source Service | One of sonarr, radarr, plex, jellyfin, emby
|
| Source Instance | A specific instance, or "all instances of this service" |
| Source Tag Name | The tag/label to read from the source |
| Destination Service | One of sonarr, radarr, plex, jellyfin, emby
|
| Destination Instance | A specific instance (always required) |
| Destination Tag Name | The tag/label to apply on the destination (created if absent) |
The destination is always a single specific instance. The source can be a single instance or all enabled instances of a service — the latter is convenient for users running multiple Sonarrs (e.g., 1080p + 4K) who want both to feed a single Plex instance.
When source and destination are different services, Label Sync needs to match items across systems. It does this via TMDb ID, populated during the regular library/cache sync. An item without a TMDb ID on either side cannot be cross-mapped, so it will not receive the destination tag.
For same-service rules (e.g., Sonarr → Sonarr or Plex → Plex), the matching uses the service's native identity (item ID).
Tags are always merged into the destination's existing tag list — never replaced. If an item already has the destination tag, the rule is a no-op for that item.
- No removal. If a source item loses its tag later, Label Sync does not remove the destination tag. This is a deliberate v1 limitation; bidirectional state tracking would change the rule's surface significantly.
- No conflict resolution. Multiple rules writing different destination tags to the same item will all apply additively. There is no "last writer wins" mode.
- No filtering. Unlike Auto-Tagger, Label Sync has no criteria DSL — it propagates an existing tag verbatim. If you need filtering (e.g., "only sync the kids tag for items also tagged 'family'"), use Auto-Tagger to seed a more specific source tag and sync that.
Label Sync rules fire on four trigger sources, each with different latency characteristics. The fastest trigger that applies wins; the slower ones serve as safety nets if a faster one missed.
| Trigger | Fires when | Latency |
|---|---|---|
| Auto-tagger chain | The arr-dashboard auto-tagger writes a tag matched by a Label Sync rule's source service + tag name | Sub-second when the Connect webhook is configured on the Sonarr/Radarr instance; up to ~5 min via the auto-tagger scheduler otherwise |
| Library-sync delta detection | A tag was added or removed on an item externally (Sonarr/Radarr UI, third-party tool, manual edit) and the next library-sync poll detects the diff | 5–15 min (matches your library-sync interval) |
| Per-item "Sync labels now" button | User clicks the button on the library item detail modal | Instant |
| Scheduler safety net | Hourly tick runs all enabled rules end-to-end | ≤1 hour |
All four triggers funnel through the same executor and apply the same idempotency rules — already-applied labels no-op at the writer layer, so triggering the same rule for the same item multiple times is safe and inexpensive.
*Currently arr-source rules only. The auto-tagger chain and library-sync delta detection only fire for rules where the source service is Sonarr or Radarr. Rules with Plex / Jellyfin / Emby as the source still rely on the per-item button or the hourly scheduler. A future release will extend delta detection to media-server source labels.
Plex must already have the item. A trigger fired for a brand-new item only succeeds if Plex has already scanned the file and matched it by tmdbId. If the trigger fires before Plex catches up, the executor returns "0 matches" silently — the next trigger (or the hourly scheduler) heals the gap once Plex completes its scan.
The four triggers compose so that every realistic tag change converges, even if individual triggers miss:
- Best case (auto-grab + Connect webhook): Radarr imports → auto-tagger applies tag → Label Sync fires inline → Plex label visible in seconds
- *Common case (manual arr edit): User tags in Sonarr UI → next library-sync poll detects the diff → Label Sync fires → label visible within ~10 min
- Edge case: Trigger fires before Plex has scanned the new file → 0 matches → scheduler tick within the next hour heals it once Plex item exists
In every path the system converges. No path leaves a state permanently incorrect.
The hourly scheduler's two ticks cannot overlap (a singleton in-flight guard skips the second). Event-driven triggers are independent and can run concurrently — the executor is idempotent at the writer layer, so concurrent runs for the same rule + item produce the same final state.
- Click Add Rule on the Label Sync page
- Give the rule a Name (shown in the rules list and scheduler logs)
- Pick Source Service from the dropdown (Sonarr, Radarr, Plex, Jellyfin, Emby)
- Pick Source Instance — either a specific instance, or "all instances" of the source service
- Enter the Source Tag Name — the tag/label name on the source side (case-sensitive)
- Pick Destination Service from the dropdown
- Pick Destination Instance — always a specific instance (required)
- Enter the Destination Tag Name — the tag/label to write. Created automatically on the destination if it does not exist.
- Toggle Enabled — disabled rules are skipped by the scheduler
- Save the rule
Each rule has a Run now button that executes the rule immediately and persists the result (lastRunAt, lastRunStatus, lastRunMessage). This runs the rule against the full source library; use it after creating a new rule to backfill labels for items that already carry the source tag.
Each Sonarr/Radarr item's detail modal (Library page → click an item) includes a Sync labels now button. Clicking it fires every enabled Label Sync rule whose source matches that item's *arr instance, scoped to just that one item.
Use cases:
- You just changed a tag in Sonarr/Radarr's UI and want to verify Plex propagation without waiting for the next library-sync tick
- You added a new Label Sync rule and want to test it on one specific item before letting it apply library-wide
- You have a Plex-source rule (no automatic event-driven trigger) and want to push the label now
The button surfaces the result inline ("Synced 1 label", "No matching rules configured", or an error message). Currently movie and series items only — per-episode and music items are deferred to a future release.
Label Sync propagates tags. Auto-Tagger creates tags. Together they form a two-stage pipeline:
[Auto-Tagger] [Label Sync]
Genre = "Anime" rule ──→ Sonarr "Anime" tag ──→ Plex "Anime" label
CF Score > 1000 rule ──→ Radarr "Premium" tag ──→ Jellyfin "Premium" label
This split keeps each engine focused. Auto-Tagger handles "when should this tag exist?" (criteria-based). Label Sync handles "where else should this tag live?" (propagation).
You can also use Label Sync standalone if you tag items manually in Sonarr/Radarr/Plex and want those tags mirrored elsewhere.
Each rule row on the Label Sync page shows:
| Field | Meaning |
|---|---|
| Last Run At | Timestamp of the last execution (scheduled or on-demand) |
| Last Run Status |
success (all items propagated), partial (some items failed), or failed (rule errored before any propagation) |
| Last Run Message | A breakdown like "Scanned 2 source instances, found 47 tagged items, matched 42 on destination, applied 38 labels (4 already had it)" or a failure reason |
The executor tracks five counts per run, exposed in the run message:
| Counter | Meaning |
|---|---|
sourceInstancesScanned |
How many source instances were walked |
taggedItemsFound |
Total source items carrying the source tag |
destMatchesFound |
Source items that resolved to a destination item |
labelsApplied |
Items where the destination tag was actually written (excludes items that already had it) |
failures |
Items that errored during the destination write |
A partial status means failures > 0 but at least one application succeeded. A failed status means the entire run errored before any application.
Source and destination tag names are stored verbatim. Renaming a tag on the source means the rule stops matching until you update the rule (or rename the source tag back). Pick names you intend to keep.
If you find yourself manually tagging items in Sonarr/Radarr just so Label Sync can propagate the tag, Auto-Tagger is the better tool. Define the criteria once, let Auto-Tagger maintain the source tag, let Label Sync handle the rest.
Cross-service rules depend on TMDb IDs being populated in both source and destination caches. If either cache is missing TMDb IDs, items cannot be cross-mapped. Check Settings → Services for cache health before assuming a rule is broken.
Because Label Sync never removes a tag, the destination accumulates labels over time. This is fine for tags representing fixed truths (e.g., "anime", "kids") but less ideal for transient state ("currently watching"). Use Run now after deleting a destination tag to re-establish a clean baseline.
Causes:
- The source tag name is misspelled or has different casing (tags are case-sensitive on most services)
- TMDb IDs are missing on either side, so cross-service matching fails
- The destination instance does not contain matching items
Solutions:
- Confirm the source tag name matches exactly in the source service (open Settings → Tags in Sonarr/Radarr or the labels list in Plex)
- Check Settings → Services and refresh the library cache for both source and destination
- Verify the destination has the items you expect (a Plex library that doesn't include the source content cannot match)
Cause: Same-service rules use native item IDs, not TMDb IDs. If both instances reference the same content but with different IDs (e.g., separate Sonarr DBs), the rule will not match correctly.
Solution: Same-service rules work best when both instances share the same underlying content database, or when both share TMDb IDs that can be used as the cross-key. Verify with one or two test items via "Run now" before relying on the rule.
Causes:
- The Plex/Jellyfin/Emby instance's user account does not have permission to write labels
- The tag was applied but the media server's UI is showing cached data
Solutions:
- Confirm the API token configured for the destination instance has write access
- Refresh metadata for the affected library in the destination service
- Check the dashboard's Label Sync run message for
labelsAppliedcount; if non-zero, the write did happen on the API side
Cause: The executor errored before any item was processed — typically an unreachable instance, an unsupported service in the URL, or an authentication failure.
Solutions:
- Read the
lastRunMessagefor the specific reason - Check Settings → Services for connectivity to both source and destination
- Verify both instances are enabled
Status: Not supported in v1. Label Sync is additive-only. To remove a destination tag, delete it manually from the destination service.
This is a deliberate v1 limitation — bidirectional sync semantics would require change-tracking infrastructure that is out of scope for the initial release.
All endpoints require a session cookie.
GET /api/label-sync/rules
Returns the user's rules sorted by createdAt descending.
POST /api/label-sync/rules
Content-Type: application/json
{
"name": "Sonarr kids → Plex kids",
"enabled": true,
"sourceService": "sonarr",
"sourceInstanceId": null,
"sourceTagName": "kids",
"destService": "plex",
"destInstanceId": "<plex-instance-uuid>",
"destTagName": "kids"
}
sourceInstanceId: null means "all enabled Sonarr instances owned by this user." destInstanceId is always a specific instance ID.
Both source and destination instances are validated for ownership and enablement at create time. Returns 404 if either is missing, disabled, or owned by a different user.
PATCH /api/label-sync/rules/:id
Content-Type: application/json
{ "enabled": false }
Any subset of fields can be updated. Source/destination changes are re-validated against the user's instances.
DELETE /api/label-sync/rules/:id
POST /api/label-sync/rules/:id/run
Executes the rule immediately and persists lastRunAt, lastRunStatus, lastRunMessage. Disabled rules return HTTP 400 — enable the rule first.
Response shape:
{
"rule": {
"id": "...",
"name": "Sonarr kids → Plex kids",
"lastRunAt": "2026-05-01T12:34:56Z",
"lastRunStatus": "success",
"lastRunMessage": "Scanned 2 source instances, found 47 tagged items, matched 42 on destination, applied 38 labels.",
...
}
}