diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2f25d2db6c59c680f127c218a77f697940a01ba1..86912bc3eafbef3b68df35eb6b0df1ee19accbe5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -206,6 +206,7 @@ set(COMMON_SOURCES
   ${APP_SRC_DIR}/pluginadapter.cpp
   ${APP_SRC_DIR}/deviceitemlistmodel.cpp
   ${APP_SRC_DIR}/pluginlistmodel.cpp
+  ${APP_SRC_DIR}/pluginstorelistmodel.cpp
   ${APP_SRC_DIR}/pluginhandlerlistmodel.cpp
   ${APP_SRC_DIR}/preferenceitemlistmodel.cpp
   ${APP_SRC_DIR}/mediacodeclistmodel.cpp
@@ -239,7 +240,8 @@ set(COMMON_SOURCES
   ${APP_SRC_DIR}/currentcall.cpp
   ${APP_SRC_DIR}/messageparser.cpp
   ${APP_SRC_DIR}/previewengine.cpp
-  ${APP_SRC_DIR}/imagedownloader.cpp)
+  ${APP_SRC_DIR}/imagedownloader.cpp
+  ${APP_SRC_DIR}/pluginversionmanager.cpp)
 
 set(COMMON_HEADERS
   ${APP_SRC_DIR}/avatarimageprovider.h
@@ -267,6 +269,7 @@ set(COMMON_HEADERS
   ${APP_SRC_DIR}/pluginadapter.h
   ${APP_SRC_DIR}/deviceitemlistmodel.h
   ${APP_SRC_DIR}/pluginlistmodel.h
+  ${APP_SRC_DIR}/pluginstorelistmodel.h
   ${APP_SRC_DIR}/pluginhandlerlistmodel.h
   ${APP_SRC_DIR}/preferenceitemlistmodel.h
   ${APP_SRC_DIR}/mediacodeclistmodel.h
@@ -303,7 +306,8 @@ set(COMMON_HEADERS
   ${APP_SRC_DIR}/currentcall.h
   ${APP_SRC_DIR}/messageparser.h
   ${APP_SRC_DIR}/htmlparser.h
-  ${APP_SRC_DIR}/imagedownloader.h)
+  ${APP_SRC_DIR}/imagedownloader.h
+  ${APP_SRC_DIR}/pluginversionmanager.h)
 
 
 # For libavutil/avframe.
diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index 7397df79451f73495a7ffbdc7dfa4b89e11e9a47..901268b5bf8a2ed82c9989a22c3e62cadb740296 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -49,6 +49,7 @@ extern const QString defaultDownloadPath;
     X(HideSelf, false) \
     X(HideSpectators, false) \
     X(AutoUpdate, true) \
+    X(PluginAutoUpdate, false) \
     X(StartMinimized, false) \
     X(ShowChatviewHorizontally, true) \
     X(NeverShowMeAgain, false) \
diff --git a/src/app/appversionmanager.cpp b/src/app/appversionmanager.cpp
index c3ac06c9c57595b54f227e29aaea33240026eebb..aead337ab942f3424971a2abc5c7e3197c9a8d4c 100644
--- a/src/app/appversionmanager.cpp
+++ b/src/app/appversionmanager.cpp
@@ -166,8 +166,8 @@ AppVersionManager::AppVersionManager(const QString& url,
                                      LRCInstance* instance,
                                      QObject* parent)
     : NetworkManager(cm, parent)
-    , pimpl_(std::make_unique<Impl>(url, instance, *this))
     , replyId_(new int(0))
+    , pimpl_(std::make_unique<Impl>(url, instance, *this))
 {}
 
 AppVersionManager::~AppVersionManager()
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index c62fbd884d2b3b8cf1cff07b9f018a274006a10d..4278a99d32746aa5bf7f6e1bc41b44e14e16d323 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -661,6 +661,7 @@ Item {
     property string enable: qsTr("Enable")
     property string pluginPreferences: qsTr("Preferences")
     property string reset: qsTr("Reset")
+    property string disableAll: qsTr("Disable all")
     property string uninstall: qsTr("Uninstall")
     property string resetPreferences: qsTr("Reset Preferences")
     property string selectPluginInstall: qsTr("Select a plugin to install")
diff --git a/src/app/lrcinstance.cpp b/src/app/lrcinstance.cpp
index 323cab0b54daeac9df070b6580fb68ca01db50fe..02bafe4fdf91bc6cffb85a64aafa705ac1d3c432 100644
--- a/src/app/lrcinstance.cpp
+++ b/src/app/lrcinstance.cpp
@@ -19,6 +19,7 @@
  */
 
 #include "lrcinstance.h"
+#include "connectivitymonitor.h"
 
 #include <QBuffer>
 #include <QMutex>
@@ -35,6 +36,7 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
                          bool muteDaemon)
     : lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, !debugMode || muteDaemon))
     , updateManager_(std::make_unique<AppVersionManager>(updateUrl, connectivityMonitor, this))
+    , connectivityMonitor_(*connectivityMonitor)
     , threadPool_(new QThreadPool(this))
 {
     debugMode_ = debugMode;
@@ -111,6 +113,12 @@ LRCInstance::pluginModel()
     return lrc_->getPluginModel();
 }
 
+ConnectivityMonitor&
+LRCInstance::connectivityMonitor()
+{
+    return connectivityMonitor_;
+}
+
 bool
 LRCInstance::isConnected()
 {
diff --git a/src/app/lrcinstance.h b/src/app/lrcinstance.h
index 0da5abee14ca57e9b0b248988ac094fa3a63c329..08075930d35e241a3e1b351bc3152b2c958891ef 100644
--- a/src/app/lrcinstance.h
+++ b/src/app/lrcinstance.h
@@ -79,6 +79,7 @@ public:
     ContactModel* getCurrentContactModel();
     AVModel& avModel();
     PluginModel& pluginModel();
+    ConnectivityMonitor& connectivityMonitor();
     BehaviorController& behaviorController();
 
     void subscribeToDebugReceived();
@@ -147,6 +148,8 @@ private:
     std::unique_ptr<Lrc> lrc_;
     std::unique_ptr<AppVersionManager> updateManager_;
 
+    ConnectivityMonitor& connectivityMonitor_;
+
     QString selectedConvUid_;
     MapStringString contentDrafts_;
     MapStringString lastConferences_;
diff --git a/src/app/networkmanager.cpp b/src/app/networkmanager.cpp
index 0af5f216e8e5e1a87eecd431aedaf0100f2efa15..7bad5e6c6bbc8a01952f5bd49e226175fb138580 100644
--- a/src/app/networkmanager.cpp
+++ b/src/app/networkmanager.cpp
@@ -72,9 +72,10 @@ NetworkManager::sendGetRequest(const QUrl& url,
 
 int
 NetworkManager::downloadFile(const QUrl& url,
-                             unsigned int replyId,
+                             int replyId,
                              std::function<void(bool, const QString&)>&& onDoneCallback,
-                             const QString& filePath)
+                             const QString& filePath,
+                             const QString& extension)
 {
     // If there is already a download in progress, return.
     if ((downloadReplies_.value(replyId) != NULL || !(replyId == 0))
@@ -111,7 +112,7 @@ NetworkManager::downloadFile(const QUrl& url,
     const QFileInfo fileInfo(url.path());
     const QString fileName = fileInfo.fileName();
     auto& file = files_[uuid];
-    file = new QFile(filePath + fileName + ".jpl");
+    file = new QFile(filePath + fileName + extension);
     if (!file->open(QIODevice::WriteOnly)) {
         Q_EMIT errorOccurred(GetError::ACCESS_DENIED);
         files_.remove(uuid);
@@ -122,8 +123,8 @@ NetworkManager::downloadFile(const QUrl& url,
     // Start the download.
     const QNetworkRequest request(url);
 
-    downloadReplies_[uuid] = manager_->get(request);
-    auto* const reply = downloadReplies_[uuid];
+    auto* const reply = manager_->get(request);
+    downloadReplies_[uuid] = reply;
     connect(reply, &QNetworkReply::readyRead, this, [file, reply]() {
         if (file && file->isOpen()) {
             file->write(reply->readAll());
@@ -148,8 +149,10 @@ NetworkManager::downloadFile(const QUrl& url,
                 Q_EMIT errorOccurred(GetError::NETWORK_ERROR);
             });
 
-    connect(reply, &QNetworkReply::finished, this, [this, uuid, onDoneCallback, reply]() {
+    connect(reply, &QNetworkReply::finished, this, [this, uuid, onDoneCallback, reply, file]() {
         bool success = false;
+        file->close();
+        reply->deleteLater();
         QString errorMessage;
         if (reply->error() == QNetworkReply::NoError) {
             resetDownload(uuid);
diff --git a/src/app/networkmanager.h b/src/app/networkmanager.h
index 67dacb237dbf14bdf820f5c1e04ac03e5cc5f2ef..daa2196be02496c89dd6a351e4aa54e6e9f500d3 100644
--- a/src/app/networkmanager.h
+++ b/src/app/networkmanager.h
@@ -41,9 +41,10 @@ public:
     void sendGetRequest(const QUrl& url, std::function<void(const QByteArray&)>&& onDoneCallback);
 
     int downloadFile(const QUrl& url,
-                     unsigned int replyId,
+                     int replyId,
                      std::function<void(bool, const QString&)>&& onDoneCallback,
-                     const QString& filePath);
+                     const QString& filePath,
+                     const QString& extension = "");
     void resetDownload(int replyId);
     void cancelDownload(int replyId);
 Q_SIGNALS:
diff --git a/src/app/pluginadapter.cpp b/src/app/pluginadapter.cpp
index 6c5a97cf08cab2d3d17f576af08bcc8578dc7a58..8b2a464fadb6dfc15ab88b8530daded3c8453074 100644
--- a/src/app/pluginadapter.cpp
+++ b/src/app/pluginadapter.cpp
@@ -20,9 +20,22 @@
 
 #include "networkmanager.h"
 #include "lrcinstance.h"
+#include "utilsadapter.h"
 
-PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent)
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QDir>
+#include <QString>
+
+PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent, QString baseUrl)
     : QmlAdapterBase(instance, parent)
+    , pluginStoreListModel_(new PluginStoreListModel(this))
+    , pluginVersionManager_(new PluginVersionManager(instance, baseUrl, this))
+    , pluginListModel_(new PluginListModel(this))
+    , lrcInstance_(instance)
+    , tempPath_(QDir::tempPath())
+    , baseUrl_(baseUrl)
 
 {
     set_isEnabled(lrcInstance_->pluginModel().getPluginsEnabled());
@@ -32,6 +45,73 @@ PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent)
             this,
             &PluginAdapter::updateHandlersListCount);
     connect(this, &PluginAdapter::isEnabledChanged, this, &PluginAdapter::updateHandlersListCount);
+    connect(pluginVersionManager_,
+            &PluginVersionManager::versionStatusChanged,
+            pluginListModel_,
+            &PluginListModel::onVersionStatusChanged);
+    connect(pluginVersionManager_,
+            &PluginVersionManager::versionStatusChanged,
+            pluginStoreListModel_,
+            &PluginStoreListModel::onVersionStatusChanged);
+    connect(pluginStoreListModel_,
+            &PluginStoreListModel::pluginAdded,
+            this,
+            &PluginAdapter::getPluginDetails);
+    connect(pluginListModel_,
+            &PluginListModel::versionCheckRequested,
+            pluginVersionManager_,
+            &PluginVersionManager::checkVersionStatus);
+    connect(pluginListModel_,
+            &PluginListModel::autoUpdateChanged,
+            pluginVersionManager_,
+            &PluginVersionManager::setAutoUpdate);
+    connect(pluginListModel_,
+            &PluginListModel::setVersionStatus,
+            pluginStoreListModel_,
+            &PluginStoreListModel::onVersionStatusChanged);
+    getPluginsFromStore();
+}
+
+void
+PluginAdapter::getPluginsFromStore()
+{
+    pluginVersionManager_->sendGetRequest(QUrl(baseUrl_), [this](const QByteArray& data) {
+        auto result = QJsonDocument::fromJson(data).array();
+        auto pluginsInstalled = lrcInstance_->pluginModel().getPluginsId();
+        QList<QVariantMap> plugins;
+        for (const auto& plugin : result) {
+            auto qPlugin = plugin.toVariant().toMap();
+            if (!pluginsInstalled.contains(qPlugin["id"].toString())) {
+                plugins.append(qPlugin);
+            }
+        }
+        pluginStoreListModel_->setPlugins(plugins);
+    });
+}
+
+void
+PluginAdapter::getPluginDetails(const QString& pluginId)
+{
+    pluginVersionManager_->sendGetRequest(QUrl(baseUrl_ + "/details/" + pluginId),
+                                          [this](const QByteArray& data) {
+                                              auto result = QJsonDocument::fromJson(data).object();
+                                              // my response is a json object and I want to convert
+                                              // it to a QVariantMap
+                                              pluginStoreListModel_->addPlugin(
+                                                  result.toVariantMap());
+                                          });
+}
+
+void
+PluginAdapter::installRemotePlugin(const QString& pluginId)
+{
+    pluginVersionManager_->installRemotePlugin(pluginId);
+}
+
+bool
+PluginAdapter::isAutoUpdaterEnabled()
+{
+    return pluginVersionManager_->isAutoUpdaterEnabled();
 }
 
 QVariant
@@ -77,3 +157,15 @@ PluginAdapter::updateHandlersListCount()
         set_chatHandlersListCount(0);
     }
 }
+
+void
+PluginAdapter::checkVersionStatus(const QString& pluginId)
+{
+    pluginVersionManager_->checkVersionStatus(pluginId);
+}
+
+QString
+PluginAdapter::baseUrl() const
+{
+    return baseUrl_;
+}
diff --git a/src/app/pluginadapter.h b/src/app/pluginadapter.h
index cb640100cf78607fb7d700295d117922d55616e8..f994b089f145dd5c4db820190e4cb0a949bc9362 100644
--- a/src/app/pluginadapter.h
+++ b/src/app/pluginadapter.h
@@ -22,6 +22,8 @@
 #include "pluginlistmodel.h"
 #include "pluginhandlerlistmodel.h"
 #include "pluginlistpreferencemodel.h"
+#include "pluginversionmanager.h"
+#include "pluginstorelistmodel.h"
 #include "preferenceitemlistmodel.h"
 
 #include <QObject>
@@ -36,9 +38,18 @@ class PluginAdapter final : public QmlAdapterBase
     QML_PROPERTY(bool, isEnabled)
 
 public:
-    explicit PluginAdapter(LRCInstance* instance, QObject* parent = nullptr);
+    explicit PluginAdapter(LRCInstance* instance,
+                           QObject* parent = nullptr,
+                           QString baseUrl = "https://plugins.jami.net");
     ~PluginAdapter() = default;
 
+    Q_INVOKABLE void getPluginsFromStore();
+    Q_INVOKABLE void getPluginDetails(const QString& pluginId);
+    Q_INVOKABLE void installRemotePlugin(const QString& pluginId);
+    Q_INVOKABLE QString baseUrl() const;
+    Q_INVOKABLE void checkVersionStatus(const QString& pluginId);
+    Q_INVOKABLE bool isAutoUpdaterEnabled();
+
 protected:
     Q_INVOKABLE QVariant getMediaHandlerSelectableModel(const QString& callId);
     Q_INVOKABLE QVariant getChatHandlerSelectableModel(const QString& accountId,
@@ -51,6 +62,11 @@ private:
     void updateHandlersListCount();
 
     std::unique_ptr<PluginHandlerListModel> pluginHandlerListModel_;
-
+    PluginStoreListModel* pluginStoreListModel_;
+    PluginVersionManager* pluginVersionManager_;
+    PluginListModel* pluginListModel_;
+    LRCInstance* lrcInstance_;
     std::mutex mtx_;
+    QString tempPath_;
+    QString baseUrl_;
 };
diff --git a/src/app/pluginlistmodel.cpp b/src/app/pluginlistmodel.cpp
index 6f6a16c3bd99d27d35bc8183772fe260f11a0924..3dc41db43524e86e33a9b07f5a54cc8e27169f40 100644
--- a/src/app/pluginlistmodel.cpp
+++ b/src/app/pluginlistmodel.cpp
@@ -142,3 +142,39 @@ PluginListModel::filterPlugins(VectorString& list) const
                               }),
                list.cend());
 }
+
+void
+PluginListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status)
+{
+    auto pluginIndex = -1;
+    for (auto& p : installedPlugins_) {
+        auto details = lrcInstance_->pluginModel().getPluginDetails(p);
+        if (details.name == pluginId) {
+            pluginIndex = installedPlugins_.indexOf(p, -1);
+            break;
+        }
+    }
+    switch (status) {
+    case PluginStatus::INSTALLED:
+        addPlugin();
+        break;
+    default:
+        break;
+    }
+
+    if (pluginIndex == -1) {
+        return;
+    }
+
+    switch (status) {
+    case PluginStatus::INSTALLABLE:
+        removePlugin(pluginIndex);
+        break;
+    case PluginStatus::FAILED:
+        qWarning() << "Failed to install plugin" << pluginId;
+        break;
+    default:
+        break;
+    }
+    return;
+}
diff --git a/src/app/pluginlistmodel.h b/src/app/pluginlistmodel.h
index b0bbbd9fa6a1bf3d088f9bd47d05f0cf02888b3f..d5c445183aea3eadef84c21ab649d7f26effbfeb 100644
--- a/src/app/pluginlistmodel.h
+++ b/src/app/pluginlistmodel.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "abstractlistmodelbase.h"
+#include "pluginversionmanager.h"
 
 class LRCInstance;
 
@@ -52,6 +53,14 @@ public:
     Q_INVOKABLE void pluginChanged(int index);
     Q_INVOKABLE void addPlugin();
 
+Q_SIGNALS:
+    void versionCheckRequested(const QString& pluginId);
+    void setVersionStatus(const QString& pluginId, PluginStatus::Role status);
+    void autoUpdateChanged(bool state);
+
+public Q_SLOTS:
+    void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
+
 private:
     void filterPlugins(VectorString& list) const;
     VectorString installedPlugins_ {};
diff --git a/src/app/pluginstorelistmodel.cpp b/src/app/pluginstorelistmodel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a90b7982860f1839c1da45cbc9d2168d698cc393
--- /dev/null
+++ b/src/app/pluginstorelistmodel.cpp
@@ -0,0 +1,196 @@
+/**
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginstorelistmodel.h"
+
+#include <QUrl>
+
+PluginStoreListModel::PluginStoreListModel(QObject* parent)
+    : AbstractListModelBase(parent)
+{}
+
+int
+PluginStoreListModel::rowCount(const QModelIndex& parent) const
+{
+    if (!parent.isValid()) {
+        return plugins_.size();
+    }
+    /// A valid QModelIndex returns 0 as no entry has sub-elements.
+    return 0;
+}
+
+QVariant
+PluginStoreListModel::data(const QModelIndex& index, int role) const
+{
+    if (!index.isValid()) {
+        return QVariant();
+    }
+    auto plugin = plugins_.at(index.row());
+    switch (role) {
+    case Role::Id:
+        return QVariant(plugin["id"].toString());
+    case Role::Title:
+        return QVariant(plugin["name"].toString());
+    case Role::IconPath:
+        return QVariant(plugin["iconPath"].toString());
+    case Role::Background:
+        return QVariant(plugin["background"].toString());
+    case Role::Description:
+        return QVariant(plugin["description"].toString());
+    case Role::Author:
+        return QVariant(plugin["author"].toString());
+    case Role::Status:
+        return QVariant(plugin.value("status", PluginStatus::INSTALLABLE).toString());
+    }
+    return QVariant();
+}
+
+QHash<int, QByteArray>
+PluginStoreListModel::roleNames() const
+{
+    using namespace PluginStoreList;
+    QHash<int, QByteArray> roles;
+#define X(role) roles[role] = #role;
+    PLUGINSTORE_ROLES
+#undef X
+    return roles;
+}
+
+void
+PluginStoreListModel::reset()
+{
+    beginResetModel();
+    plugins_.clear();
+    endResetModel();
+}
+
+void
+PluginStoreListModel::addPlugin(const QVariantMap& plugin)
+{
+    beginInsertRows(QModelIndex(), plugins_.size(), plugins_.size());
+    plugins_.append(plugin);
+    endInsertRows();
+}
+
+void
+PluginStoreListModel::setPlugins(const QList<QVariantMap>& plugins)
+{
+    beginResetModel();
+    plugins_ = plugins;
+    endResetModel();
+}
+
+void
+PluginStoreListModel::removePlugin(const QString& pluginId)
+{
+    auto index = 0;
+    for (auto& plugin : plugins_) {
+        if (plugin["id"].toString() == pluginId) {
+            beginRemoveRows(QModelIndex(), index, index);
+            plugins_.removeAt(index);
+            endRemoveRows();
+            return;
+        }
+        index++;
+    }
+}
+
+void
+PluginStoreListModel::updatePlugin(const QVariantMap& plugin)
+{
+    auto index = 0;
+    for (auto& p : plugins_) {
+        if (p["id"].toString() == plugin["id"].toString()) {
+            p = plugin;
+            Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0));
+            return;
+        }
+        index++;
+    }
+}
+
+QColor
+PluginStoreListModel::computeAverageColorOfImage(const QString& file)
+{
+    auto fileUrl = QUrl(file);
+    // Return an invalid color if the file URL is invalid.
+    if (!fileUrl.isValid()) {
+        return QColor();
+    }
+    // Load the image.
+    QImage image(fileUrl.toLocalFile());
+    // If the image is valid...
+    if (!image.isNull()) {
+        static const QSize size(3, 3);
+        static const int nPixels = size.width() * size.height();
+        // Scale the image to 3x3 pixels.
+        image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+        // Return the average color of the image's pixels.
+        double red = 0;
+        double green = 0;
+        double blue = 0;
+        for (int i = 0; i < size.width(); i++) {
+            for (int j = 0; j < size.height(); j++) {
+                auto pixelColor = image.pixelColor(i, j);
+                red += pixelColor.red();
+                green += pixelColor.green();
+                blue += pixelColor.blue();
+            }
+        }
+        return QColor(red / nPixels, green / nPixels, blue / nPixels, 70);
+    } else {
+        // Return an invalid color.
+        return QColor();
+    }
+}
+
+void
+PluginStoreListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status)
+{
+    auto plugin = QVariantMap();
+    for (auto& p : plugins_) {
+        if (p["id"].toString() == pluginId) {
+            plugin = p;
+            break;
+        }
+    }
+    switch (status) {
+    case PluginStatus::INSTALLABLE:
+        if (!plugin.isEmpty())
+            break;
+        pluginAdded(pluginId);
+        break;
+
+    default:
+        break;
+    }
+    if (plugin.isEmpty()) {
+        return;
+    }
+    plugin["status"] = status;
+
+    switch (status) {
+    case PluginStatus::INSTALLED:
+        removePlugin(pluginId);
+        break;
+    case PluginStatus::FAILED:
+        qWarning() << "Failed to install plugin" << pluginId;
+        break;
+    default:
+        break;
+    }
+}
\ No newline at end of file
diff --git a/src/app/pluginstorelistmodel.h b/src/app/pluginstorelistmodel.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a603c5103073234dd39d4ab45efc3bfbcb142fd
--- /dev/null
+++ b/src/app/pluginstorelistmodel.h
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2019-2023 Savoir-faire Linux Inc.
+ * Author: Xavier Jouslin de Noray   <xavier.jouslindenoray@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "abstractlistmodelbase.h"
+#include "pluginversionmanager.h"
+
+class QColor;
+class QString;
+
+#define PLUGINSTORE_ROLES \
+    X(Id) \
+    X(Title) \
+    X(IconPath) \
+    X(Background) \
+    X(Description) \
+    X(Status) \
+    X(Author)
+
+namespace PluginStoreList {
+Q_NAMESPACE
+enum Role {
+    DummyRole = Qt::UserRole + 1,
+#define X(role) role,
+    PLUGINSTORE_ROLES
+#undef X
+};
+Q_ENUM_NS(Role)
+} // namespace PluginStoreList
+
+class PluginStoreListModel : public AbstractListModelBase
+{
+    Q_OBJECT
+
+public:
+    explicit PluginStoreListModel(QObject* parent = nullptr);
+
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+    Q_INVOKABLE void reset();
+
+    void addPlugin(const QVariantMap& plugin);
+    void setPlugins(const QList<QVariantMap>& plugins);
+    void removePlugin(const QString& pluginId);
+    void updatePlugin(const QVariantMap& plugin);
+    Q_INVOKABLE QColor computeAverageColorOfImage(const QString& fileUrl);
+
+Q_SIGNALS:
+    void pluginAdded(const QString& pluginId);
+
+public Q_SLOTS:
+    void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
+
+private:
+    using Role = PluginStoreList::Role;
+    QList<QVariantMap> plugins_;
+};
diff --git a/src/app/pluginversionmanager.cpp b/src/app/pluginversionmanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28217891fcab1def243209a15ab652c30462a322
--- /dev/null
+++ b/src/app/pluginversionmanager.cpp
@@ -0,0 +1,221 @@
+/**
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginversionmanager.h"
+#include "networkmanager.h"
+#include "appsettingsmanager.h"
+#include "lrcinstance.h"
+#include "api/pluginmodel.h"
+
+#include <QMap>
+#include <QTimer>
+#include <QDir>
+
+static constexpr int updatePeriod = 1000 * 60 * 60 * 24; // one day in millis
+
+struct PluginVersionManager::Impl : public QObject
+{
+public:
+    Impl(LRCInstance* instance, PluginVersionManager& parent)
+        : QObject(nullptr)
+        , parent_(parent)
+        , appSettingsManager_(new AppSettingsManager(this))
+        , lrcInstance_(instance)
+        , tempPath_(QDir::tempPath())
+        , updateTimer_(new QTimer(this))
+    {
+        connect(updateTimer_, &QTimer::timeout, this, [this] { checkForUpdates(); });
+        connect(&parent_, &NetworkManager::downloadFinished, this, [this](int replyId) {
+            auto pluginsId = parent_.pluginRepliesId.keys(replyId);
+            if (pluginsId.size() == 0) {
+                return;
+            }
+            for (const auto& pluginId : qAsConst(pluginsId)) {
+                Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLING);
+                parent_.pluginRepliesId.remove(pluginId);
+            }
+        });
+        checkForUpdates();
+        setAutoUpdateCheck(true);
+    }
+
+    ~Impl()
+    {
+        setAutoUpdateCheck(false);
+    }
+
+    void checkForUpdates()
+    {
+        if (!lrcInstance_) {
+            return;
+        }
+        for (const auto& plugin : lrcInstance_->pluginModel().getInstalledPlugins()) {
+            checkVersionStatusFromPath(plugin);
+        }
+    }
+
+    void cancelUpdate(const QString& pluginId)
+    {
+        if (!parent_.pluginRepliesId.contains(pluginId)) {
+            return;
+        }
+        parent_.cancelDownload(parent_.pluginRepliesId[pluginId]);
+    };
+
+    bool isAutoUpdaterEnabled()
+    {
+        return appSettingsManager_->getValue(Settings::Key::PluginAutoUpdate).toBool();
+    }
+
+    void setAutoUpdate(bool state)
+    {
+        appSettingsManager_->setValue(Settings::Key::PluginAutoUpdate, state);
+    }
+
+    void checkVersionStatus(const QString& pluginId)
+    {
+        checkVersionStatusFromPath(lrcInstance_->pluginModel().getPluginPath(pluginId));
+    }
+
+    void checkVersionStatusFromPath(const QString& pluginPath)
+    {
+        if (!lrcInstance_) {
+            return;
+        }
+
+        auto plugin = lrcInstance_->pluginModel().getPluginDetails(pluginPath);
+        if (plugin.version == "" || plugin.id == "") {
+            Q_EMIT parent_.versionStatusChanged(plugin.id, PluginStatus::Role::FAILED);
+            return;
+        }
+
+        parent_.sendGetRequest(QUrl(parent_.baseUrl + "/versions/" + plugin.id),
+                               [this, plugin](const QByteArray& data) {
+                                   // `data` represents the version in this case.
+                                   if (plugin.version < data) {
+                                       if (isAutoUpdaterEnabled()) {
+                                           installRemotePlugin(plugin.name);
+                                           return;
+                                       }
+                                   }
+                                   parent_.versionStatusChanged(plugin.id,
+                                                                PluginStatus::Role::UPDATABLE);
+                               });
+    }
+
+    void installRemotePlugin(const QString& pluginId)
+    {
+        parent_.downloadFile(
+            QUrl(parent_.baseUrl + "/download/" + pluginId),
+            pluginId,
+            0,
+            [this, pluginId](bool success, const QString& error) {
+                if (!success) {
+                    qDebug() << "Download Plugin error: " << error;
+                    parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
+                    return;
+                }
+                auto res = lrcInstance_->pluginModel().installPlugin(QDir(tempPath_).filePath(
+                                                                         pluginId + ".jpl"),
+                                                                     true);
+                if (res) {
+                    parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLED);
+                } else {
+                    parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
+                }
+            },
+            tempPath_ + '/');
+        Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::DOWNLOADING);
+    }
+
+    void setAutoUpdateCheck(bool state)
+    {
+        // Quiet check for updates periodically, if set to.
+        if (!state) {
+            updateTimer_->stop();
+            return;
+        }
+        updateTimer_->start(updatePeriod);
+    };
+
+    PluginVersionManager& parent_;
+    AppSettingsManager* appSettingsManager_ {nullptr};
+    LRCInstance* lrcInstance_ {nullptr};
+    QString tempPath_;
+    QTimer* updateTimer_;
+};
+
+PluginVersionManager::PluginVersionManager(LRCInstance* instance, QString& baseUrl, QObject* parent)
+    : NetworkManager(&instance->connectivityMonitor(), parent)
+    , baseUrl(baseUrl)
+    , pimpl_(std::make_unique<Impl>(instance, *this))
+{}
+
+PluginVersionManager::~PluginVersionManager()
+{
+    for (const auto& pluginReplyId : qAsConst(pluginRepliesId)) {
+        cancelDownload(pluginReplyId);
+    }
+    pluginRepliesId.clear();
+}
+
+void
+PluginVersionManager::cancelUpdate(const QString& pluginId)
+{
+    pimpl_->cancelUpdate(pluginId);
+}
+
+bool
+PluginVersionManager::isAutoUpdaterEnabled()
+{
+    return pimpl_->isAutoUpdaterEnabled();
+}
+
+void
+PluginVersionManager::setAutoUpdate(bool state)
+{
+    pimpl_->setAutoUpdate(state);
+}
+
+int
+PluginVersionManager::downloadFile(const QUrl& url,
+                                   const QString& pluginId,
+                                   int replyId,
+                                   std::function<void(bool, const QString&)>&& onDoneCallback,
+                                   const QString& filePath,
+                                   const QString& extension)
+{
+    auto reply = NetworkManager::downloadFile(url,
+                                              replyId,
+                                              std::move(onDoneCallback),
+                                              filePath,
+                                              extension);
+    pluginRepliesId[pluginId] = reply;
+    return reply;
+}
+
+void
+PluginVersionManager::checkVersionStatus(const QString& pluginId)
+{
+    pimpl_->checkVersionStatus(pluginId);
+}
+
+void
+PluginVersionManager::installRemotePlugin(const QString& pluginId)
+{
+    pimpl_->installRemotePlugin(pluginId);
+}
diff --git a/src/app/pluginversionmanager.h b/src/app/pluginversionmanager.h
new file mode 100644
index 0000000000000000000000000000000000000000..365006bbe784c3ad236785d4bf751484a761319f
--- /dev/null
+++ b/src/app/pluginversionmanager.h
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <memory>
+#include "networkmanager.h"
+
+class QString;
+class LRCInstance;
+
+#define PLUGIN_STATUS_ROLES \
+    X(INSTALLABLE) \
+    X(DOWNLOADING) \
+    X(INSTALLING) \
+    X(INSTALLED) \
+    X(FAILED) \
+    X(UPDATABLE)
+
+namespace PluginStatus {
+Q_NAMESPACE
+enum Role {
+    DummyRole = Qt::UserRole + 1,
+#define X(role) role,
+    PLUGIN_STATUS_ROLES
+#undef X
+};
+Q_ENUM_NS(Role)
+} // namespace PluginStatus
+
+class PluginVersionManager final : public NetworkManager
+{
+    Q_OBJECT
+public:
+    explicit PluginVersionManager(LRCInstance* instance,
+                                  QString& baseUrl,
+                                  QObject* parent = nullptr);
+    ~PluginVersionManager();
+
+    Q_INVOKABLE bool isAutoUpdaterEnabled();
+
+    Q_INVOKABLE void cancelUpdate(const QString& pluginId);
+    int downloadFile(const QUrl& url,
+                     const QString& pluginId,
+                     int replyId,
+                     std::function<void(bool, const QString&)>&& onDoneCallback,
+                     const QString& filePath,
+                     const QString& extension = ".jpl");
+    void installRemotePlugin(const QString& pluginId);
+
+public Q_SLOTS:
+    void checkVersionStatus(const QString& pluginId);
+    void setAutoUpdate(bool state);
+
+Q_SIGNALS:
+    void versionStatusChanged(const QString& pluginId, PluginStatus::Role status);
+
+private:
+    QString baseUrl;
+    bool autoUpdateCheck = false;
+    QMap<QString, unsigned int> pluginRepliesId {};
+    struct Impl;
+    friend struct Impl;
+    std::unique_ptr<Impl> pimpl_;
+};
+Q_DECLARE_METATYPE(PluginVersionManager*)
diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp
index 5c5d677b1d212e5526bc7c871590dbccbc1082a5..4ffec74d07667d752e6431f2d6347b00162b9e06 100644
--- a/src/app/qmlregister.cpp
+++ b/src/app/qmlregister.cpp
@@ -56,6 +56,7 @@
 #include "mainapplication.h"
 #include "namedirectory.h"
 #include "pluginlistmodel.h"
+#include "pluginversionmanager.h"
 #include "appversionmanager.h"
 #include "pluginlistpreferencemodel.h"
 #include "preferenceitemlistmodel.h"
@@ -188,6 +189,7 @@ registerTypes(QQmlEngine* engine,
     QML_REGISTERNAMESPACE(NS_MODELS, ContactList::staticMetaObject, "ContactList");
     QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
     QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList");
+    QML_REGISTERNAMESPACE(NS_MODELS, PluginStatus::staticMetaObject, "PluginStatus");
 
     // Qml singleton components
     QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/constant/JamiTheme.qml", JamiTheme);
diff --git a/src/app/settingsview/components/PluginAvailableDelagate.qml b/src/app/settingsview/components/PluginAvailableDelagate.qml
new file mode 100644
index 0000000000000000000000000000000000000000..0d66d5d9846fab349b550b64a510473e0ed0f1e7
--- /dev/null
+++ b/src/app/settingsview/components/PluginAvailableDelagate.qml
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ * Author: Xavier Jouslin de Noray  <xjouslindenoray@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import Qt5Compat.GraphicalEffects
+import net.jami.Constants 1.1
+import "../../commoncomponents"
+import "../../mainview/components"
+
+ItemDelegate {
+    id: root
+    property string pluginId
+    property string pluginTitle
+    property string pluginIcon
+    property string pluginBackground
+    property string pluginDescription
+    property string pluginAuthor
+    property string pluginShortDescription
+    property int pluginStatus
+
+    Rectangle {
+        id: rect
+        Scaffold {
+        }
+        color: Qt.rgba(0, 0, 0, 1)
+        anchors.fill: parent
+        radius: 15
+    }
+    Page {
+        id: plugin
+        anchors.fill: parent
+        header: Control {
+            padding: 10
+            background: Rectangle {
+                color: pluginBackground
+            }
+            contentItem: ColumnLayout {
+                RowLayout {
+                    Layout.alignment: Qt.AlignTop | Qt.AlignRight
+                    MaterialButton {
+                        id: install
+                        Layout.alignment: Qt.AlignRight
+                        Layout.rightMargin: 8
+                        Layout.topMargin: 8
+                        Layout.preferredHeight: 20
+                        TextMetrics {
+                            id: installTextSize
+                            font.weight: Font.Black
+                            font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                            font.capitalization: Font.Medium
+                            text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
+                        }
+                        onClicked: installPlugin()
+                        secondary: true
+                        preferredWidth: installTextSize.width + JamiTheme.buttontextWizzardPadding
+                        text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
+                        fontSize: 15
+                    }
+                }
+                RowLayout {
+                    spacing: 10
+
+                    CachedImage {
+                        id: icon
+                        Component.onCompleted: {
+                            pluginBackground = PluginStoreListModel.computeAverageColorOfImage("file://" + UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg');
+                        }
+                        width: 50
+                        height: 50
+                        downloadUrl: PluginAdapter.baseUrl + "/icons/" + pluginId
+                        fileExtension: '.svg'
+                        localPath: UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg'
+                    }
+                    ColumnLayout {
+                        Label {
+                            text: pluginTitle
+                            font.kerning: true
+                            color: JamiTheme.textColor
+                            font.pointSize: JamiTheme.settingsFontSize
+                            verticalAlignment: Text.AlignVCenter
+                        }
+                        Label {
+                            color: JamiTheme.textColor
+                            text: pluginShortDescription
+                            font.kerning: true
+                            font.pointSize: JamiTheme.settingsFontSize
+                            verticalAlignment: Text.AlignVCenter
+                        }
+                    }
+                }
+            }
+        }
+        Rectangle {
+            anchors.fill: parent
+            color: JamiTheme.pluginViewBackgroundColor
+        }
+        Flickable {
+            anchors.fill: parent
+            anchors.margins: 10
+            contentWidth: description.width
+            contentHeight: description.height
+            clip: true
+            flickableDirection: Flickable.VerticalFlick
+            ScrollBar.vertical: ScrollBar {
+                id: scrollBar
+                policy: ScrollBar.AsNeeded
+            }
+            Text {
+                id: description
+                width: parent.width
+                color: JamiTheme.textColor
+                text: pluginDescription
+                wrapMode: Text.WordWrap
+            }
+        }
+        footer: Control {
+            padding: 10
+            background: Rectangle {
+                color: JamiTheme.pluginViewBackgroundColor
+            }
+            contentItem: Text {
+                Layout.fillWidth: true
+                Layout.preferredHeight: implicitHeight
+                Layout.topMargin: 8
+                Layout.leftMargin: 8
+                color: JamiTheme.textColor
+
+                font.pointSize: JamiTheme.settingsFontSize
+                font.kerning: true
+                text: "By " + pluginAuthor
+                verticalAlignment: Text.AlignVCenter
+            }
+        }
+
+        DropShadow {
+            z: 2
+            visible: hovered
+            width: root.width
+            height: root.height
+            radius: 16
+            color: Qt.rgba(0, 0.34, 0.6, 0.16)
+            source: root
+            transparentBorder: true
+            samples: radius + 1
+            cached: true
+        }
+    }
+    function installPlugin() {
+        if (isDownloading()) {
+            return;
+        }
+        PluginAdapter.installRemotePlugin(pluginId);
+    }
+
+    function isDownloading() {
+        return pluginStatus === PluginStatus.DOWNLOADING;
+    }
+}
diff --git a/src/app/settingsview/components/PluginItemDelegate.qml b/src/app/settingsview/components/PluginItemDelegate.qml
index 578808bb8307516122f744582f2ed5658d2bfcfb..e273d8772beaf2313ce764924c544109798ea64c 100644
--- a/src/app/settingsview/components/PluginItemDelegate.qml
+++ b/src/app/settingsview/components/PluginItemDelegate.qml
@@ -77,21 +77,7 @@ ItemDelegate {
                 text: pluginName === "" ? pluginId : pluginName
                 verticalAlignment: Text.AlignVCenter
             }
-            MaterialButton {
-                id: update
-                TextMetrics {
-                    id: updateTextSize
-                    font.weight: Font.Bold
-                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
-                    font.capitalization: Font.AllUppercase
-                    text: JamiStrings.updatePlugin
-                }
-                visible: false
-                secondary: true
-                preferredWidth: updateTextSize.width
-                text: JamiStrings.updatePlugin
-                fontSize: 15
-            }
+
             ToggleSwitch {
                 id: loadSwitch
                 Layout.fillHeight: true
diff --git a/src/app/settingsview/components/PluginListView.qml b/src/app/settingsview/components/PluginListView.qml
index 487042c4d6edc10b02b26bb5d392589700db6506..1afa2d4cfac87fb6d263fe0fe79de0e69026bae1 100644
--- a/src/app/settingsview/components/PluginListView.qml
+++ b/src/app/settingsview/components/PluginListView.qml
@@ -29,7 +29,6 @@ Rectangle {
 
     property string activePlugin: ""
 
-    visible: false
     color: JamiTheme.secondaryBackgroundColor
 
     ColumnLayout {
@@ -50,6 +49,21 @@ Rectangle {
             verticalAlignment: Text.AlignVCenter
         }
 
+        MaterialButton {
+            id: disableAll
+            TextMetrics {
+                id: disableTextSize
+                font.weight: Font.Bold
+                font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                font.capitalization: Font.AllUppercase
+                text: JamiStrings.disableAll
+            }
+            secondary: true
+            preferredWidth: disableTextSize.width
+            text: JamiStrings.disableAll
+            fontSize: 15
+        }
+
         MaterialButton {
             id: installButton
 
@@ -88,7 +102,6 @@ Rectangle {
             id: pluginList
 
             Layout.fillWidth: true
-            Layout.minimumHeight: 0
             Layout.bottomMargin: 10
             Layout.preferredHeight: childrenRect.height
             clip: true
diff --git a/src/app/settingsview/components/PluginPreferencesView.qml b/src/app/settingsview/components/PluginPreferencesView.qml
index a1f188aa166688ee86454bbbfc575f092b09599b..f8c12e321d20f338df2fe3b1fa6b63b88fcf0a72 100644
--- a/src/app/settingsview/components/PluginPreferencesView.qml
+++ b/src/app/settingsview/components/PluginPreferencesView.qml
@@ -178,7 +178,9 @@ Rectangle {
                     "buttonCallBacks": [function () {
                             pluginPreferencesView.visible = false;
                             PluginModel.uninstallPlugin(pluginId);
-                            installedPluginsModel.removePlugin(index);
+                            PluginListModel.removePlugin(index);
+                            var pluginPath = pluginId.split('/');
+                            PluginListModel.setVersionStatus(pluginPath[pluginPath.length - 1], PluginStatus.INSTALLABLE);
                         }]
                 })
         }
diff --git a/src/app/settingsview/components/PluginSettingsPage.qml b/src/app/settingsview/components/PluginSettingsPage.qml
index 6b177e94bb5a3a82cc8b8cd356978dca766c2513..6a3a28c67fa7e8bd91b7003291c0022f80cf82c8 100644
--- a/src/app/settingsview/components/PluginSettingsPage.qml
+++ b/src/app/settingsview/components/PluginSettingsPage.qml
@@ -41,12 +41,9 @@ SettingsPageBase {
             Layout.preferredWidth: root.width
             spacing: JamiTheme.settingsCategorySpacing
         }
-        // View of installed plugins
         PluginListView {
             id: pluginListView
 
-            visible: PluginAdapter.isEnabled
-
             Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
             Layout.preferredWidth: parent.width
             Layout.minimumHeight: 0
diff --git a/src/app/settingsview/components/PluginStoreListView.qml b/src/app/settingsview/components/PluginStoreListView.qml
new file mode 100644
index 0000000000000000000000000000000000000000..a47adc8de82bc29e4d21b988269c9fa9b5233de5
--- /dev/null
+++ b/src/app/settingsview/components/PluginStoreListView.qml
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ * Author: Xavier Jouslin de Noray  <xjouslindenoray@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.platform
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+import "../../commoncomponents"
+
+ColumnLayout {
+    function installPlugin() {
+        var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
+                "title": JamiStrings.selectPluginInstall,
+                "fileMode": JamiFileDialog.OpenFile,
+                "folder": StandardPaths.writableLocation(StandardPaths.DownloadLocation),
+                "nameFilters": [JamiStrings.pluginFiles, JamiStrings.allFiles]
+            });
+        dlg.fileAccepted.connect(function (file) {
+                var url = UtilsAdapter.getAbsPath(file.toString());
+                PluginModel.installPlugin(url, true);
+                PluginListModel.addPlugin();
+            });
+    }
+    RowLayout {
+        Layout.fillWidth: true
+        Layout.fillHeight: true
+        Label {
+            Layout.fillWidth: true
+            Layout.preferredHeight: 25
+
+            text: JamiStrings.pluginStoreTitle
+            font.pointSize: JamiTheme.headerFontSize
+            font.kerning: true
+            color: JamiTheme.textColor
+
+            horizontalAlignment: Text.AlignLeft
+            verticalAlignment: Text.AlignVCenter
+        }
+        RowLayout {
+            Layout.alignment: Qt.AlignRight
+            MaterialButton {
+                id: installManually
+
+                TextMetrics {
+                    id: installManuallyTextSize
+                    font.weight: Font.Black
+                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                    font.capitalization: Font.Capitalize
+                    text: JamiStrings.installManually
+                }
+                secondary: true
+                preferredWidth: installManuallyTextSize.width
+                text: JamiStrings.installManually
+                toolTipText: JamiStrings.installManually
+                fontSize: 15
+                onClicked: installPlugin()
+            }
+        }
+    }
+
+    Flow {
+        id: pluginStoreList
+
+        Layout.fillWidth: true
+        spacing: 20
+        Layout.preferredHeight: childrenRect.height
+        clip: true
+        Repeater {
+            model: PluginStoreListModel
+
+            delegate: PluginAvailableDelagate {
+                id: pluginItemDelegate
+
+                width: 350
+                height: 400
+                pluginId: Id
+                pluginTitle: Title
+                pluginIcon: IconPath
+                pluginBackground: Background === '' ? JamiTheme.backgroundColor : Background
+                pluginDescription: Description
+                pluginAuthor: Author
+                pluginShortDescription: ""
+                pluginStatus: Status
+            }
+        }
+    }
+}
diff --git a/src/libclient/api/pluginmodel.h b/src/libclient/api/pluginmodel.h
index 7d58c7ebd55773aa68484a525274df004d5453dc..65fe7e375822bc51caf43e5e3ce9c9299e9afdff 100644
--- a/src/libclient/api/pluginmodel.h
+++ b/src/libclient/api/pluginmodel.h
@@ -38,8 +38,10 @@ namespace plugin {
  */
 struct PluginDetails
 {
+    QString id = "";
     QString name = "";
     QString path = "";
+    QString version = "";
     QString iconPath = "";
     bool loaded = false;
 };
@@ -102,6 +104,25 @@ public:
      */
     Q_INVOKABLE bool uninstallPlugin(const QString& rootPath);
 
+    /**
+     * @brief get the plugin path
+     * @param pluginId
+     * @return plugin path
+     */
+    QString getPluginPath(const QString& pluginId);
+
+    /**
+     * @brief fetch all plugins path and id
+     *
+     */
+    void setPluginsPath();
+
+    /**
+     * @brief get all plugins id
+     * @return plugins id
+     */
+    VectorString getPluginsId();
+
     /**
      * Load plugin
      * @return true if plugin was succesfully loaded
@@ -184,6 +205,9 @@ public:
 Q_SIGNALS:
     void chatHandlerStatusUpdated(bool isVisible);
     void modelUpdated();
+
+private:
+    MapStringString pluginsPath_ = {};
 };
 
 } // namespace api
diff --git a/src/libclient/pluginmodel.cpp b/src/libclient/pluginmodel.cpp
index 7b11cd4701e5c887d0fe78cb115866d8a4677a1a..3b22a64aebfa4549394bb16374f10e1c830a12a4 100644
--- a/src/libclient/pluginmodel.cpp
+++ b/src/libclient/pluginmodel.cpp
@@ -38,13 +38,23 @@
 // LRC
 #include "dbus/pluginmanager.h"
 
+enum PluginInstallStatus {
+    SUCCESS = 0,
+    PLUGIN_ALREADY_INSTALLED = 100,
+    PLUGIN_OLD_VERSION = 200,
+    SIGNATURE_VERIFICATION_FAILED = 300,
+    CERTIFICATE_VERIFICATION_FAILED = 400,
+    INVALID_PLUGIN = 500,
+} PluginInstallStatus;
 namespace lrc {
 
 using namespace api;
 
 PluginModel::PluginModel()
     : QObject()
-{}
+{
+    setPluginsPath();
+}
 
 PluginModel::~PluginModel() {}
 
@@ -87,11 +97,15 @@ PluginModel::getPluginDetails(const QString& path)
     MapStringString details = PluginManager::instance().getPluginDetails(path);
     plugin::PluginDetails result;
     if (!details.empty()) {
+        result.id = details["id"];
         result.name = details["name"];
         result.path = path;
         result.iconPath = details["iconPath"];
+        result.version = details["version"];
+    }
+    if (!pluginsPath_.contains(result.id)) {
+        pluginsPath_[result.id] = path;
     }
-
     VectorString loadedPlugins = getLoadedPlugins();
     if (std::find(loadedPlugins.begin(), loadedPlugins.end(), result.path) != loadedPlugins.end()) {
         result.loaded = true;
@@ -106,7 +120,27 @@ PluginModel::installPlugin(const QString& jplPath, bool force)
     if (getPluginsEnabled()) {
         auto result = PluginManager::instance().installPlugin(jplPath, force);
         Q_EMIT modelUpdated();
-        return result;
+        if (result != 0) {
+            switch (result) {
+            case PluginInstallStatus::PLUGIN_ALREADY_INSTALLED:
+                qWarning() << "Plugin already installed";
+                break;
+            case PluginInstallStatus::PLUGIN_OLD_VERSION:
+                qWarning() << "Plugin already installed with a newer version";
+                break;
+            case PluginInstallStatus::SIGNATURE_VERIFICATION_FAILED:
+                qWarning() << "Signature verification failed";
+                break;
+            case PluginInstallStatus::CERTIFICATE_VERIFICATION_FAILED:
+                qWarning() << "Certificate verification failed";
+                break;
+            case PluginInstallStatus::INVALID_PLUGIN:
+                qWarning() << "Invalid plugin";
+                break;
+            }
+        }
+        pluginsPath_[getPluginDetails(jplPath).id] = jplPath;
+        return result == 0;
     }
     return false;
 }
@@ -115,10 +149,37 @@ bool
 PluginModel::uninstallPlugin(const QString& rootPath)
 {
     auto result = PluginManager::instance().uninstallPlugin(rootPath);
+    for (auto plugin : pluginsPath_.keys(rootPath)) {
+        pluginsPath_.remove(plugin);
+    }
     Q_EMIT modelUpdated();
     return result;
 }
 
+QString
+PluginModel::getPluginPath(const QString& pluginId)
+{
+    return pluginsPath_[pluginId];
+}
+
+void
+PluginModel::setPluginsPath()
+{
+    for (auto plugin : getInstalledPlugins()) {
+        auto details = getPluginDetails(plugin);
+        pluginsPath_[details.name] = details.path;
+    }
+}
+
+VectorString
+PluginModel::getPluginsId()
+{
+    if (pluginsPath_.empty()) {
+        setPluginsPath();
+    }
+    return pluginsPath_.keys();
+}
+
 bool
 PluginModel::loadPlugin(const QString& path)
 {