diff --git a/docs/usage.md b/docs/usage.md
index d0c749f8d..94ef5ae1b 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -92,6 +92,16 @@ and more. These areas allow you to view, add, edit, delete and manage permission
for these objects. You can also manage saved views, mail accounts, mail rules,
workflows and more from the management sections.
+### Nested Tags
+
+Paperless-ngx v2.19 introduces support for nested tags, allowing you to create a
+hierarchy of tags, which may be useful for organizing your documents. Tags can
+have a 'parent' tag, creating a tree-like structure, to a maximum depth of 5. When
+a tag is added to a document, all of its parent tags are also added automatically
+and similarly, when a tag is removed from a document, all of its child tags are
+also removed. Additionally, assigning a parent to an existing tag will automatically
+update all documents that have this tag assigned, adding the parent tag as well.
+
## Adding documents to Paperless-ngx
Once you've got Paperless setup, you need to start feeding documents
diff --git a/pyproject.toml b/pyproject.toml
index f761e17e1..a49e94f38 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,6 +36,7 @@ dependencies = [
"django-guardian~=3.1.2",
"django-multiselectfield~=1.0.1",
"django-soft-delete~=1.0.18",
+ "django-treenode>=0.23.2",
"djangorestframework~=3.16",
"djangorestframework-guardian~=0.4.0",
"drf-spectacular~=0.28",
diff --git a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html
index 1024560d3..0af48c58b 100644
--- a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html
@@ -12,6 +12,8 @@
+
+
@if (patternRequired) {
diff --git a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
index aa0572213..3855f9008 100644
--- a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
@@ -35,11 +35,16 @@ import { TextComponent } from '../../input/text/text.component'
],
})
export class TagEditDialogComponent extends EditDialogComponent {
+ tags: Tag[]
+
constructor() {
super()
this.service = inject(TagService)
this.userService = inject(UserService)
this.settingsService = inject(SettingsService)
+ this.service.listAll().subscribe((result) => {
+ this.tags = result.results
+ })
}
getCreateTitle() {
@@ -55,6 +60,7 @@ export class TagEditDialogComponent extends EditDialogComponent {
name: new FormControl(''),
color: new FormControl(randomColor()),
is_inbox_tag: new FormControl(false),
+ parent: new FormControl(null),
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
match: new FormControl(''),
is_insensitive: new FormControl(true),
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
index ce1137d2a..6107438da 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
@@ -114,6 +114,13 @@ export class FilterableDropdownSelectionModel {
b.id == NEGATIVE_NULL_FILTER_VALUE)
) {
return 1
+ }
+
+ // Preserve hierarchical order when provided (e.g., Tags)
+ const ao = (a as any)['orderIndex']
+ const bo = (b as any)['orderIndex']
+ if (ao !== undefined && bo !== undefined) {
+ return ao - bo
} else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) != ToggleableItemState.NotSelected
diff --git a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
index 1c7dad499..3951143ac 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
+++ b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
@@ -15,12 +15,17 @@
}
-