diff --git a/src/Makefile.am b/src/Makefile.am
index 8d0239b2..1866457c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -5,6 +5,7 @@ lms_SOURCES = \
$(srcdir)/av/AvInfo.cpp \
$(srcdir)/av/AvTranscoder.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
+ $(srcdir)/database/Cluster.cpp \
$(srcdir)/database/DbArtist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/MediaDirectory.cpp \
diff --git a/src/database/Cluster.cpp b/src/database/Cluster.cpp
new file mode 100644
index 00000000..b32ac64d
--- /dev/null
+++ b/src/database/Cluster.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013-2016 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "Cluster.hpp"
+
+#include "DbArtist.hpp"
+#include "Release.hpp"
+#include "SqlQuery.hpp"
+#include "Track.hpp"
+
+namespace Database {
+
+Cluster::Cluster()
+{
+}
+
+Cluster::Cluster(Wt::Dbo::ptr type, std::string name)
+ : _name(std::string(name, 0, _maxNameLength)),
+ _clusterType(type)
+{
+}
+
+Cluster::pointer
+Cluster::create(Wt::Dbo::Session& session, Wt::Dbo::ptr type, std::string name)
+{
+ return session.add(std::make_unique(type, name));
+}
+
+std::vector
+Cluster::getAll(Wt::Dbo::Session& session)
+{
+ Wt::Dbo::collection res = session.find();
+
+ return std::vector(res.begin(), res.end());
+}
+
+Cluster::pointer
+Cluster::getById(Wt::Dbo::Session& session, IdType id)
+{
+ return session.find().where("id = ?").bind(id);
+}
+
+ClusterType::ClusterType(std::string name)
+ : _name(name)
+{
+}
+
+std::vector
+ClusterType::getAllOrphans(Wt::Dbo::Session& session)
+{
+ Wt::Dbo::collection res = session.query>("select c_t from cluster_type c_t LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id WHERE c.id IS NULL");
+
+ return std::vector(res.begin(), res.end());
+}
+
+
+ClusterType::pointer
+ClusterType::getByName(Wt::Dbo::Session& session, std::string name)
+{
+ return session.find().where("name = ?").bind(name);
+}
+
+std::vector
+ClusterType::getAll(Wt::Dbo::Session& session)
+{
+ Wt::Dbo::collection res = session.find();
+
+ return std::vector(res.begin(), res.end());
+}
+
+ClusterType::pointer
+ClusterType::create(Wt::Dbo::Session& session, std::string name)
+{
+ return session.add(std::make_unique(name));
+}
+
+Cluster::pointer
+ClusterType::getCluster(std::string name) const
+{
+ assert(self());
+ assert(IdIsValid(self()->id()));
+ assert(session());
+
+ return session()->find()
+ .where("name = ?").bind(name)
+ .where("cluster_type_id = ?").bind(self()->id());
+}
+
+std::vector
+ClusterType::getClusters() const
+{
+ assert(self());
+ assert(IdIsValid(self()->id()));
+ assert(session());
+
+ Wt::Dbo::collection res = session()->find()
+ .where("cluster_type_id = ?").bind(self()->id())
+ .orderBy("name");
+
+ return std::vector(res.begin(), res.end());
+}
+
+void
+Cluster::addTrack(Wt::Dbo::ptr