diff --git a/core/Command/SystemTag/Files/Add.php b/core/Command/SystemTag/Files/Add.php
new file mode 100644
index 0000000000000..d51c44f4b3f71
--- /dev/null
+++ b/core/Command/SystemTag/Files/Add.php
@@ -0,0 +1,90 @@
+setName('tag:files:add')
+ ->setDescription('Add a system-tag to a file or folder')
+ ->addArgument('target', InputArgument::REQUIRED, 'file id or path')
+ ->addArgument('tags', InputArgument::REQUIRED, 'Name of the tag(s) to add, comma separated')
+ ->addArgument('access', InputArgument::REQUIRED, 'access level of the tag (public, restricted or invisible)');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $targetInput = $input->getArgument('target');
+ $tagsInput = $input->getArgument('tags');
+
+ if ($tagsInput === '') {
+ $output->writeln('`tags` can\'t be empty');
+ return 3;
+ }
+
+ $tagNameArray = explode(',', $tagsInput);
+
+ $access = $input->getArgument('access');
+ switch ($access) {
+ case 'public':
+ $userVisible = true;
+ $userAssignable = true;
+ break;
+ case 'restricted':
+ $userVisible = true;
+ $userAssignable = false;
+ break;
+ case 'invisible':
+ $userVisible = false;
+ $userAssignable = false;
+ break;
+ default:
+ $output->writeln('`access` property is invalid');
+ return 1;
+ }
+
+ $targetNode = $this->fileUtils->getNode($targetInput);
+
+ if (! $targetNode) {
+ $output->writeln("file $targetInput not found");
+ return 1;
+ }
+
+ foreach ($tagNameArray as $tagName) {
+ try {
+ $tag = $this->systemTagManager->createTag($tagName, $userVisible, $userAssignable);
+ $output->writeln("$access tag named $tagName created.");
+ } catch (TagAlreadyExistsException $e) {
+ $tag = $this->systemTagManager->getTag($tagName, $userVisible, $userAssignable);
+ }
+
+ $this->systemTagObjectMapper->assignTags((string)$targetNode->getId(), 'files', $tag->getId());
+ $output->writeln("$access tag named $tagName added.");
+ }
+
+ return 0;
+ }
+}
diff --git a/core/Command/SystemTag/Files/Delete.php b/core/Command/SystemTag/Files/Delete.php
new file mode 100644
index 0000000000000..94fa849704831
--- /dev/null
+++ b/core/Command/SystemTag/Files/Delete.php
@@ -0,0 +1,88 @@
+setName('tag:files:delete')
+ ->setDescription('Delete a system-tag from a file or folder')
+ ->addArgument('target', InputArgument::REQUIRED, 'file id or path')
+ ->addArgument('tags', InputArgument::REQUIRED, 'Name of the tag(s) to delete, comma separated')
+ ->addArgument('access', InputArgument::REQUIRED, 'access level of the tag (public, restricted or invisible)');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $targetInput = $input->getArgument('target');
+ $tagsInput = $input->getArgument('tags');
+
+ if ($tagsInput === '') {
+ $output->writeln('`tags` can\'t be empty');
+ return 3;
+ }
+
+ $tagNameArray = explode(',', $tagsInput);
+
+ $access = $input->getArgument('access');
+ switch ($access) {
+ case 'public':
+ $userVisible = true;
+ $userAssignable = true;
+ break;
+ case 'restricted':
+ $userVisible = true;
+ $userAssignable = false;
+ break;
+ case 'invisible':
+ $userVisible = false;
+ $userAssignable = false;
+ break;
+ default:
+ $output->writeln('`access` property is invalid');
+ return 1;
+ }
+
+ $targetNode = $this->fileUtils->getNode($targetInput);
+
+ if (! $targetNode) {
+ $output->writeln("file $targetInput not found");
+ return 1;
+ }
+
+ foreach ($tagNameArray as $tagName) {
+ try {
+ $tag = $this->systemTagManager->getTag($tagName, $userVisible, $userAssignable);
+ $this->systemTagObjectMapper->unassignTags((string)$targetNode->getId(), 'files', $tag->getId());
+ $output->writeln("$access tag named $tagName removed.");
+ } catch (TagNotFoundException $e) {
+ $output->writeln("$access tag named $tagName does not exist!");
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/core/Command/SystemTag/Files/DeleteAll.php b/core/Command/SystemTag/Files/DeleteAll.php
new file mode 100644
index 0000000000000..8c83d02f155cb
--- /dev/null
+++ b/core/Command/SystemTag/Files/DeleteAll.php
@@ -0,0 +1,49 @@
+setName('tag:files:delete-all')
+ ->setDescription('Delete all system-tags from a file or folder')
+ ->addArgument('target', InputArgument::REQUIRED, 'file id or path');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $targetInput = $input->getArgument('target');
+ $targetNode = $this->fileUtils->getNode($targetInput);
+
+ if (! $targetNode) {
+ $output->writeln("file $targetInput not found");
+ return 1;
+ }
+
+ $tags = $this->systemTagObjectMapper->getTagIdsForObjects($targetNode->getId(), 'files');
+ $this->systemTagObjectMapper->unassignTags((string)$targetNode->getId(), 'files', $tags[$targetNode->getId()]);
+ $output->writeln('all tags removed.');
+
+ return 0;
+ }
+}
diff --git a/core/register_command.php b/core/register_command.php
index 5857c227fea67..8e6a0c9671dc2 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -135,6 +135,9 @@
$application->add(Server::get(Command\SystemTag\Delete::class));
$application->add(Server::get(Command\SystemTag\Add::class));
$application->add(Server::get(Command\SystemTag\Edit::class));
+ $application->add(Server::get(Command\SystemTag\Files\Delete::class));
+ $application->add(Server::get(Command\SystemTag\Files\DeleteAll::class));
+ $application->add(Server::get(Command\SystemTag\Files\Add::class));
$application->add(Server::get(Command\Security\ListCertificates::class));
$application->add(Server::get(Command\Security\ExportCertificates::class));
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 166d5805bdc5d..6252718f5d5f3 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -1225,6 +1225,9 @@
'OC\\Core\\Command\\SystemTag\\Add' => $baseDir . '/core/Command/SystemTag/Add.php',
'OC\\Core\\Command\\SystemTag\\Delete' => $baseDir . '/core/Command/SystemTag/Delete.php',
'OC\\Core\\Command\\SystemTag\\Edit' => $baseDir . '/core/Command/SystemTag/Edit.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\Add' => $baseDir . '/core/Command/SystemTag/Files/Add.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\Delete' => $baseDir . '/core/Command/SystemTag/Files/Delete.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\DeleteAll' => $baseDir . '/core/Command/SystemTag/Files/DeleteAll.php',
'OC\\Core\\Command\\SystemTag\\ListCommand' => $baseDir . '/core/Command/SystemTag/ListCommand.php',
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => $baseDir . '/core/Command/TaskProcessing/ListCommand.php',
'OC\\Core\\Command\\TaskProcessing\\Statistics' => $baseDir . '/core/Command/TaskProcessing/Statistics.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index b455077b958d7..2cda6c07ea070 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -1258,6 +1258,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\SystemTag\\Add' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Add.php',
'OC\\Core\\Command\\SystemTag\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Delete.php',
'OC\\Core\\Command\\SystemTag\\Edit' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Edit.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\Add' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Files/Add.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Files/Delete.php',
+ 'OC\\Core\\Command\\SystemTag\\Files\\DeleteAll' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Files/DeleteAll.php',
'OC\\Core\\Command\\SystemTag\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/SystemTag/ListCommand.php',
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/ListCommand.php',
'OC\\Core\\Command\\TaskProcessing\\Statistics' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/Statistics.php',