Automations
Automations are a rule-based engine that automatically applies actions to torrents based on conditions. Use them to manage speed limits, delete old torrents, organize with tags and categories, and more.
How Automations Work
Automations are evaluated in sort order (first match wins for exclusive actions like delete). Each rule can match torrents using a flexible query builder with nested conditions.
- Automatic - Background service scans torrents every 20 seconds
- Per-Rule Intervals - Each rule can have its own interval (minimum 60 seconds, default 15 minutes)
- Manual - Click "Apply Now" to trigger immediately (bypasses interval checks)
- Manual dry-run - Run "Dry-run now" from the workflow dialog or "Run dry-run now" from the workflow menu
- Debouncing - Same torrent won't be re-processed within 2 minutes
Query Builder
The query builder supports complex nested conditions with AND/OR groups. Drag conditions to reorder them.
Available Condition Fields
Identity Fields
| Field | Description |
|---|---|
| Name | Torrent display name (supports cross-category operators) |
| Hash | Info hash |
| Infohash v1 | BitTorrent v1 info hash |
| Infohash v2 | BitTorrent v2 info hash |
| Magnet URI | Magnet link for the torrent |
| Category | qBittorrent category |
| Tags | Set-based tag matching |
| State | Status filter (see State Values below) |
| Created By | Torrent creator metadata |
Path Fields
| Field | Description |
|---|---|
| Save Path | Download location |
| Content Path | Full path to content |
| Download Path | Session download path from qBittorrent |
Size Fields (bytes)
| Field | Description |
|---|---|
| Size | Selected file size |
| Total Size | Total torrent size |
| Completed | Completed bytes |
| Downloaded | Bytes downloaded |
| Downloaded (Session) | Downloaded in current session |
| Uploaded | Bytes uploaded |
| Uploaded (Session) | Uploaded in current session |
| Amount Left | Remaining bytes |
| Free Space | Free space on disk (configurable source - see Free Space Source) |
Duration Fields (seconds)
| Field | Description |
|---|---|
| Added Age | Time since added |
| Completion Age | Time since completed |
| Inactive Time | Time since last activity |
| Seen Complete Age | Time since torrent was last complete |
| ETA | Estimated time to completion |
| Reannounce In | Seconds until next announce |
| Seeding Time | Time spent seeding |
| Time Active | Total active time |
| Max Seeding Time | Configured max seeding time |
| Max Inactive Seeding Time | Configured max inactive seeding time |
| Seeding Time Limit | Torrent seeding time limit |
| Inactive Seeding Time Limit | Torrent inactive seeding time limit |
Progress Fields
| Field | Description |
|---|---|
| Ratio | Upload/download ratio |
| Ratio Limit | Configured ratio limit |
| Max Ratio | qBittorrent max ratio value |
| Progress | Download progress (0-100%) |
| Availability | Distributed copies available |
| Popularity | Swarm popularity metric |
Speed Fields (bytes/s)
| Field | Description |
|---|---|
| Download Speed | Current download speed |
| Upload Speed | Current upload speed |
| Download Limit | Configured download speed limit |
| Upload Limit | Configured upload speed limit |
Peer/Queue Fields
| Field | Description |
|---|---|
| Active Seeders | Currently connected seeders |
| Active Leechers | Currently connected leechers |
| Total Seeders | Tracker-reported seeders |
| Total Leechers | Tracker-reported leechers |
| Trackers Count | Number of trackers |
| Queue Priority | Torrent queue priority value |
Tracker/Status Fields
| Field | Description |
|---|---|
| Tracker | Primary tracker (URL, domain, or customization display name) |
| Trackers (All) | All tracker URLs/domains/display names for this torrent |
| Private | Boolean - is private tracker |
| Is Unregistered | Boolean - tracker reports unregistered |
| Comment | Torrent comment field |
Note: if you have Settings → Tracker Customizations configured, the Tracker condition can match the display name in addition to the raw URL/domain.
Mode Fields
| Field | Description |
|---|---|
| Auto-managed | Managed by automatic torrent management |
| First/Last Piece Priority | First and last pieces are prioritized |
| Force Start | Ignores queue limits and starts immediately |
| Sequential Download | Downloads pieces sequentially |
| Super Seeding | Super-seeding mode enabled |
Release/Grouping Fields
| Field | Description |
|---|---|
| Content Type | Derived from release name parsing (useful for grouping; may be empty) |
| Effective Name | Normalized title derived from release parsing (useful for grouping; may be empty) |
| Release Source | Parsed release specifier (e.g. WEBDL, WEBRIP, BLURAY; may be empty) |
| Release Resolution | Parsed release specifier (e.g. 1080p; may be empty) |
| Release Codec | Parsed release specifier (e.g. HEVC; may be empty) |
| Release HDR | Parsed release specifier (e.g. DV, HDR; may be empty) |
| Release Audio | Parsed release specifier (e.g. TrueHD; may be empty) |
| Release Channels | Parsed release specifier (e.g. 5.1; may be empty) |
| Release Group | Parsed release specifier (e.g. NTb; may be empty) |
| Group Size | Size of the selected group for this condition (requires grouping; see Grouping) |
| Is Grouped | Boolean - true when selected group size > 1 (requires grouping; see Grouping) |
Filesystem Fields
| Field | Description |
|---|---|
| Hardlink Scope | none, torrents_only, or outside_qbittorrent (requires local filesystem access) |
| Has Missing Files | Boolean - completed torrent has files missing on disk (requires local filesystem access) |
State Values
The State field matches these status buckets:
| State | Description |
|---|---|
downloading | Actively downloading |
uploading | Actively uploading |
completed | Download finished |
stopped | Paused by user |
active | Has transfer activity |
inactive | No current activity |
running | Not paused |
stalled | No peers available |
stalled_uploading | Stalled while uploading |
stalled_downloading | Stalled while downloading |
errored | Has errors |
tracker_down | Tracker unreachable |
checking | Verifying files |
checkingResumeData | Checking resume data |
moving | Moving files |
missingFiles | Files not found |
unregistered | Tracker reports unregistered |
Operators
String: equals, not equals, contains, not contains, starts with, ends with, matches regex
Numeric: =, !=, >, >=, <, <=, between
Boolean: is, is not
State: is, is not
Cross-Category (Name field only):
EXISTS_IN- Exact name match in target categoryCONTAINS_IN- Partial/normalized name match in target category
Regex Support
There are two ways to use regex in filter conditions:
The matches regex operator is a dedicated operator where the value is always treated as a regex pattern. The condition is true if the pattern matches anywhere in the field value.
The regex toggle (.* button) appears next to the value input on other string operators such as equals, contains, not contains, starts with, and ends with. When enabled, the value is treated as a regex pattern.
When the regex toggle is enabled, the selected operator's logic (negation, containment, prefix/suffix matching) is not applied. The condition becomes a simple regex match, equivalent to matches regex, regardless of which operator is selected in the dropdown.
This means not contains with the regex toggle enabled does not negate the match. It behaves the same as matches regex -- if the pattern is found, the condition evaluates to true.
To negate a regex match, use the NOT toggle (the IF / IF NOT button at the start of the condition row) together with the matches regex operator.
Full RE2 (Go regex) syntax is supported. Patterns are case-insensitive by default.
Regex can be used either by selecting matches regex or by enabling the Regex toggle for a condition:
- When regex is enabled, the condition checks whether the regex matches the field value.
not equalsandnot containsinvert the regex result (true only if the regex does not match).- Operators like
equals,contains,starts with, andends withare treated as regex match when regex is enabled. - Regex is not implicitly anchored: use
^and$if you want an exact/full-string match (example:^BHD$).
Field notes:
- Tracker: checked against multiple candidates (raw URL, extracted domain, and optional customization display name). Negative regex passes only if none of the candidates match.
- Tags: without regex, string operators are applied per-tag. With regex enabled, the regex is matched against the full raw tags string.
The UI validates patterns and shows helpful error messages for invalid regex.
Tag conditions
Each tag condition checks against a single value. The value field does not support comma-separated lists -- if you enter tag1, tag2, tag3 as the value, it will be treated as one literal string, not three separate tags.
Without regex enabled, tag operators (equals, not equals, contains, not contains) compare the condition value against each of the torrent's tags individually.
equals/not equals: exact tag membership (case-insensitive)contains/not contains: substring match per tag (case-insensitive)
contains is substring matchingTags contains tag1 will also match torrents tagged tag10. For exact tag membership, prefer equals / not equals with one condition per tag and combine them with an OR group.
Matching any of multiple tags
To check whether a torrent has any one of several tags, create an OR group with one condition per tag (exact match):
| # | Field | Operator | Value |
|---|---|---|---|
| 1 | Tags | equals | tag1 |
| 2 | Tags | equals | tag2 |
| 3 | Tags | equals | tag3 |
Group these with OR logic so the rule matches when at least one tag is present.
To exclude torrents that have any of several tags, create an AND group of not equals conditions (exact match):
| # | Field | Operator | Value |
|---|---|---|---|
| 1 | Tags | not equals | tag1 |
| 2 | Tags | not equals | tag2 |
| 3 | Tags | not equals | tag3 |
Group these with AND logic -- all three must be true, meaning none of those tags are present.
Using regex for tag matching
As an alternative to multiple conditions, you can use regex to match against several tags in a single condition. When regex is enabled, the pattern matches against the full raw tag string (e.g. cross-seed, noHL, racing) rather than checking each tag individually.
For example, to exclude torrents tagged with tag1 or tag2, use a single condition:
- Field:
Tags - Toggle:
IF NOT(negate the match) - Operator:
matches regex - Value:
(^|,\s*)(tag1|tag2)(\s*,|$)
This evaluates the regex against the raw tags string. The delimiter-aware pattern ensures tag1 does not match tag10. The IF NOT toggle then negates the result, so the condition is true only for torrents that do not have either tag.
Live impact preview and dry-run
Workflow editing includes immediate feedback for delete/category workflows:
- Live impact preview in the workflow dialog updates automatically as conditions/actions change.
- Shows current impacted count plus a small preview list of matching torrents.
- For category rules, the preview summary splits direct matches and cross-seed expansions.
You can also run dry-runs immediately without waiting for interval execution:
- Workflow dialog:
Dry-run now - Workflow list menu:
Run dry-run now
Dry-run executes the current workflow config as a simulation only and writes results to automation activity.
No-match behavior:
- Manual dry-runs still log a
dry_run_no_matchsummary row when nothing matches. - Scheduled dry-run rules do not log no-match rows (to avoid event noise).
Tracker Matching
This is sort of not needed, since you can already scope trackers outside the workflows. But its available either way.
| Pattern | Example | Matches |
|---|---|---|
| All | * | Every tracker |
| Exact | tracker.example.com | Only that domain |
| Glob | *.example.com | Subdomains |
| Suffix | .example.com | Domain and subdomains |
Separate multiple patterns with commas, semicolons, or pipes. All matching is case-insensitive.
Grouping
Grouping lets an automation treat "related torrents" as a single unit for:
- Group-aware conditions:
GROUP_SIZE,IS_GROUPED - Group expansion: apply an action to every torrent in the group (instead of only the matched torrent)
- Strict matching: grouped expansion runs only when all members satisfy the action conditions
Group-Scoped Condition Fields
GROUP_SIZE and IS_GROUPED can be scoped per condition row:
- Set
groupIdon eachGROUP_SIZE/IS_GROUPEDcondition if you want explicit per-row grouping. - If a grouped condition row has no
groupId, qui usesconditions.grouping.defaultGroupId. - If no default is configured, legacy unscoped grouped conditions fall back to
cross_seed_content_save_path.
This allows multiple grouped conditions in the same workflow, each using different grouping strategies.
Action Expansion
Some actions accept a groupId. When set, qui expands the action to all torrents in that group.
Group expansion semantics are strict:
- Every member in the expanded group must satisfy the action condition checks for that rule.
- If any member fails (or the group cannot be resolved), qui skips the entire grouped action.
- There is no "trigger-only fallback" when
groupIdis set.
Built-in group IDs:
cross_seed_content_path: same Content Path (normalized)cross_seed_content_save_path: same Content Path + Save Path (normalized)release_item: same Content Type + Effective Nametracker_release_item: same Tracker + Content Type + Effective Namehardlink_signature: same physical file set signature (requires local filesystem access)
Custom Groups (Advanced)
Rules can define custom groups via conditions.grouping.groups[] with:
id: stringkeys: list of key names combined to form the group key
Supported keys:
contentPath,savePath,effectiveName,contentType,trackerrlsSource,rlsResolution,rlsCodec,rlsHDR,rlsAudio,rlsChannels,rlsGrouphardlinkSignature
For content-path-based grouping where Content Path == Save Path (ambiguous), you can set:
ambiguousPolicy: "verify_overlap"(default for cross-seed groups) withminFileOverlapPercent(default90)ambiguousPolicy: "skip"
Actions
Actions can be combined (except Delete which must be standalone). Each action supports an optional condition override.
Speed Limits
Set upload and/or download limits. Each field supports these modes:
| Mode | Value | Description |
|---|---|---|
| No change | - | Don't modify this field |
| Unlimited | 0 | Remove speed limit (qBittorrent treats 0 as unlimited) |
| Custom | >0 | Specific limit in KiB/s or MiB/s |
Applied in batches for efficiency.
Share Limits
Set ratio limit and/or seeding time limit. Each field supports these modes:
| Mode | Value | Description |
|---|---|---|
| No change | - | Don't modify this field |
| Use global | -2 | Follow qBittorrent's global share settings |
| Unlimited | -1 | No limit for this field |
| Custom | >=0 | Specific value (ratio as decimal, time in minutes) |
Torrents stop seeding when any enabled limit is reached.
Pause
Pause matching torrents. Only pauses if not already stopped.
If a resume action is also present, last action wins.
Resume
Resume matching torrents. Only resumes if not already running.
If a pause action is also present, last action wins.
Force Recheck
Force recheck matching torrents.
- Triggers qBittorrent recheck for matched torrents.
- Can be combined with other actions.
- Supports optional condition override (like other actions).
Force Reannounce
Force reannounce matching torrents.
- Triggers immediate tracker reannounce for matched torrents.
- Can be combined with other actions.
- Supports optional condition override (like other actions).
Delete
Remove torrents from qBittorrent. Must be standalone - cannot combine with other actions.
| Mode | Description |
|---|---|
delete | Remove from client, keep files |
deleteWithFiles | Remove with files |
deleteWithFilesPreserveCrossSeeds | Remove files but preserve if cross-seeds detected |
deleteWithFilesIncludeCrossSeeds | Remove files and also delete all cross-seeded torrents sharing the same files |
Optional grouping (advanced):
Delete actions can specify a groupId to expand the deletion to all torrents in that group.
- For
delete(keep files): this is useful when you want "remove from client" to be cross-seed aware. - With
groupId, keep-files delete is strict all-or-none: if any group member does not satisfy rule conditions, nothing in that group is removed.
Include cross-seeds mode behavior:
When a torrent matches the rule, the system finds other torrents that point to the same downloaded files (cross-seeds/duplicates) and deletes them together. This is useful when you want to fully remove content and all its cross-seeded copies at once.
- Safe expansion: If qui can't safely confirm another torrent uses the same files, it won't be included in the deletion.
- Safety-first: If verification can't complete for any reason, the entire group is skipped rather than risking broken torrents.
- Preview: The delete preview shows all torrents that would be deleted, with cross-seeds marked.
Include hardlinked copies:
When "Include hardlinked copies" is enabled (only available with deleteWithFilesIncludeCrossSeeds mode), the system also deletes torrents that share the same underlying physical files via hardlinks, even if they have different Content Paths.
- Requires: Local Filesystem Access must be enabled on the instance.
- Safe scope: Only includes hardlinks that are fully contained within qBittorrent's torrent set. Never follows hardlinks to files outside qBittorrent (e.g., your media library).
- Preview: Hardlink-expanded torrents are marked as "Cross-seed (hardlinked)" in the preview.
- Free Space projection: When combined with Free Space conditions, hardlink groups are correctly deduplicated in the space projection - torrents sharing the same physical files are only counted once.
This is useful when you have hardlinked copies of content across different locations in qBittorrent and want to clean up all copies together.
Tag
Manage tags on torrents. You can add multiple Tag actions in one workflow.
| Mode | Description |
|---|---|
full | Add to matches, remove from non-matches (smart toggle) |
add | Only add to matches |
remove | Only remove from matches |
mode: remove removes tags from torrents that match the tag action condition. It does not remove from non-matches.
mode: full is evaluated within the rule's scope for that run (enabled rule, tracker pattern match, and run eligibility). It is not a client-wide sweep by itself.
Options:
- Managed / Replace in Client -
Managed(default) applies per-torrent add/remove diffs only.Replace in clientdeletes managed tags from qBittorrent first, then reapplies to current matches. - Use Tracker as Tag - Derive tag from tracker domain
- Use Display Name - Use tracker customization display name instead of raw domain
Behavior reference:
| Configuration | Behavior |
|---|---|
mode: full + Managed | Adds/removes tag for torrents this rule evaluates. No client-wide reset. |
mode: full + Replace in client | Deletes selected tag(s) client-wide first, then re-adds only current matches. |
If you see repeated activity like +tag=696 every run, that usually means Replace in client is enabled for that tag action.
Quick troubleshooting:
- Check logs for
automations: deleted managed tags from client before retagging. - In Automations UI, open enabled rules and verify whether "Replace in client" is enabled on any tag action.
- Confirm the activity entry's rule list matches the rule you expect.
Category
Move torrents to a different category.
Options:
- Include Cross-Seeds - Also move cross-seeds (matching ContentPath AND SavePath)
- Group ID (advanced) - Expand category changes to all torrents in the specified group (see Grouping). If set, this takes precedence over "Include Cross-Seeds".
- Strict grouped matching - With
groupId, category expansion applies only when all group members satisfy the category rule checks. - Block If Cross-Seed In Categories - Prevent move if another cross-seed is in protected categories
Move
Move torrents to a different path on disk. This is needed to move the contents if AutoTMM is not enabled.
Options:
- Group ID (advanced) - Expand moves to all torrents in the specified group (see Grouping). The move path is resolved for the matched torrent and then applied to the whole group.
- Strict grouped matching - With
groupId, move expansion is all-or-none: every member must satisfy the move rule checks. - Skip if cross-seeds don't match the rule's conditions - Skip the move if the torrent has cross-seeds that don't match the rule's conditions. This is ignored when Group ID is set.
Move path templates
The move path is evaluated as a Go template for each torrent. You can use a fixed path (e.g. /data/archive) or template actions to build paths from torrent properties.
Available template variables:
| Variable | Description |
|---|---|
.Name | Torrent display name |
.Hash | Info hash |
.Category | qBittorrent category |
.IsolationFolderName | Filesystem-safe folder name (hash or sanitized name) |
.Tracker | Tracker display name (when available from instance config), otherwise the tracker domain |
Template function:
| Function | Description |
|---|---|
sanitize | Makes a string safe for use as a path segment (removes invalid characters). Use for user-controlled values like names, e.g. {{ sanitize .Name }}. |
Examples:
- Fixed path (no template actions):
/data/archive - By category:
/data/{{.Category}}→ e.g./data/movies - By name (safe for paths):
/data/{{ sanitize .Name }} - By isolation folder:
/data/{{.IsolationFolderName}} - By tracker:
/data/{{.Tracker}}(when tracker display name is configured)
External Program
Run a pre-configured external program when torrents match the automation rule. Uses the same programs configured in Settings → External Programs.
| Field | Description |
|---|---|
| Program | Select from enabled external programs |
| Condition Override | Optional condition specific to this action |
Behavior:
- Executes asynchronously (fire-and-forget) to avoid blocking automation processing
- Can be combined with other actions (speed limits, share limits, pause, tag, category)
- Only enabled programs appear in the dropdown
- Activity is logged with rule name, torrent details, and success/failure status
The program must be enabled in Settings → External Programs to appear in the automation dropdown.
When multiple rules match the same torrent with External Program actions enabled, the last matching rule (by sort order) determines which program executes for that torrent. Only one program runs per torrent per automation cycle.
The program's executable path must be present in the application's allowlist. Programs that are disabled or have forbidden paths will not run—attempts are rejected and logged in the activity log with the rule name and torrent details.
Use cases:
- Run post-processing scripts when torrents complete
- Notify external systems (webhooks, notifications) when conditions are met
- Trigger media library scans after category changes
- Execute cleanup scripts for old or stalled torrents
Cross-Seed Awareness
Automations detect cross-seeded torrents (same content/files) and can handle them specially:
- Detection - Matches via ContentPath (and SavePath for category moves)
- Delete Rules:
- Use
deleteWithFilesPreserveCrossSeedsto keep files if cross-seeds exist - Use
deleteWithFilesIncludeCrossSeedsto delete matching torrents and all their cross-seeds together
- Use
- Category Rules - Enable "Include Cross-Seeds" to move related torrents together
- Blocking - Prevent category moves if cross-seeds are in protected categories
Hardlink Detection
The HARDLINK_SCOPE field lets automations distinguish between torrents whose files are hardlinked into an external library (Sonarr, Radarr, etc.) and torrents that exist only within qBittorrent. This is the foundation for safe "Remove Upgraded Torrents" automations.
How scope is determined
When an automation references HARDLINK_SCOPE, qui builds a hardlink index by calling Lstat() on every file of every torrent in qBittorrent. For each file it extracts:
- The inode and device ID — uniquely identifying the file on disk.
- The nlink count — the total number of hardlinks to that inode, as reported by the filesystem.
It then counts how many unique file paths across the entire qBittorrent torrent set point to each inode. The scope for each torrent is determined by comparing these two numbers:
| Scope | Condition | Meaning |
|---|---|---|
none | No file has nlink > 1 | No hardlinks detected. |
torrents_only | At least one file has nlink > 1, and no file has nlink > uniquePathCount | Hardlinks exist, but only between torrents in qBittorrent. No external library links. |
outside_qbittorrent | Any file has nlink > uniquePathCount | Something outside qBittorrent has hardlinked the file — typically a Sonarr/Radarr library import. |
HARDLINK_SCOPE only reflects hardlink metadata. Cross-seeds are detected separately (ContentPath matching), so a torrent can have HARDLINK_SCOPE = none and still be cross-seeded.
Unknown scope and safety behavior
If qui cannot Lstat() any file in a torrent — due to wrong paths, missing permissions, or inaccessible storage — that torrent receives no scope entry. All HARDLINK_SCOPE conditions evaluate to false for that torrent, regardless of the operator or value. This is a safety measure to prevent unintended deletions of torrents qui cannot fully inspect.
To diagnose this, enable debug logging and look for the "hardlink index built" log message, which reports an inaccessible count.
Docker volume requirements
For hardlink scope detection to work in Docker:
-
Paths must match exactly. qui must be able to read files at the same paths qBittorrent reports. If qBittorrent says a torrent's save path is
/data/torrents/radarr/, qui must be able to access/data/torrents/radarr/inside its container. -
Same underlying storage. Both containers must share the same host mount so that inode numbers are consistent. If qui and qBittorrent access the same files through different host mounts or different bind-mount configurations, inode numbers may not match.
-
Single mount, not subdivided. Mount the common parent directory rather than mounting subdirectories separately. For example, if your data lives under
/mnt/media/dataon the host:
services:
qui:
volumes:
- /home/user/docker/qui:/config
- /mnt/media/data:/data # single mount covering both torrents and library
Avoid mounting both /mnt/media/data/torrents:/data/torrents and /mnt/media/data:/data — the overlapping mounts can cause inconsistent inode visibility. Use a single mount at the common parent.
Filesystem limitations
Hardlink scope detection depends on the kernel reporting accurate nlink values in stat results. Some filesystems do not do this:
- FUSE-based filesystems (sshfs, mergerfs, rclone mount) may report
nlink = 1for all files regardless of actual hardlink count. - Some NAS appliance filesystems and overlay filesystems (overlayfs) may behave similarly.
- Network filesystems (NFS, CIFS/SMB) generally report accurate nlink values but behavior varies by server implementation.
On affected filesystems, every torrent appears to have scope none because nlink is always 1. There is no workaround within qui — this is a kernel/filesystem limitation. If you suspect this issue, run stat on a file you know is hardlinked and check the "Links" count.
Hardlinks also cannot span across different filesystems. If your torrent data and media library are on separate filesystems (or separate Docker volumes backed by different host paths), Sonarr/Radarr will copy instead of hardlink, and scope detection has nothing to detect.
Example: Remove Upgraded Torrents
This automation deletes torrents that have been replaced by an upgrade in Sonarr/Radarr. It targets torrents where the library hardlink no longer exists (the arr removed or re-linked it during upgrade), the torrent has been seeding for at least 7 days, and the category matches your arr categories.
Use HARDLINK_SCOPE with NOT_EQUAL to outside_qbittorrent rather than EQUAL to none. This way torrents with scope torrents_only (cross-seeded but not in a library) are also eligible for cleanup, while any torrent still linked into your media library is protected.
{
"name": "Remove Upgraded Torrents",
"trackerPattern": "*",
"trackerDomains": ["*"],
"conditions": {
"schemaVersion": "1",
"delete": {
"enabled": true,
"mode": "deleteWithFilesPreserveCrossSeeds",
"condition": {
"operator": "AND",
"conditions": [
{
"operator": "OR",
"conditions": [
{ "field": "CATEGORY", "operator": "EQUAL", "value": "radarr" },
{
"field": "CATEGORY",
"operator": "EQUAL",
"value": "radarr.cross"
},
{
"field": "CATEGORY",
"operator": "EQUAL",
"value": "tv-sonarr"
},
{
"field": "CATEGORY",
"operator": "EQUAL",
"value": "tv-sonarr.cross"
}
]
},
{
"field": "HARDLINK_SCOPE",
"operator": "NOT_EQUAL",
"value": "outside_qbittorrent"
},
{
"field": "SEEDING_TIME",
"operator": "GREATER_THAN_OR_EQUAL",
"value": "604800"
}
]
}
}
}
}
This works because when Sonarr/Radarr upgrades a release, the old library hardlink is removed. The old torrent's files then have nlink == 1 (scope none) or are only linked to other torrents (scope torrents_only). Either way, the scope is not outside_qbittorrent, so the automation matches and deletes the torrent after the seeding time requirement is met.
If the automation is matching torrents you expect to be protected, verify:
- qui can access all torrent files at the paths qBittorrent reports (check debug logs for inaccessible files).
- Your filesystem reports accurate nlink values (
stat <file>should show Links > 1 for hardlinked files). - Your Docker volume mounts do not overlap or subdivide the storage in a way that breaks inode consistency.
Missing Files Detection
The Has Missing Files field detects whether any files belonging to a completed torrent are missing from disk.
- Only checks completed torrents
- Returns
trueif any file is missing from its expected path
Requires "Local filesystem access" enabled on the instance.
Important Behavior
Settings Only Set Values
Automations apply settings but do not revert when disabled or deleted. If a rule sets upload limit to 1000 KiB/s, affected torrents keep that limit until manually changed or another rule applies a different value.
Efficient Updates
Only sends API calls when the torrent's current setting differs from the desired value. No-op updates are skipped.
Processing Order
- First match wins for delete actions (delete ends torrent processing, no further rules evaluated)
- Last rule wins for speed limits, share limits, category, and external program actions
- Accumulative for tag actions (tags are combined across matching rules)
Free Space Condition Behavior
When using the Free Space condition in delete rules, the system uses intelligent cumulative tracking:
- Oldest-first processing - Torrents are sorted by age (oldest first) for deterministic, predictable cleanup
- Cumulative space tracking - As each torrent is marked for deletion, its size is added to the projected free space (only when the delete mode actually frees disk bytes)
- Stop when satisfied - Once
Free Space + Space To Be Clearedexceeds your threshold, remaining torrents no longer match - Cross-seed aware - Cross-seeded torrents sharing the same files are only counted once to avoid overestimating freed space
Preview Views for Free Space Rules
When previewing a delete rule with a Free Space condition, a toggle allows switching between two views:
| View | Description |
|---|---|
| Needed to reach target | Shows only the torrents that would be removed right now to reach your free-space target. This is the default view and reflects actual delete behavior. |
| All eligible | Shows all torrents this rule could remove while free space is low. Useful for understanding the full scope of what the rule could potentially delete (may include cross-seeds that don't directly match filters). |
The toggle only appears for delete rules that use the Free Space condition.
Preview features:
- Path column - Shows the content path for each torrent with copy-to-clipboard support
- Export CSV - Download the full preview list (all pages) as a CSV file for external analysis
Cross-seed expansion in previews:
Cross-seeds are only expanded and displayed in the preview when using Remove with files (include cross-seeds) mode. In this mode, the preview shows all torrents that would be deleted together, with cross-seeds clearly marked. Other delete modes don't expand cross-seeds in the preview since they either preserve cross-seeds or don't consider them specially.
Delete mode affects space projection:
| Delete Mode | Space Added to Projection |
|---|---|
| Remove with files | Full torrent size |
| Preserve cross-seeds (no cross-seeds) | Full torrent size |
| Preserve cross-seeds (has cross-seeds) | 0 (files kept) |
How preserve cross-seeds works:
- Cross-seed detection checks if any other torrent shares the same Content Path at evaluation time (before any removals).
- If multiple torrents share the same files, removing them all in one rule run will still keep the files on disk. No disk space is freed from that group because each torrent sees the others as cross-seeds.
- Only non-cross-seeded torrents contribute to the free-space projection when using preserve mode.
Example: With 400GB free and a rule "Delete if Free Space < 500GB" using Remove with files, the system deletes oldest torrents until the cumulative freed space reaches 100GB, then stops. A 50GB torrent and its cross-seed (same files) only count as 50GB freed, not 100GB.
The UI and API prevent combining Remove (keep files) mode with Free Space conditions. Since keep-files doesn't free disk space, such a rule could never satisfy the free space target and would match indefinitely.
After removing files, qui waits ~5 minutes before running Free Space deletes again to allow qBittorrent to refresh its disk free space reading. The UI prevents selecting 1 minute intervals for Free Space delete rules.
Free Space Source
By default, Free Space uses qBittorrent's reported free space (based on its default download location). If you have multiple disks or want to manage a specific mount point, select "Path on server" and enter the path to that disk.
| Source | Description |
|---|---|
| Default (qBittorrent) | Uses qBittorrent's reported free space |
| Path on server | Reads free space from a specific filesystem path |
Path on server requires "Local Filesystem Access" to be enabled on the instance.
If you want to manage multiple disks, create one workflow per disk and set a different Path on server for each workflow.
On Windows, Path on server is not supported and Free Space always uses qBittorrent's reported free space. The UI disables the option and switches legacy workflows back to the default when opened.
Batching
Torrents are grouped by action value and sent to qBittorrent in batches of up to 50 hashes per API call.
Activity Log
All automation actions are logged with:
- Torrent name and hash
- Rule name and action type
- Outcome (success/failed) with reasons
- Action-specific details
Activity is retained for 7 days by default. View the log in the Automations section for each instance.
Example Rules
Delete Old Completed Torrents
Remove torrents completed over 30 days ago when disk space is low:
- Condition:
Completion On Age > 30 daysANDState is completedANDFree Space < 500GB - Action: Remove with files
Deletes oldest matching torrents first, stopping once enough space would be freed to exceed 500GB.
Speed Limit Private Trackers
Limit upload on private trackers:
- Tracker:
* - Condition:
Private is true - Action: Upload limit 10000 KiB/s
Tag Stalled Torrents
Auto-tag torrents with no activity:
- Tracker:
* - Condition:
Last Activity Age > 7 days - Action: Tag "stalled" (mode: add)
Clean Unregistered Torrents
Remove torrents the tracker no longer recognizes:
- Tracker:
* - Condition:
Is Unregistered is true - Action: Delete (keep files)
Maintain Minimum Free Space
Keep at least 200GB free by removing oldest completed torrents:
- Tracker:
* - Condition:
Free Space < 200GBANDState is completed - Action: Remove with files (preserve cross-seeds)
Removes torrents from the client, oldest first, until enough space is projected to be freed. Cross-seeded torrents keep their files on disk and don't contribute to the projection. If only cross-seeded torrents match, this may remove many torrents without freeing any disk space.
Clean Up Old Content with Cross-Seeds
Remove completed torrents and all their cross-seeded copies when they're old enough:
- Tracker:
* - Condition:
Completion On Age > 30 daysANDState is completed - Action: Remove with files (include cross-seeds)
When a torrent matches, any other torrents pointing to the same downloaded files are deleted together. Useful for complete cleanup when you no longer need any copy of the content.
Organize by Tracker
Move torrents to tracker-named categories:
- Tracker:
tracker.example.com - Action: Category "example" with "Include Cross-Seeds" enabled
Post-Processing on Completion
Run a script when torrents finish downloading:
- Tracker:
* - Condition:
State is completedANDProgress = 100 - Action: External Program "post-process.sh"
Notify on Stalled Torrents
Alert an external monitoring system when torrents stall:
- Tracker:
* - Condition:
State is stalledANDLast Activity Age > 24 hours - Action: External Program "send-alert" + Tag "stalled" (mode: add)