diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php
index c1635c9cde5a5985d2e91434eb9d1f4803c344cf..9ee32822bbda9f1fed823adc2ac701afdeb7073f 100644
--- a/apps/dav/lib/rootcollection.php
+++ b/apps/dav/lib/rootcollection.php
@@ -33,6 +33,13 @@ class RootCollection extends SimpleCollection {
 		$caldavBackend = new CalDavBackend($db);
 		$calendarRoot = new CalendarRoot($principalBackend, $caldavBackend, 'principals/users');
 		$calendarRoot->disableListing = $disableListing;
+		$systemTagCollection = new SystemTag\SystemTagsByIdCollection(
+			\OC::$server->getSystemTagManager()
+		);
+		$systemTagRelationsCollection = new SystemTag\SystemTagsRelationsCollection(
+			\OC::$server->getSystemTagManager(),
+			\OC::$server->getSystemTagObjectMapper()
+		);
 
 		$usersCardDavBackend = new CardDavBackend($db, $principalBackend);
 		$usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users');
@@ -51,6 +58,8 @@ class RootCollection extends SimpleCollection {
 				new SimpleCollection('addressbooks', [
 						$usersAddressBookRoot,
 						$systemAddressBookRoot]),
+				$systemTagCollection,
+				$systemTagRelationsCollection,
 		];
 
 		parent::__construct('root', $children);
diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php
index a031f2c442bbf60ac5848a6ed9ba788c35237ba6..ffcbb02db70a0beeeba70f02d4f42e2d6bf86b7e 100644
--- a/apps/dav/lib/server.php
+++ b/apps/dav/lib/server.php
@@ -60,6 +60,9 @@ class Server {
 		// addressbook plugins
 		$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
 
+		// system tags plugins
+		$this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(\OC::$server->getSystemTagManager()));
+
 		// Finder on OS X requires Class 2 WebDAV support (locking), since we do
 		// not provide locking we emulate it using a fake locking plugin.
 		if($request->isUserAgent(['/WebDAVFS/'])) {
diff --git a/apps/dav/lib/systemtag/systemtagmappingnode.php b/apps/dav/lib/systemtag/systemtagmappingnode.php
new file mode 100644
index 0000000000000000000000000000000000000000..cbf8542a4fd3c1ab18df5e40529641eea78ca804
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagmappingnode.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\NotFound;
+
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OCP\SystemTag\TagNotFoundException;
+
+/**
+ * Mapping node for system tag to object id
+ */
+class SystemTagMappingNode extends SystemTagNode {
+
+	/**
+	 * @var ISystemTagObjectMapper
+	 */
+	private $tagMapper;
+
+	/**
+	 * @var string
+	 */
+	private $objectId;
+
+	/**
+	 * @var string
+	 */
+	private $objectType;
+
+	/**
+	 * Sets up the node, expects a full path name
+	 *
+	 * @param ISystemTag $tag system tag
+	 * @param string $objectId
+	 * @param string $objectType
+	 * @param ISystemTagManager $tagManager
+	 * @param ISystemTagObjectMapper $tagMapper
+	 */
+	public function __construct(
+		ISystemTag $tag,
+		$objectId,
+		$objectType,
+		ISystemTagManager $tagManager,
+		ISystemTagObjectMapper $tagMapper
+	) {
+		$this->objectId = $objectId;
+		$this->objectType = $objectType;
+		$this->tagMapper = $tagMapper;
+		parent::__construct($tag, $tagManager);
+	}
+
+	/**
+	 * Returns the object id of the relationship
+	 *
+	 * @return string object id
+	 */
+	public function getObjectId() {
+		return $this->objectId;
+	}
+
+	/**
+	 * Returns the object type of the relationship
+	 *
+	 * @return string object type
+	 */
+	public function getObjectType() {
+		return $this->objectType;
+	}
+
+	/**
+	 * Delete tag to object association
+	 */
+	public function delete() {
+		try {
+			$this->tagMapper->unassignTags($this->objectId, $this->objectType, $this->tag->getId());
+		} catch (TagNotFoundException $e) {
+			// can happen if concurrent deletion occurred
+			throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
+		}
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagnode.php b/apps/dav/lib/systemtag/systemtagnode.php
new file mode 100644
index 0000000000000000000000000000000000000000..7ab4a8a14f442e3cbcda2cb2d412de61323e254e
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagnode.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAV\Exception\Conflict;
+
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+/**
+ * DAV node representing a system tag, with the name being the tag id.
+ */
+class SystemTagNode implements \Sabre\DAV\INode {
+
+	/**
+	 * @var ISystemTag
+	 */
+	protected $tag;
+
+	/**
+	 * @var ISystemTagManager
+	 */
+	protected $tagManager;
+
+	/**
+	 * Sets up the node, expects a full path name
+	 *
+	 * @param ISystemTag $tag system tag
+	 * @param ISystemTagManager $tagManager
+	 */
+	public function __construct(ISystemTag $tag, ISystemTagManager $tagManager) {
+		$this->tag = $tag;
+		$this->tagManager = $tagManager;
+	}
+
+	/**
+	 *  Returns the id of the tag
+	 *
+	 * @return string
+	 */
+	public function getName() {
+		return $this->tag->getId();
+	}
+
+	/**
+	 * Returns the system tag represented by this node
+	 *
+	 * @return ISystemTag system tag
+	 */
+	public function getSystemTag() {
+		return $this->tag;
+	}
+
+	/**
+	 * Renames the node
+	 *
+	 * @param string $name The new name
+	 *
+	 * @throws MethodNotAllowed not allowed to rename node
+	 */
+	public function setName($name) {
+		throw new MethodNotAllowed();
+	}
+
+	/**
+	 * Update tag
+	 *
+	 * @param string $name new tag name
+	 * @param bool $userVisible user visible
+	 * @param bool $userAssignable user assignable
+	 * @throws NotFound whenever the given tag id does not exist
+	 * @throws Conflict whenever a tag already exists with the given attributes
+	 */
+	public function update($name, $userVisible, $userAssignable) {
+		try {
+			$this->tagManager->updateTag($this->tag->getId(), $name, $userVisible, $userAssignable);
+		} catch (TagNotFoundException $e) {
+			throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist');
+		} catch (TagAlreadyExistsException $e) {
+			throw new Conflict(
+				'Tag with the properties "' . $name . '", ' .
+				$userVisible . ', ' . $userAssignable . ' already exists'
+			);
+		}
+	}
+
+	/**
+	 * Returns null, not supported
+	 *
+	 */
+	public function getLastModified() {
+		return null;
+	}
+
+	public function delete() {
+		try {
+			$this->tagManager->deleteTags($this->tag->getId());
+		} catch (TagNotFoundException $e) {
+			// can happen if concurrent deletion occurred
+			throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
+		}
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagplugin.php b/apps/dav/lib/systemtag/systemtagplugin.php
new file mode 100644
index 0000000000000000000000000000000000000000..51db0632549cfc78d57331b21b9b557e8960b5e8
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagplugin.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\PropPatch;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\UnsupportedMediaType;
+use Sabre\DAV\Exception\Conflict;
+
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\TagAlreadyExistsException;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * Sabre plugin to handle system tags:
+ *
+ * - makes it possible to create new tags with POST operation
+ * - get/set Webdav properties for tags
+ *
+ */
+class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
+
+	// namespace
+	const NS_OWNCLOUD = 'http://owncloud.org/ns';
+	const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
+	const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
+	const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
+	const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
+
+	/**
+	 * @var \Sabre\DAV\Server $server
+	 */
+	private $server;
+
+	/**
+	 * @var ISystemTagManager
+	 */
+	protected $tagManager;
+
+	/**
+	 * System tags plugin
+	 *
+	 * @param ISystemTagManager $tagManager tag manager
+	 */
+	public function __construct(ISystemTagManager $tagManager) {
+		$this->tagManager = $tagManager;
+	}
+
+	/**
+	 * This initializes the plugin.
+	 *
+	 * This function is called by \Sabre\DAV\Server, after
+	 * addPlugin is called.
+	 *
+	 * This method should set up the required event subscriptions.
+	 *
+	 * @param \Sabre\DAV\Server $server
+	 * @return void
+	 */
+	public function initialize(\Sabre\DAV\Server $server) {
+
+		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
+
+		$server->protectedProperties[] = self::ID_PROPERTYNAME;
+
+		$server->on('propFind', array($this, 'handleGetProperties'));
+		$server->on('propPatch', array($this, 'handleUpdateProperties'));
+		$server->on('method:POST', [$this, 'httpPost']);
+
+		$this->server = $server;
+	}
+
+	/**
+	 * POST operation on system tag collections
+	 *
+	 * @param RequestInterface $request request object
+	 * @param ResponseInterface $response response object
+	 * @return null|false
+	 */
+	public function httpPost(RequestInterface $request, ResponseInterface $response) {
+		$path = $request->getPath();
+
+		// Making sure the node exists
+		try {
+			$node = $this->server->tree->getNodeForPath($path);
+		} catch (NotFound $e) {
+			return null;
+		}
+
+		if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
+			$data = $request->getBodyAsString();
+
+			$tag = $this->createTag($data, $request->getHeader('Content-Type'));
+
+			if ($node instanceof SystemTagsObjectMappingCollection) {
+				// also add to collection
+				$node->createFile($tag->getId());
+				$url = $request->getBaseUrl() . 'systemtags/';
+			} else {
+				$url = $request->getUrl();
+			}
+
+			if ($url[strlen($url) - 1] !== '/') {
+				$url .= '/';
+			}
+
+			$response->setHeader('Location', $url . $tag->getId());
+
+			// created
+			$response->setStatus(201);
+			return false;
+		}
+	}
+
+	/**
+	 * Creates a new tag
+	 *
+	 * @param string $data JSON encoded string containing the properties of the tag to create
+	 * @param string $contentType content type of the data
+	 * @return ISystemTag newly created system tag
+	 *
+	 * @throws BadRequest if a field was missing
+	 * @throws Conflict if a tag with the same properties already exists
+	 * @throws UnsupportedMediaType if the content type is not supported
+	 */
+	private function createTag($data, $contentType = 'application/json') {
+		if ($contentType === 'application/json') {
+			$data = json_decode($data, true);
+		} else {
+			throw new UnsupportedMediaType();
+		}
+
+		if (!isset($data['name'])) {
+			throw new BadRequest('Missing "name" attribute');
+		}
+
+		$tagName = $data['name'];
+		$userVisible = true;
+		$userAssignable = true;
+
+		if (isset($data['userVisible'])) {
+			$userVisible = (bool)$data['userVisible'];
+		}
+
+		if (isset($data['userAssignable'])) {
+			$userAssignable = (bool)$data['userAssignable'];
+		}
+		try {
+			return $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
+		} catch (TagAlreadyExistsException $e) {
+			throw new Conflict('Tag already exists', 0, $e);
+		}
+	}
+
+
+	/**
+	 * Retrieves system tag properties
+	 *
+	 * @param PropFind $propFind
+	 * @param \Sabre\DAV\INode $node
+	 */
+	public function handleGetProperties(
+		PropFind $propFind,
+		\Sabre\DAV\INode $node
+	) {
+		if (!($node instanceof SystemTagNode)) {
+			return;
+		}
+
+		$propFind->handle(self::ID_PROPERTYNAME, function() use ($node) {
+			return $node->getSystemTag()->getId();
+		});
+
+		$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function() use ($node) {
+			return $node->getSystemTag()->getName();
+		});
+
+		$propFind->handle(self::USERVISIBLE_PROPERTYNAME, function() use ($node) {
+			return (int)$node->getSystemTag()->isUserVisible();
+		});
+
+		$propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function() use ($node) {
+			return (int)$node->getSystemTag()->isUserAssignable();
+		});
+	}
+
+	/**
+	 * Updates tag attributes
+	 *
+	 * @param string $path
+	 * @param PropPatch $propPatch
+	 *
+	 * @return void
+	 */
+	public function handleUpdateProperties($path, PropPatch $propPatch) {
+		$propPatch->handle([
+			self::DISPLAYNAME_PROPERTYNAME,
+			self::USERVISIBLE_PROPERTYNAME,
+			self::USERASSIGNABLE_PROPERTYNAME,
+		], function($props) use ($path) {
+			$node = $this->server->tree->getNodeForPath($path);
+			if (!($node instanceof SystemTagNode)) {
+				return;
+			}
+
+			$tag = $node->getSystemTag();
+			$name = $tag->getName();
+			$userVisible = $tag->isUserVisible();
+			$userAssignable = $tag->isUserAssignable();
+
+			if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
+				$name = $props[self::DISPLAYNAME_PROPERTYNAME];
+			}
+
+			if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
+				$userVisible = (bool)$props[self::USERVISIBLE_PROPERTYNAME];
+			}
+
+			if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
+				$userAssignable = (bool)$props[self::USERASSIGNABLE_PROPERTYNAME];
+			}
+
+			$node->update($name, $userVisible, $userAssignable);
+			return true;
+		});
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagsbyidcollection.php b/apps/dav/lib/systemtag/systemtagsbyidcollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7b7b6d0acc387a7bc3a5c8f272b7ab288c5782b
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagsbyidcollection.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\ICollection;
+
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\TagNotFoundException;
+
+class SystemTagsByIdCollection implements ICollection {
+
+	/**
+	 * @var ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * SystemTagsByIdCollection constructor.
+	 *
+	 * @param ISystemTagManager $tagManager
+	 */
+	public function __construct($tagManager) {
+		$this->tagManager = $tagManager;
+	}
+
+	function createFile($name, $data = null) {
+		throw new Forbidden('Cannot create tags by id');
+	}
+
+	function createDirectory($name) {
+		throw new Forbidden('Permission denied to create collections');
+	}
+
+	function getChild($name) {
+		try {
+			$tags = $this->tagManager->getTagsByIds([$name]);
+			return $this->makeNode(current($tags));
+		} catch (\InvalidArgumentException $e) {
+			throw new BadRequest('Invalid tag id', 0, $e);
+		} catch (TagNotFoundException $e) {
+			throw new NotFound('Tag with id ' . $name . ' not found', 0, $e);
+		}
+	}
+
+	function getChildren() {
+		$tags = $this->tagManager->getAllTags(true);
+		return array_map(function($tag) {
+			return $this->makeNode($tag);
+		}, $tags);
+	}
+
+	function childExists($name) {
+		try {
+			$this->tagManager->getTagsByIds([$name]);
+			return true;
+		} catch (\InvalidArgumentException $e) {
+			throw new BadRequest('Invalid tag id', 0, $e);
+		} catch (TagNotFoundException $e) {
+			return false;
+		}
+	}
+
+	function delete() {
+		throw new Forbidden('Permission denied to delete this collection');
+	}
+
+	function getName() {
+		return 'systemtags';
+	}
+
+	function setName($name) {
+		throw new Forbidden('Permission denied to rename this collection');
+	}
+
+	/**
+	 * Returns the last modification time, as a unix timestamp
+	 *
+	 * @return int
+	 */
+	function getLastModified() {
+		return null;
+	}
+
+	/**
+	 * Create a sabre node for the given system tag
+	 *
+	 * @param ISystemTag $tag
+	 *
+	 * @return SystemTagNode
+	 */
+	private function makeNode(ISystemTag $tag) {
+		return new SystemTagNode($tag, $this->tagManager);
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php b/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..89e8620614b88a98a6c924c5a6e5e1d79aa5c701
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagsobjectmappingcollection.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\PreconditionFailed;
+use Sabre\DAV\ICollection;
+
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\TagNotFoundException;
+
+/**
+ * Collection containing tags by object id
+ */
+class SystemTagsObjectMappingCollection implements ICollection {
+
+	/**
+	 * @var string
+	 */
+	private $objectId;
+
+	/**
+	 * @var string
+	 */
+	private $objectType;
+
+	/**
+	 * @var ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var ISystemTagObjectMapper
+	 */
+	private $tagMapper;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $objectId object id
+	 * @param string $objectType object type
+	 * @param ISystemTagManager $tagManager
+	 * @param ISystemTagObjectMapper $tagMapper
+	 */
+	public function __construct($objectId, $objectType, $tagManager, $tagMapper) {
+		$this->tagManager = $tagManager;
+		$this->tagMapper = $tagMapper;
+		$this->objectId = $objectId;
+		$this->objectType = $objectType;
+	}
+
+	function createFile($tagId, $data = null) {
+		try {
+			$this->tagMapper->assignTags($this->objectId, $this->objectType, $tagId);
+		} catch (TagNotFoundException $e) {
+			throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign');
+		}
+	}
+
+	function createDirectory($name) {
+		throw new Forbidden('Permission denied to create collections');
+	}
+
+	function getChild($tagId) {
+		try {
+			if ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)) {
+				$tag = $this->tagManager->getTagsByIds([$tagId]);
+				return $this->makeNode(current($tag));
+			}
+			throw new NotFound('Tag with id ' . $tagId . ' not present for object ' . $this->objectId);
+		} catch (\InvalidArgumentException $e) {
+			throw new BadRequest('Invalid tag id', 0, $e);
+		} catch (TagNotFoundException $e) {
+			throw new NotFound('Tag with id ' . $tagId . ' not found', 0, $e);
+		}
+	}
+
+	function getChildren() {
+		$tagIds = current($this->tagMapper->getTagIdsForObjects([$this->objectId], $this->objectType));
+		if (empty($tagIds)) {
+			return [];
+		}
+		$tags = $this->tagManager->getTagsByIds($tagIds);
+		return array_values(array_map(function($tag) {
+			return $this->makeNode($tag);
+		}, $tags));
+	}
+
+	function childExists($tagId) {
+		try {
+			return ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true));
+		} catch (\InvalidArgumentException $e) {
+			throw new BadRequest('Invalid tag id', 0, $e);
+		} catch (TagNotFoundException $e) {
+			return false;
+		}
+	}
+
+	function delete() {
+		throw new Forbidden('Permission denied to delete this collection');
+	}
+
+	function getName() {
+		return $this->objectId;
+	}
+
+	function setName($name) {
+		throw new Forbidden('Permission denied to rename this collection');
+	}
+
+	/**
+	 * Returns the last modification time, as a unix timestamp
+	 *
+	 * @return int
+	 */
+	function getLastModified() {
+		return null;
+	}
+
+	/**
+	 * Create a sabre node for the mapping of the 
+	 * given system tag to the collection's object
+	 *
+	 * @param ISystemTag $tag
+	 *
+	 * @return SystemTagNode
+	 */
+	private function makeNode(ISystemTag $tag) {
+		return new SystemTagMappingNode(
+			$tag,
+			$this->objectId,
+			$this->objectType,
+			$this->tagManager,
+			$this->tagMapper
+		);
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php b/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..e544073613f3b099d75890a313f941ba1b9ca6d5
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagsobjecttypecollection.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAV\ICollection;
+
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
+
+/**
+ * Collection containing object ids by object type
+ */
+class SystemTagsObjectTypeCollection implements ICollection {
+
+	/**
+	 * @var string
+	 */
+	private $objectType;
+
+	/**
+	 * @var ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var ISystemTagObjectMapper
+	 */
+	private $tagMapper;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $objectType object type
+	 * @param ISystemTagManager $tagManager
+	 * @param ISystemTagObjectMapper $tagMapper
+	 */
+	public function __construct($objectType, $tagManager, $tagMapper) {
+		$this->tagManager = $tagManager;
+		$this->tagMapper = $tagMapper;
+		$this->objectType = $objectType;
+	}
+
+	function createFile($name, $data = null) {
+		throw new Forbidden('Permission denied to create nodes');
+	}
+
+	function createDirectory($name) {
+		throw new Forbidden('Permission denied to create collections');
+	}
+
+	function getChild($objectId) {
+		return new SystemTagsObjectMappingCollection(
+			$objectId,
+			$this->objectType,
+			$this->tagManager,
+			$this->tagMapper
+		);
+	}
+
+	function getChildren() {
+		// do not list object ids
+		throw new MethodNotAllowed();
+	}
+
+	function childExists($name) {
+		return true;
+	}
+
+	function delete() {
+		throw new Forbidden('Permission denied to delete this collection');
+	}
+
+	function getName() {
+		return $this->objectType;
+	}
+
+	function setName($name) {
+		throw new Forbidden('Permission denied to rename this collection');
+	}
+
+	/**
+	 * Returns the last modification time, as a unix timestamp
+	 *
+	 * @return int
+	 */
+	function getLastModified() {
+		return null;
+	}
+}
diff --git a/apps/dav/lib/systemtag/systemtagsrelationscollection.php b/apps/dav/lib/systemtag/systemtagsrelationscollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..44069bca02c9b0087412cbacf18606cd09e7be06
--- /dev/null
+++ b/apps/dav/lib/systemtag/systemtagsrelationscollection.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\SystemTag;
+
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\SimpleCollection;
+
+class SystemTagsRelationsCollection extends SimpleCollection {
+
+	/**
+	 * SystemTagsRelationsCollection constructor.
+	 *
+	 * @param ISystemTagManager $tagManager
+	 * @param ISystemTagObjectMapper $tagMapper
+	 */
+	public function __construct($tagManager, $tagMapper) {
+		$children = [
+			new SystemTagsObjectTypeCollection('files', $tagManager, $tagMapper),
+		];
+
+		parent::__construct('root', $children);
+	}
+
+	function getName() {
+		return 'systemtags-relations';
+	}
+
+	function setName($name) {
+		throw new Forbidden('Permission denied to rename this collection');
+	}
+
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagmappingnode.php b/apps/dav/tests/unit/systemtag/systemtagmappingnode.php
new file mode 100644
index 0000000000000000000000000000000000000000..849f7c2fa54f68bf77d3ca172f5b6c3e4c0a261b
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagmappingnode.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAV\Exception\Conflict;
+
+use OC\SystemTag\SystemTag;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+class SystemTagMappingNode extends SystemTagNode {
+
+	/**
+	 * @var \OCA\DAV\SystemTag\SystemTagMappingNode
+	 */
+	private $node;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagObjectMapper
+	 */
+	private $tagMapper;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTag
+	 */
+	private $tag;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->tag = new SystemTag(1, 'Test', true, false);
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+		$this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper');
+
+		$this->node = new \OCA\DAV\SystemTag\SystemTagMappingNode(
+			$this->tag,
+			123,
+			'files',
+			$this->tagManager,
+			$this->tagMapper
+		);
+	}
+
+	public function testGetters() {
+		parent::testGetters();
+		$this->assertEquals(123, $this->node->getObjectId());
+		$this->assertEquals('files', $this->node->getObjectType());
+	}
+
+	public function testDeleteTag() {
+		$this->tagManager->expects($this->never())
+			->method('deleteTags');
+		$this->tagMapper->expects($this->once())
+			->method('unassignTags')
+			->with(123, 'files', 1);
+
+		$this->node->delete();
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testDeleteTagNotFound() {
+		$this->tagMapper->expects($this->once())
+			->method('unassignTags')
+			->with(123, 'files', 1)
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->node->delete();
+	}
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagnode.php b/apps/dav/tests/unit/systemtag/systemtagnode.php
new file mode 100644
index 0000000000000000000000000000000000000000..a43dda3025d245f2f5f17e2ec8ce90616286a36b
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagnode.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAV\Exception\Conflict;
+
+use OC\SystemTag\SystemTag;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+class SystemTagNode extends \Test\TestCase {
+
+	/**
+	 * @var \OCA\DAV\SystemTag\SystemTagNode
+	 */
+	private $node;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTag
+	 */
+	private $tag;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->tag = new SystemTag(1, 'Test', true, false);
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+
+		$this->node = new \OCA\DAV\SystemTag\SystemTagNode($this->tag, $this->tagManager);
+	}
+
+	public function testGetters() {
+		$this->assertEquals('1', $this->node->getName());
+		$this->assertEquals($this->tag, $this->node->getSystemTag());
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\MethodNotAllowed
+	 */
+	public function testSetName() {
+		$this->node->setName('2');
+	}
+
+	public function testUpdateTag() {
+		$this->tagManager->expects($this->once())
+			->method('updateTag')
+			->with(1, 'Renamed', false, true);
+		$this->node->update('Renamed', false, true);
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Conflict
+	 */
+	public function testUpdateTagAlreadyExists() {
+		$this->tagManager->expects($this->once())
+			->method('updateTag')
+			->with(1, 'Renamed', false, true)
+			->will($this->throwException(new TagAlreadyExistsException()));
+		$this->node->update('Renamed', false, true);
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testUpdateTagNotFound() {
+		$this->tagManager->expects($this->once())
+			->method('updateTag')
+			->with(1, 'Renamed', false, true)
+			->will($this->throwException(new TagNotFoundException()));
+		$this->node->update('Renamed', false, true);
+	}
+
+	public function testDeleteTag() {
+		$this->tagManager->expects($this->once())
+			->method('deleteTags')
+			->with('1');
+		$this->node->delete();
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testDeleteTagNotFound() {
+		$this->tagManager->expects($this->once())
+			->method('deleteTags')
+			->with('1')
+			->will($this->throwException(new TagNotFoundException()));
+		$this->node->delete();
+	}
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagplugin.php b/apps/dav/tests/unit/systemtag/systemtagplugin.php
new file mode 100644
index 0000000000000000000000000000000000000000..48c9aa69f7b5d07f8c0a2cbbfafbf49378566d0e
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagplugin.php
@@ -0,0 +1,308 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+use OC\SystemTag\SystemTag;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+class SystemTagPlugin extends \Test\TestCase {
+
+	const ID_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::ID_PROPERTYNAME;
+	const DISPLAYNAME_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::DISPLAYNAME_PROPERTYNAME;
+	const USERVISIBLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERVISIBLE_PROPERTYNAME;
+	const USERASSIGNABLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME;
+
+	/**
+	 * @var \Sabre\DAV\Server
+	 */
+	private $server;
+
+	/**
+	 * @var \Sabre\DAV\Tree
+	 */
+	private $tree;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var \OCA\DAV\Connector\Sabre\TagsPlugin
+	 */
+	private $plugin;
+
+	public function setUp() {
+		parent::setUp();
+		$this->tree = $this->getMockBuilder('\Sabre\DAV\Tree')
+			->disableOriginalConstructor()
+			->getMock();
+
+		$this->server = new \Sabre\DAV\Server($this->tree);
+
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+
+		$this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin($this->tagManager);
+		$this->plugin->initialize($this->server);
+	}
+
+	public function testGetProperties() {
+		$systemTag = new SystemTag(1, 'Test', true, true);
+		$requestedProperties = [
+			self::ID_PROPERTYNAME,
+			self::DISPLAYNAME_PROPERTYNAME,
+			self::USERVISIBLE_PROPERTYNAME,
+			self::USERASSIGNABLE_PROPERTYNAME
+		];
+		$expectedProperties = [
+			200 => [
+				self::ID_PROPERTYNAME => '1',
+				self::DISPLAYNAME_PROPERTYNAME => 'Test',
+				self::USERVISIBLE_PROPERTYNAME => 1,
+				self::USERASSIGNABLE_PROPERTYNAME => 1,
+			]
+		];
+
+		$node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagNode')
+			->disableOriginalConstructor()
+			->getMock();
+		$node->expects($this->any())
+			->method('getSystemTag')
+			->will($this->returnValue($systemTag));
+
+		$this->tree->expects($this->any())
+			->method('getNodeForPath')
+			->with('/systemtag/1')
+			->will($this->returnValue($node));
+
+		$propFind = new \Sabre\DAV\PropFind(
+			'/systemtag/1',
+			$requestedProperties,
+			0
+		);
+
+		$this->plugin->handleGetProperties(
+			$propFind,
+			$node
+		);
+
+		$result = $propFind->getResultForMultiStatus();
+
+		$this->assertEmpty($result[404]);
+		unset($result[404]);
+		$this->assertEquals($expectedProperties, $result);
+	}
+
+	public function testUpdateProperties() {
+		$systemTag = new SystemTag(1, 'Test', true, false);
+		$node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagNode')
+			->disableOriginalConstructor()
+			->getMock();
+		$node->expects($this->any())
+			->method('getSystemTag')
+			->will($this->returnValue($systemTag));
+
+		$this->tree->expects($this->any())
+			->method('getNodeForPath')
+			->with('/systemtag/1')
+			->will($this->returnValue($node));
+
+		$node->expects($this->once())
+			->method('update')
+			->with('Test changed', false, true);
+
+		// properties to set
+		$propPatch = new \Sabre\DAV\PropPatch(array(
+			self::DISPLAYNAME_PROPERTYNAME => 'Test changed',
+			self::USERVISIBLE_PROPERTYNAME => 0,
+			self::USERASSIGNABLE_PROPERTYNAME => 1,
+		));
+
+		$this->plugin->handleUpdateProperties(
+			'/systemtag/1',
+			$propPatch
+		);
+
+		$propPatch->commit();
+
+		// all requested properties removed, as they were processed already
+		$this->assertEmpty($propPatch->getRemainingMutations());
+
+		$result = $propPatch->getResult();
+		$this->assertEquals(200, $result[self::DISPLAYNAME_PROPERTYNAME]);
+		$this->assertEquals(200, $result[self::USERASSIGNABLE_PROPERTYNAME]);
+		$this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]);
+	}
+
+	public function testCreateTagInByIdCollection() {
+		$systemTag = new SystemTag(1, 'Test', true, false);
+
+		$requestData = json_encode([
+			'name' => 'Test',
+			'userVisible' => true,
+			'userAssignable' => false,
+		]);
+
+		$node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsByIdCollection')
+			->disableOriginalConstructor()
+			->getMock();
+		$this->tagManager->expects($this->once())
+			->method('createTag')
+			->with('Test', true, false)
+			->will($this->returnValue($systemTag));
+
+		$this->tree->expects($this->any())
+			->method('getNodeForPath')
+			->with('/systemtags')
+			->will($this->returnValue($node));
+
+		$request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+				->disableOriginalConstructor()
+				->getMock();
+		$response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+				->disableOriginalConstructor()
+				->getMock();
+
+		$request->expects($this->once())
+			->method('getPath')
+			->will($this->returnValue('/systemtags'));
+
+		$request->expects($this->once())
+			->method('getBodyAsString')
+			->will($this->returnValue($requestData));
+
+		$request->expects($this->once())
+			->method('getHeader')
+			->with('Content-Type')
+			->will($this->returnValue('application/json'));	
+
+		$request->expects($this->once())
+			->method('getUrl')
+			->will($this->returnValue('http://example.com/dav/systemtags'));
+
+		$response->expects($this->once())
+			->method('setHeader')
+			->with('Location', 'http://example.com/dav/systemtags/1');
+
+		$this->plugin->httpPost($request, $response);
+	}
+
+	public function nodeClassProvider() {
+		return [
+			['\OCA\DAV\SystemTag\SystemTagsByIdCollection'],
+			['\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection'],
+		];
+	}
+
+	public function testCreateTagInMappingCollection() {
+		$systemTag = new SystemTag(1, 'Test', true, false);
+
+		$requestData = json_encode([
+			'name' => 'Test',
+			'userVisible' => true,
+			'userAssignable' => false,
+		]);
+
+		$node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection')
+			->disableOriginalConstructor()
+			->getMock();
+
+		$this->tagManager->expects($this->once())
+			->method('createTag')
+			->with('Test', true, false)
+			->will($this->returnValue($systemTag));
+
+		$this->tree->expects($this->any())
+			->method('getNodeForPath')
+			->with('/systemtags-relations/files/12')
+			->will($this->returnValue($node));
+
+		$node->expects($this->once())
+			->method('createFile')
+			->with(1);
+
+		$request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+				->disableOriginalConstructor()
+				->getMock();
+		$response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+				->disableOriginalConstructor()
+				->getMock();
+
+		$request->expects($this->once())
+			->method('getPath')
+			->will($this->returnValue('/systemtags-relations/files/12'));
+
+		$request->expects($this->once())
+			->method('getBodyAsString')
+			->will($this->returnValue($requestData));
+
+		$request->expects($this->once())
+			->method('getHeader')
+			->with('Content-Type')
+			->will($this->returnValue('application/json'));	
+
+		$request->expects($this->once())
+			->method('getBaseUrl')
+			->will($this->returnValue('http://example.com/dav/'));
+
+		$response->expects($this->once())
+			->method('setHeader')
+			->with('Location', 'http://example.com/dav/systemtags/1');
+
+		$this->plugin->httpPost($request, $response);
+	}
+
+	/**
+	 * @dataProvider nodeClassProvider
+	 * @expectedException Sabre\DAV\Exception\Conflict
+	 */
+	public function testCreateTagConflict($nodeClass) {
+		$requestData = json_encode([
+			'name' => 'Test',
+			'userVisible' => true,
+			'userAssignable' => false,
+		]);
+
+		$node = $this->getMockBuilder($nodeClass)
+			->disableOriginalConstructor()
+			->getMock();
+		$this->tagManager->expects($this->once())
+			->method('createTag')
+			->with('Test', true, false)
+			->will($this->throwException(new TagAlreadyExistsException('Tag already exists')));
+
+		$this->tree->expects($this->any())
+			->method('getNodeForPath')
+			->with('/systemtags')
+			->will($this->returnValue($node));
+
+		$request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+				->disableOriginalConstructor()
+				->getMock();
+		$response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+				->disableOriginalConstructor()
+				->getMock();
+
+		$request->expects($this->once())
+			->method('getPath')
+			->will($this->returnValue('/systemtags'));
+
+		$request->expects($this->once())
+			->method('getBodyAsString')
+			->will($this->returnValue($requestData));
+
+		$request->expects($this->once())
+			->method('getHeader')
+			->with('Content-Type')
+			->will($this->returnValue('application/json'));	
+
+		$this->plugin->httpPost($request, $response);
+	}
+
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php b/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..104ce366034046f642c2bf01e701953a284d8106
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagsbyidcollection.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+
+use OC\SystemTag\SystemTag;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+class SystemTagsByIdCollection extends \Test\TestCase {
+
+	/**
+	 * @var \OCA\DAV\SystemTag\SystemTagsByIdCollection
+	 */
+	private $node;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+
+		$this->node = new \OCA\DAV\SystemTag\SystemTagsByIdCollection($this->tagManager);
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testForbiddenCreateFile() {
+		$this->node->createFile('555');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testForbiddenCreateDirectory() {
+		$this->node->createDirectory('789');
+	}
+
+	public function testGetChild() {
+		$tag = new SystemTag(123, 'Test', true, false);
+
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['123'])
+			->will($this->returnValue([$tag]));
+
+		$childNode = $this->node->getChild('123');
+
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $childNode);
+		$this->assertEquals('123', $childNode->getName());
+		$this->assertEquals($tag, $childNode->getSystemTag());
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\BadRequest
+	 */
+	public function testGetChildInvalidName() {
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['invalid'])
+			->will($this->throwException(new \InvalidArgumentException()));
+
+		$this->node->getChild('invalid');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testGetChildNotFound() {
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['444'])
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->node->getChild('444');
+	}
+
+	public function testGetChildren() {
+		$tag1 = new SystemTag(123, 'One', true, false);
+		$tag2 = new SystemTag(456, 'Two', true, true);
+
+		$this->tagManager->expects($this->once())
+			->method('getAllTags')
+			->with(true)
+			->will($this->returnValue([$tag1, $tag2]));
+
+		$children = $this->node->getChildren();
+
+		$this->assertCount(2, $children);
+
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $children[0]);
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $children[1]);
+		$this->assertEquals($tag1, $children[0]->getSystemTag());
+		$this->assertEquals($tag2, $children[1]->getSystemTag());
+	}
+
+	public function testGetChildrenEmpty() {
+		$this->tagManager->expects($this->once())
+			->method('getAllTags')
+			->with(true)
+			->will($this->returnValue([]));
+		$this->assertCount(0, $this->node->getChildren());
+	}
+
+	public function testChildExists() {
+		$tag = new SystemTag(123, 'One', true, false);
+
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['123'])
+			->will($this->returnValue([$tag]));
+
+		$this->assertTrue($this->node->childExists('123'));
+	}
+
+	public function testChildExistsNotFound() {
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['123'])
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->assertFalse($this->node->childExists('123'));
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\BadRequest
+	 */
+	public function testChildExistsBadRequest() {
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['invalid'])
+			->will($this->throwException(new \InvalidArgumentException()));
+
+		$this->node->childExists('invalid');
+	}
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php b/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e15bb78e7c02bd513639ac3a74311afc74dfbcc
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagsobjectmappingcollection.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+
+use OC\SystemTag\SystemTag;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\SystemTag\TagAlreadyExistsException;
+
+class SystemTagsObjectMappingCollection extends \Test\TestCase {
+
+	/**
+	 * @var \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection
+	 */
+	private $node;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagMapper
+	 */
+	private $tagMapper;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+		$this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper');
+
+		$this->node = new \OCA\DAV\SystemTag\SystemTagsObjectMappingCollection (
+			111,
+			'files',
+			$this->tagManager,
+			$this->tagMapper
+		);
+	}
+
+	public function testAssignTag() {
+		$this->tagMapper->expects($this->once())
+			->method('assignTags')
+			->with(111, 'files', '555');
+
+		$this->node->createFile('555');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\PreconditionFailed
+	 */
+	public function testAssignTagNotFound() {
+		$this->tagMapper->expects($this->once())
+			->method('assignTags')
+			->with(111, 'files', '555')
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->node->createFile('555');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testForbiddenCreateDirectory() {
+		$this->node->createDirectory('789');
+	}
+
+	public function testGetChild() {
+		$tag = new SystemTag(555, 'TheTag', true, false);
+
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '555', true)
+			->will($this->returnValue(true));
+
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['555'])
+			->will($this->returnValue(['555' => $tag]));
+
+		$childNode = $this->node->getChild('555');
+
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagNode', $childNode);
+		$this->assertEquals('555', $childNode->getName());
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testGetChildRelationNotFound() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '777')
+			->will($this->returnValue(false));
+
+		$this->node->getChild('777');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\BadRequest
+	 */
+	public function testGetChildInvalidId() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', 'badid')
+			->will($this->throwException(new \InvalidArgumentException()));
+
+		$this->node->getChild('badid');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\NotFound
+	 */
+	public function testGetChildTagDoesNotExist() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '777')
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->node->getChild('777');
+	}
+
+	public function testGetChildren() {
+		$tag1 = new SystemTag(555, 'TagOne', true, false);
+		$tag2 = new SystemTag(556, 'TagTwo', true, true);
+
+		$this->tagMapper->expects($this->once())
+			->method('getTagIdsForObjects')
+			->with([111], 'files')
+			->will($this->returnValue(['111' => ['555', '556']]));
+
+		$this->tagManager->expects($this->once())
+			->method('getTagsByIds')
+			->with(['555', '556'])
+			->will($this->returnValue(['555' => $tag1, '666' => $tag2]));
+
+		$children = $this->node->getChildren();
+
+		$this->assertCount(2, $children);
+
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagMappingNode', $children[0]);
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagMappingNode', $children[1]);
+
+		$this->assertEquals(111, $children[0]->getObjectId());
+		$this->assertEquals('files', $children[0]->getObjectType());
+		$this->assertEquals($tag1, $children[0]->getSystemTag());
+
+		$this->assertEquals(111, $children[1]->getObjectId());
+		$this->assertEquals('files', $children[1]->getObjectType());
+		$this->assertEquals($tag2, $children[1]->getSystemTag());
+	}
+
+	public function testChildExists() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '555')
+			->will($this->returnValue(true));
+
+		$this->assertTrue($this->node->childExists('555'));
+	}
+
+	public function testChildExistsNotFound() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '555')
+			->will($this->returnValue(false));
+
+		$this->assertFalse($this->node->childExists('555'));
+	}
+
+	public function testChildExistsTagNotFound() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '555')
+			->will($this->throwException(new TagNotFoundException()));
+
+		$this->assertFalse($this->node->childExists('555'));
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\BadRequest
+	 */
+	public function testChildExistsInvalidId() {
+		$this->tagMapper->expects($this->once())
+			->method('haveTag')
+			->with([111], 'files', '555')
+			->will($this->throwException(new \InvalidArgumentException()));
+
+		$this->node->childExists('555');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testDelete() {
+		$this->node->delete();
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testSetName() {
+		$this->node->setName('somethingelse');
+	}
+
+	public function testGetName() {
+		$this->assertEquals('111', $this->node->getName());
+	}
+}
diff --git a/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php b/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php
new file mode 100644
index 0000000000000000000000000000000000000000..39223ff91222aa5fdc592e4d48cf68354bd2f485
--- /dev/null
+++ b/apps/dav/tests/unit/systemtag/systemtagsobjecttypecollection.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\SystemTag;
+
+class SystemTagsObjectTypeCollection extends \Test\TestCase {
+
+	/**
+	 * @var \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection
+	 */
+	private $node;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagManager
+	 */
+	private $tagManager;
+
+	/**
+	 * @var \OCP\SystemTag\ISystemTagMapper
+	 */
+	private $tagMapper;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+		$this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper');
+
+		$this->node = new \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection(
+			'files',
+			$this->tagManager,
+			$this->tagMapper
+		);
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testForbiddenCreateFile() {
+		$this->node->createFile('555');
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testForbiddenCreateDirectory() {
+		$this->node->createDirectory('789');
+	}
+
+	public function testGetChild() {
+		$childNode = $this->node->getChild('files');
+
+		$this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection', $childNode);
+		$this->assertEquals('files', $childNode->getName());
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\MethodNotAllowed
+	 */
+	public function testGetChildren() {
+		$this->node->getChildren();
+	}
+
+	public function testChildExists() {
+		$this->assertTrue($this->node->childExists('123'));
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testDelete() {
+		$this->node->delete();
+	}
+
+	/**
+	 * @expectedException Sabre\DAV\Exception\Forbidden
+	 */
+	public function testSetName() {
+		$this->node->setName('somethingelse');
+	}
+
+	public function testGetName() {
+		$this->assertEquals('files', $this->node->getName());
+	}
+}
diff --git a/lib/private/systemtag/systemtagmanager.php b/lib/private/systemtag/systemtagmanager.php
index 8caf10d69da0fca001b9fd887b7d1d05753dfca7..7f239dc84cf5a20e905f870f56c152ac2bfb3e88 100644
--- a/lib/private/systemtag/systemtagmanager.php
+++ b/lib/private/systemtag/systemtagmanager.php
@@ -63,7 +63,7 @@ class SystemTagManager implements ISystemTagManager {
 	/**
 	 * {@inheritdoc}
 	 */
-	public function getTagsById($tagIds) {
+	public function getTagsByIds($tagIds) {
 		if (!is_array($tagIds)) {
 			$tagIds = [$tagIds];
 		}
@@ -242,7 +242,7 @@ class SystemTagManager implements ISystemTagManager {
 
 		$tagNotFoundException = null;
 		try {
-			$this->getTagsById($tagIds);
+			$this->getTagsByIds($tagIds);
 		} catch (TagNotFoundException $e) {
 			$tagNotFoundException = $e;
 		}
diff --git a/lib/private/systemtag/systemtagobjectmapper.php b/lib/private/systemtag/systemtagobjectmapper.php
index 75f2631a010b5b5a5270426172d94e1b63083941..988fa66d77eb0b83cc36d97545c8d38e4ef421c5 100644
--- a/lib/private/systemtag/systemtagobjectmapper.php
+++ b/lib/private/systemtag/systemtagobjectmapper.php
@@ -171,6 +171,10 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
 	public function haveTag($objIds, $objectType, $tagId, $all = true) {
 		$this->assertTagsExist([$tagId]);
 
+		if (!is_array($objIds)) {
+			$objIds = [$objIds];
+		}
+
 		$query = $this->connection->getQueryBuilder();
 
 		if (!$all) {
@@ -209,7 +213,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
 	 * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
 	 */
 	private function assertTagsExist($tagIds) {
-		$tags = $this->tagManager->getTagsById($tagIds);
+		$tags = $this->tagManager->getTagsByIds($tagIds);
 		if (count($tags) !== count($tagIds)) {
 			// at least one tag missing, bail out
 			$foundTagIds = array_map(
diff --git a/lib/public/systemtag/isystemtagmanager.php b/lib/public/systemtag/isystemtagmanager.php
index 4e3b263e56c97a5c644598276accd7859f440925..6e8fed36dce52b0378add426ebff5c2ceb5c18ea 100644
--- a/lib/public/systemtag/isystemtagmanager.php
+++ b/lib/public/systemtag/isystemtagmanager.php
@@ -41,7 +41,7 @@ interface ISystemTagManager {
 	 *
 	 * @since 9.0.0
 	 */
-	public function getTagsById($tagIds);
+	public function getTagsByIds($tagIds);
 
 	/**
 	 * Returns the tag object matching the given attributes.
diff --git a/tests/lib/systemtag/systemtagmanagertest.php b/tests/lib/systemtag/systemtagmanagertest.php
index 8498b85519f4c8fb2fd552c0e561b1000484e2f9..97c072f33f634df318373c2cf9ea95de59aa6f31 100644
--- a/tests/lib/systemtag/systemtagmanagertest.php
+++ b/tests/lib/systemtag/systemtagmanagertest.php
@@ -250,7 +250,7 @@ class SystemTagManagerTest extends TestCase {
 		$tag1 = $this->tagManager->createTag('one', true, false);
 		$tag2 = $this->tagManager->createTag('two', false, true);
 
-		$tagList = $this->tagManager->getTagsById([$tag1->getId(), $tag2->getId()]);
+		$tagList = $this->tagManager->getTagsByIds([$tag1->getId(), $tag2->getId()]);
 
 		$this->assertCount(2, $tagList);
 
@@ -270,7 +270,7 @@ class SystemTagManagerTest extends TestCase {
 	 */
 	public function testGetNonExistingTagsById() {
 		$tag1 = $this->tagManager->createTag('one', true, false);
-		$this->tagManager->getTagsById([$tag1->getId(), 100, 101]);
+		$this->tagManager->getTagsByIds([$tag1->getId(), 100, 101]);
 	}
 
 	/**
@@ -278,7 +278,7 @@ class SystemTagManagerTest extends TestCase {
 	 */
 	public function testGetInvalidTagIdFormat() {
 		$tag1 = $this->tagManager->createTag('one', true, false);
-		$this->tagManager->getTagsById([$tag1->getId() . 'suffix']);
+		$this->tagManager->getTagsByIds([$tag1->getId() . 'suffix']);
 	}
 
 	public function updateTagProvider() {
diff --git a/tests/lib/systemtag/systemtagobjectmappertest.php b/tests/lib/systemtag/systemtagobjectmappertest.php
index 43d0b8c696067060905b126c7989ddb454944776..4ea80c216edcdb09313432cfc43d7b50c81e2621 100644
--- a/tests/lib/systemtag/systemtagobjectmappertest.php
+++ b/tests/lib/systemtag/systemtagobjectmappertest.php
@@ -74,7 +74,7 @@ class SystemTagObjectMapperTest extends TestCase {
 		$this->tag3 = new SystemTag(3, 'testtag3', false, false);
 
 		$this->tagManager->expects($this->any())
-			->method('getTagsById')
+			->method('getTagsByIds')
 			->will($this->returnCallback(function($tagIds) {
 				$result = [];
 				if (in_array(1, $tagIds)) {