Skip to content

Auto Tagger

khak1s edited this page May 3, 2026 · 2 revisions

Auto-Tagger Guide

The Auto-Tagger applies tags to your Sonarr and Radarr items based on rule criteria — the same 50+ rule type DSL used by Library Cleanup, plus composite (AND/OR) rules and real-time tagging via Sonarr/Radarr Connect webhooks. Pair with Label Sync to mirror those tags onto Plex, Jellyfin, or Emby labels.

Overview

Auto-Tagger automates what you would otherwise do manually: walking your library to apply consistent tags to items that match a definition (genre + year, watch state, custom format score, list membership, etc.).

The workflow follows three stages:

  1. Configure — Define an Auto-Tag rule with a single rule type or a composite (AND/OR) of multiple conditions, and the tag name to apply
  2. Match — Either the 5-minute scheduler tick walks your LibraryCache or a Sonarr/Radarr Connect webhook fires within seconds of an import event
  3. Apply — The rule walks matched items and merges the tag into the source-side *arr (creating it if absent)

Tags written by Auto-Tagger are real *arr tags. They appear in Sonarr/Radarr, can be filtered on in those tools, and can drive other Sonarr/Radarr automations (custom format profile assignment, release profiles, notifications) just like any manually-applied tag.


Getting Started

Prerequisites

  1. At least one Sonarr or Radarr instance configured in Settings (Lidarr is not yet supported)
  2. (Optional) Plex, Jellyfin/Emby, Tautulli, or Seerr for cross-service rule criteria (e.g., "tag items that have Plex label X" or "tag items requested in Seerr by user Y")
  3. (Optional, for TMDb list-membership rules) A TMDb v3 read-access token in Settings → Account
  4. (Optional, for Trakt list-membership rules) A Trakt personal access token in Settings → Account, plus the operator-side TRAKT_CLIENT_ID env var

Access the Auto-Tagger

  1. Log in to Arr Dashboard
  2. Click Auto-Tag in the sidebar (Maintenance section)
  3. The page lists your existing rules with last-run status and a "Run now" button per rule, plus the webhook configuration panel

How It Works

Two Trigger Paths

Auto-Tagger rules can match items in two ways:

Trigger When Latency
Scheduler tick Every 5 minutes Up to 5 minutes for new rules; subsequent passes respect a 60-minute per-rule cooldown
Connect webhook Sonarr/Radarr fires on import-related events Sub-second

Both triggers use the same evaluation engine and apply the same tag merge logic. Webhooks are optional — without them, the scheduler still tags imports within ~5 minutes when the next library cache refresh runs.

Scheduler Tick

A scheduler runs every 5 minutes and selects rules that are either:

  • Never run (no lastRunAt), or
  • Last run more than 60 minutes ago

For each due rule, the scheduler walks every item in the user's LibraryCache and evaluates the rule. Items that match get the rule's tag merged in via a single series.update or movie.update call per item.

Per-Rule Lock

A per-rule mutex prevents two concurrent executions from racing. If you click "Run now" while the scheduler tick is mid-execution for the same rule, the second request returns HTTP 409 with the message "Rule is currently running — try again in a moment."

This matters because two concurrent series.update or movie.update calls perform a read-modify-write on the same tags: number[] array; without a lock, one rule's tag merge can clobber another's.

Sonarr/Radarr Connect Webhook

Sonarr and Radarr can fire HTTP webhooks via their Connect → Webhook notification setting. Auto-Tagger exposes a public endpoint that receives those events and tags the imported item within seconds.

Allowed event types (anything else is acknowledged with HTTP 202 and ignored):

  • Download, Grab, Rename, Upgrade
  • MovieFile.Import, MovieFile.Download
  • ManualInteraction, ManualInteractionRequired
  • Test — acknowledged with no-op so you can verify the wiring

The webhook handler:

  1. Authenticates via Bearer token (the user's hashed webhook secret)
  2. Verifies the instance ID in the URL belongs to the authenticated user
  3. Live-fetches the imported item from the *arr API (the LibraryCache row may not exist yet — sync runs every 6h, but Connect fires within seconds)
  4. Evaluates every enabled rule whose scope includes the instance
  5. Merges all matching rule tags into the item

One-click auto-install (2.18.2+)

You no longer need to hand-paste the URL and Bearer header into each *arr's Connect settings. Inside Settings → Auto-Tagger → Real-time webhook, the Auto-install sub-panel discovers your enabled Sonarr/Radarr instances and installs the canonical arr-dashboard auto-tagger Connect notification on selected instances in a single click.

Prerequisites

  1. Click Rotate secret in the Real-time webhook panel — the plaintext is only displayed at generation time and is required for the install button to be enabled.
  2. The instances you target must be enabled in Settings → Services. Disabled instances are excluded from the discovery list.

What gets installed

For each selected instance, the dashboard creates (or updates, on re-run) a Connect notification named arr-dashboard auto-tagger with:

  • implementation: Webhook
  • url: the auto-tag webhook URL with the instance ID baked in
  • headers: Authorization: Bearer <your-webhook-secret>
  • Event flags from the Events checkboxes (default: onDownload + onUpgrade, no onGrab)

Idempotency

The installer matches by notification name, so re-running it updates the existing entry instead of creating duplicates. Rotating the webhook secret invalidates the old Bearer header — re-run the install on every instance after rotation.

Per-instance failures

If an *arr is offline, has a stale API key, or otherwise rejects the create call, that instance reports its error inline (e.g. 401 Invalid API key) without aborting the rest of the batch. The dashboard's Bearer secret is never sent for failed instances.

URL resolution

The installer prefers SystemSettings.externalUrl when set (System → Settings → External URL). If unset, it falls back to the request's protocol + hostname as observed by Fastify's trustProxy-aware request fields. If your dashboard is behind a reverse proxy, set externalUrl explicitly to avoid relying on X-Forwarded-* headers.

Library Cache Dependency

Scheduled rule evaluation reads from LibraryCache. The cache is populated by background sync; rules will not match items until the cache contains them. Webhook-driven evaluation does not depend on the cache — it live-fetches the item directly.


Configuration

Creating a Rule

  1. Click Add Rule on the Auto-Tag page
  2. Give the rule a Name (shown in the rules list and in scheduler logs)
  3. Pick a Tag Name — the tag to apply to matched items. Created on the *arr side automatically if it does not exist.
  4. Configure a Single criterion OR a Composite (AND/OR) of conditions (see Rule Types below)
  5. Optionally set Scope filters to narrow which items the rule sees:
    • Service Filter — Limit to Sonarr or Radarr only
    • Instance Filter — Limit to specific instances
    • Exclude Tags — Skip items that already carry one of these *arr tag IDs
    • Exclude Titles — Skip items whose title matches any of these regex patterns
    • Plex Library Filter — Limit to items in specific Plex sections (only meaningful for rules that depend on Plex data)
  6. Toggle Enabled — disabled rules are skipped by both the scheduler and the webhook
  7. Save the rule

Composite (AND/OR) Rules

Composite rules combine multiple conditions with a single boolean operator. Examples:

  • Genre is "Anime" AND Audio Channels >= 6 AND Custom Format Score > 1000 → tag "Premium Anime"
  • Year >= 2020 AND HDR Type is HDR10 AND Plex Watch Count = 0 → tag "New 4K Unwatched"
  • tmdb_list_member is_in 8068 OR Plex Label is "Award Winner" → tag "Awards"

Each condition uses the same parameter schema as its standalone rule type. Composite rules and single-criterion rules cannot be mixed in the same rule — switch the rule's mode in the dialog.

Run Now

Each rule has a Run now button. This:

  • Acquires the per-rule lock; returns HTTP 409 if a scheduler tick or another run is already executing this rule
  • Walks every cached library item once
  • Persists lastRunAt, lastRunStatus (success / partial / failed), and lastRunMessage (count summary or failure reason)

Rule Types

The Auto-Tagger uses the same rule criteria DSL as Library Cleanup. See the Library Cleanup#Rule Types for the full table covering Content Attributes, File Metadata, Seerr/Plex/Jellyfin/Tautulli integration rules, and Behavior-Aware rules.

In addition, Auto-Tagger ships with two list-membership rule types not yet exposed in Library Cleanup:

List-Membership Rules

Rule Type Description Key Parameters
TMDb List Member Match items in a TMDb curated list List ID (e.g., 8068), Operator (is_in / not_in)
Trakt List Member Match items in a Trakt curated list List slug (e.g., trakt-official/oscar-winners), Operator (is_in / not_in)

Both use cached membership tables refreshed every 4 hours by background schedulers. Lists referenced by enabled rules get refreshed; cache rows for unreferenced lists are garbage-collected.

TMDb requirements: A TMDb v3 read-access token in Settings → Account (the same token Discover uses). Public lists only — private list support would need TMDb v4 OAuth.

Trakt requirements: A per-user Trakt personal access token in Settings → Account, plus the operator-side TRAKT_CLIENT_ID env var (issued from trakt.tv/oauth/applications). Without TRAKT_CLIENT_ID set, the Trakt scheduler logs "skipped — TRAKT_CLIENT_ID not configured" and trakt rules will not match.


Connect Webhook Setup

The webhook gives you sub-second tagging on import, instead of waiting up to 5 minutes for the scheduler tick.

Generate the Webhook Secret

  1. Open the Webhook Configuration panel on the Auto-Tag page
  2. Click Generate Secret if no secret exists yet, or Rotate Secret to invalidate the old one
  3. Copy the plaintext secret immediately — Arr Dashboard hashes it before storing, so the plaintext is shown only at generation or rotation. If you lose it, you must rotate to get a new one. (This is intentional: a database compromise no longer yields a usable webhook credential.)

Configure Sonarr or Radarr

In your Sonarr or Radarr instance:

  1. Go to Settings → Connect → Add → Webhook
  2. Set URL to: https://your-arr-dashboard-host/api/auto-tag/webhook/<INSTANCE_ID>
    • Replace <INSTANCE_ID> with the instance's UUID from Arr Dashboard's Settings → Services page (shown next to each instance)
  3. Set Method to POST
  4. Add a custom header Authorization: Bearer <your-webhook-secret>
  5. Enable the events you want to react to: On Grab, On Import / On Download, On Upgrade, On Rename, On Manual Interaction Required are all supported
  6. Click Test — the webhook should return a 200 status with {"status": "test", "message": "Test webhook received."}
  7. Save

Webhook Response Codes

Code Meaning
200 Event processed; check response body for tag application count
202 Event ignored (unsupported event type)
400 Bad payload, instance/payload type mismatch, or tag-update failure
401 Missing or invalid Bearer token
404 Instance not found, disabled, or not owned by the token's user

Failed webhooks do not retry. If a Connect call fails, the next scheduler tick (within 5 minutes) will catch the item.


Composition with Label Sync

Auto-Tagger writes tags to the source-side *arr (Sonarr or Radarr). To mirror those tags into Plex, Jellyfin, or Emby labels, pair with a Label Sync rule:

[Auto-Tagger]                       [Label Sync]
  Sonarr "Anime" tag      ──→        Plex "Anime" label
  Radarr "Premium" tag    ──→        Jellyfin "Premium" label

This split keeps the two engines simple. Auto-Tagger seeds the source-of-truth tag based on criteria. Label Sync propagates that tag to other services on its own cadence.

You can also use Auto-Tagger and Label Sync independently. Auto-Tagger is useful on its own if you only need *arr tags. Label Sync is useful on its own if you maintain tags manually in Sonarr/Radarr.


Operations

Status and Telemetry

Each rule row on the Auto-Tag page shows:

Field Meaning
Last Run At Timestamp of the last execution (scheduled or on-demand)
Last Run Status success (all matched items tagged), partial (some items failed), or failed (rule errored before any application)
Last Run Message Count summary like "Applied tag to 12 items across 2 instances" or a failure reason

Operator Environment Variables

Variable Purpose Required For
TRAKT_CLIENT_ID Trakt API app credential trakt_list_member rules

TMDb list-membership has no operator-side env requirement — it uses the user's TMDb v3 token from Settings → Account.

Public Routes

The inbound Connect webhook (POST /api/auto-tag/webhook/:instanceId) is mounted as a public route (no session cookie required). Authentication is the per-user Bearer token. All other Auto-Tagger endpoints require a session cookie like the rest of the app.


Best Practices

Start Small, Measure, Then Expand

Create one or two rules first, click Run now, and check the resulting tag count and lastRunMessage. Once you trust the rule's match set, enable additional rules.

Use Composite Rules to Avoid Tag Storms

A single broad rule like genre includes "Action" may match thousands of items on the first run. Composite rules (Genre is "Action" AND Year >= 2020 AND Custom Format Score > 500) keep the match set targeted.

Pair with Label Sync for Cross-Service Tagging

Don't write two rules — one in Auto-Tagger applying the Sonarr tag, one in Label Sync mirroring it to Plex — for two separate match sets. Use a single Auto-Tagger rule as the source of truth and let Label Sync propagate.

Rotate the Webhook Secret if Logs Show Unexpected Calls

Bearer tokens in Connect webhook headers are visible to anyone with access to the Sonarr/Radarr config. If you suspect leakage, rotate via the Webhook Configuration panel — the old secret stops authenticating immediately.

Use Exclude Tags as a Safety Net

Tag certain items in Sonarr/Radarr with a "no-auto-tag" tag and add that tag ID to every Auto-Tagger rule's Exclude Tags list. This gives you an opt-out per item without disabling the whole rule.


Troubleshooting

"Rule shows last_run_status: success but no tag was applied"

Causes:

  • Items already carried the tag (the tag is merged additively, so re-running on items already tagged is a no-op)
  • Rule criteria do not match any cached items
  • Rule's scope filters exclude the instances containing matching items

Solutions:

  1. Check lastRunMessage — it reports the application count, not just the match count
  2. Use the Library Cleanup#Explain Feature feature on a known item to see whether the rule would match
  3. Verify scope filters (service, instance, library) include the instances you expect

"Webhook returns 401 Invalid or missing webhook secret"

Causes:

  • The Bearer token in the Connect header is wrong or has been rotated
  • The Authorization header is missing or malformed (must be exactly Authorization: Bearer <token>)
  • The token is shorter than 16 characters (rejected outright as malformed)

Solutions:

  1. Open the Webhook Configuration panel and rotate the secret to get a fresh one
  2. Copy the plaintext value directly into the Sonarr/Radarr Webhook custom header
  3. Test with the Sonarr/Radarr "Test" button before saving

"Webhook returns 404 Instance not found or disabled"

Causes:

  • The <INSTANCE_ID> in the URL is wrong
  • The instance is disabled in Settings → Services
  • The instance belongs to a different user

Solutions:

  1. Check Settings → Services and copy the exact instance UUID
  2. Confirm the instance is enabled
  3. Confirm you generated the webhook secret as the same user that owns the instance

"Webhook returns 400 with payload type mismatch"

Cause: The Sonarr Connect webhook is pointing at a Radarr instance ID (or vice versa). The webhook payload contains a series object but the URL targets a Radarr instance.

Solution: Each instance gets its own webhook URL. Configure Sonarr Connect to target a Sonarr instance ID and Radarr Connect to target a Radarr instance ID.

"Trakt rules show no matches"

Causes:

  • TRAKT_CLIENT_ID env var not set in the API process — Trakt scheduler skips with a one-line log notice
  • Trakt PAT not configured in Settings → Account
  • List slug uses a wrong format (must be username/list-slug, e.g., trakt-official/oscar-winners)
  • Cache has not refreshed yet — first refresh takes up to 4 hours after the rule is created

Solutions:

  1. Confirm TRAKT_CLIENT_ID is in the API container's environment
  2. Add the Trakt PAT in Settings → Account
  3. Verify the list slug is in username/list-slug form
  4. Wait for the next 4-hour refresh, or restart the API to fire the startup tick

"TMDb rules show no matches"

Causes:

  • TMDb v3 token not configured in Settings → Account
  • List ID is wrong (TMDb list IDs are integers like 8068, not URL slugs)
  • The TMDb list is private (only public lists work in v1)

Solutions:

  1. Confirm the TMDb v3 token in Settings → Account is the same one used by Discover
  2. Use the numeric list ID from the TMDb URL
  3. Use a public list

"Rule is currently running" (HTTP 409 on Run Now)

Cause: A scheduler tick or a concurrent on-demand run is already executing this rule. The per-rule lock prevents racing.

Solution: Wait a moment and retry. Scheduled ticks complete in seconds for most rules.


API Reference

All endpoints require a session cookie except POST /api/auto-tag/webhook/:instanceId, which uses Bearer-token authentication.

List Rules

GET /api/auto-tag/rules

Returns the user's rules sorted by createdAt descending.

Create Rule

POST /api/auto-tag/rules
Content-Type: application/json

{
  "name": "Premium Anime",
  "enabled": true,
  "ruleType": "composite",
  "operator": "AND",
  "conditions": [
    { "ruleType": "genre", "parameters": { "operator": "includes_any", "genres": ["Anime"] } },
    { "ruleType": "audio_channels", "parameters": { "operator": "greater_than_or_equal", "channels": 6 } }
  ],
  "tagName": "premium-anime",
  "serviceFilter": ["sonarr"]
}

For single-criterion rules, set ruleType to the rule type and parameters to its parameters; omit operator and conditions.

Update Rule

PATCH /api/auto-tag/rules/:id
Content-Type: application/json

{ "enabled": false }

Any subset of fields can be updated. The validator re-evaluates the post-PATCH composite-vs-leaf shape and rejects mode mismatches.

Delete Rule

DELETE /api/auto-tag/rules/:id

Run Rule On Demand

POST /api/auto-tag/rules/:id/run

Returns the updated rule with lastRunAt, lastRunStatus, lastRunMessage. Returns HTTP 409 if the rule is already executing.

Get Webhook Configuration

GET /api/auto-tag/webhook-config

Returns { secret: "...", configured: true, freshlyGenerated: true } on the first-ever access (the secret is generated lazily). On subsequent accesses returns { secret: null, configured: true, freshlyGenerated: false } — the plaintext is not stored, only its SHA-256 hash.

Rotate Webhook Secret

POST /api/auto-tag/webhook-config/regenerate

Generates a new 256-bit secret, stores its hash, and returns the plaintext once. Invalidates the old secret immediately.

Inbound Connect Webhook

POST /api/auto-tag/webhook/:instanceId
Authorization: Bearer <your-webhook-secret>
Content-Type: application/json

(Sonarr or Radarr Connect payload)

Public endpoint — no session cookie required. The Bearer token resolves to the user; the path's instanceId must reference an enabled instance owned by that user.

Returns:

{
  "status": "ok",
  "message": "Applied 2 tags from 2 rules.",
  "tagsApplied": 2,
  "rulesEvaluated": 2
}

Clone this wiki locally