diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index a497c0bb8d618056a1520c57b0779bec7d7c33c8..509339ffecad18e18067d382c0063a2a8c0fc28a 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -208,6 +208,7 @@ namespace std {
 %include "datatransfer.i"
 %include "presencemanager.i"
 %include "videomanager.i"
+%include "plugin_manager_interface.i"
 
 #include "dring/callmanager_interface.h"
 
diff --git a/bin/jni/plugin_manager_interface.i b/bin/jni/plugin_manager_interface.i
new file mode 100644
index 0000000000000000000000000000000000000000..dac0bcbe8858ed4a576f718de4a44e908d29080a
--- /dev/null
+++ b/bin/jni/plugin_manager_interface.i
@@ -0,0 +1,20 @@
+%header %{
+
+#include "dring/dring.h"
+#include "dring/plugin_manager_interface.h"
+%}
+
+namespace DRing {
+bool loadPlugin(const std::string& path);
+bool unloadPlugin(const std::string& path);
+void togglePlugin(const std::string& path, bool toggle);
+std::map<std::string,std::string> getPluginDetails(const std::string& path);
+std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& path);
+bool setPluginPreference(const std::string& path, const std::string& key, const std::string& value);
+std::map<std::string,std::string> getPluginPreferencesValues(const std::string& path);
+bool resetPluginPreferencesValues(const std::string& path);
+std::vector<std::string> listAvailablePlugins();
+std::vector<std::string> listLoadedPlugins();
+int installPlugin(const std::string& jplPath, bool force);
+int uninstallPlugin(const std::string& pluginRootPath);
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index f9f7e3995c2c44298e7f788299a92139ea6d3244..7d6e601f79bd5a1cd7798242611e36d28f851f28 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -161,7 +161,15 @@ libring_la_SOURCES = \
 		generic_io.h \
 		scheduled_executor.h \
 		scheduled_executor.cpp \
-		transport/peer_channel.h
+		transport/peer_channel.h \
+		plugin/pluginloader.h \
+		plugin/pluginloaderdl.cpp \
+		plugin/pluginmanager.h \
+		plugin/pluginmanager.cpp \
+		plugin/mediahandler.h \
+		plugin/callservicemanager.h \
+		plugin/jamipluginmanager.h \
+		plugin/jamipluginmanager.cpp
 
 if HAVE_WIN32
 libring_la_SOURCES += \
diff --git a/src/client/Makefile.am b/src/client/Makefile.am
index 9d9ab36ff1be8211a9f258b614fe196bf0d4fb92..1dad320f59f38356e881a3e3f2c05cf05b9f0802 100644
--- a/src/client/Makefile.am
+++ b/src/client/Makefile.am
@@ -17,6 +17,7 @@ libclient_la_SOURCES = \
 	callmanager.cpp \
 	configurationmanager.cpp \
 	datatransfer.cpp \
+	plugin_manager_interface.cpp \
 	$(PRESENCE_SRC) \
 	$(VIDEO_SRC)
 
diff --git a/src/client/plugin_manager_interface.cpp b/src/client/plugin_manager_interface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f7e9ef6684901cf0ea297e3424d1ddd59add32d1
--- /dev/null
+++ b/src/client/plugin_manager_interface.cpp
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (C) 2004-2020 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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include "plugin_manager_interface.h"
+#include "manager.h"
+#include "plugin/jamipluginmanager.h"
+#include "logger.h"
+#include <iostream>
+
+namespace DRing {
+//
+bool
+loadPlugin(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().loadPlugin(path);
+}
+
+bool
+unloadPlugin(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().unloadPlugin(path);
+}
+
+void
+togglePlugin(const std::string& path, bool toggle){
+    jami::Manager::instance().getJamiPluginManager().togglePlugin(path,toggle);
+}
+
+std::map<std::string,std::string>
+getPluginDetails(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().getPluginDetails(path);
+}
+
+std::vector<std::map<std::string,std::string>>
+getPluginPreferences(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().getPluginPreferences(path);
+}
+
+bool
+setPluginPreference(const std::string& path, const std::string& key, const std::string& value) {
+    return jami::Manager::instance().getJamiPluginManager().setPluginPreference(path, key, value);
+}
+
+std::map<std::string,std::string>
+getPluginPreferencesValues(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(path);
+}
+bool
+resetPluginPreferencesValues(const std::string& path){
+    return jami::Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(path);
+}
+
+std::vector<std::string>
+listAvailablePlugins() {
+    return jami::Manager::instance().getJamiPluginManager().listAvailablePlugins();
+}
+
+std::vector<std::string>
+listLoadedPlugins() {
+    return jami::Manager::instance().getJamiPluginManager().listLoadedPlugins();
+}
+
+int installPlugin(const std::string& jplPath, bool force) {
+    return jami::Manager::instance().getJamiPluginManager().installPlugin(jplPath, force);
+}
+
+int uninstallPlugin(const std::string& pluginRootPath) {
+    return jami::Manager::instance().getJamiPluginManager().uninstallPlugin(pluginRootPath);
+}
+}
diff --git a/src/dring/plugin_manager_interface.h b/src/dring/plugin_manager_interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..6639bc24775cc5a27f2fe4c2dff4c6d0330a6816
--- /dev/null
+++ b/src/dring/plugin_manager_interface.h
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (C) 2004-2020 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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "def.h"
+
+#include <string>
+#include <vector>
+#include <map>
+
+namespace DRing {
+DRING_PUBLIC bool loadPlugin(const std::string& path);
+DRING_PUBLIC bool unloadPlugin(const std::string& path);
+DRING_PUBLIC void togglePlugin(const std::string& path, bool toggle);
+DRING_PUBLIC std::map<std::string,std::string> getPluginDetails(const std::string& path);
+DRING_PUBLIC std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& path);
+DRING_PUBLIC bool setPluginPreference(const std::string& path, const std::string& key, const std::string& value);
+DRING_PUBLIC std::map<std::string,std::string> getPluginPreferencesValues(const std::string& path);
+DRING_PUBLIC bool resetPluginPreferencesValues(const std::string& path);
+DRING_PUBLIC std::vector<std::string> listAvailablePlugins();
+DRING_PUBLIC std::vector<std::string> listLoadedPlugins();
+DRING_PUBLIC int installPlugin(const std::string& jplPath, bool force);
+DRING_PUBLIC int uninstallPlugin(const std::string& pluginRootPath);
+}
+
diff --git a/src/fileutils.h b/src/fileutils.h
index 447204d5a8476c93e119b3c052789d17077714e6..57320be430867f84ecf9e802268e85ac34328beb 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -46,12 +46,14 @@
 
 #ifndef _WIN32
 #include <sys/stat.h>           // mode_t
-#define DIR_SEPARATOR_STR "/"   // Directory separator char
-#define DIR_SEPARATOR_CH  '/'   // Directory separator string
+#define DIR_SEPARATOR_STR "/"   // Directory separator string
+#define DIR_SEPARATOR_CH  '/'   // Directory separator char
+#define DIR_SEPARATOR_STR_ESC "\\/"   // Escaped directory separator string
 #else
 #define mode_t unsigned
-#define DIR_SEPARATOR_STR "\\"  // Directory separator char
-#define DIR_SEPARATOR_CH  '\\'  // Directory separator string
+#define DIR_SEPARATOR_STR "\\"  // Directory separator string
+#define DIR_SEPARATOR_CH  '\\'  // Directory separator char
+#define DIR_SEPARATOR_STR_ESC "\\\\"   // Escaped directory separator string
 #endif
 
 namespace jami { namespace fileutils {
diff --git a/src/manager.cpp b/src/manager.cpp
index a4b271196162e8a297509abc912659f22fe1401a..b1bcd148f3f88a55c7ca0d9dd7123a8de61452c4 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -63,6 +63,7 @@ using random_device = dht::crypto::random_device;
 #include "audio/sound/tonelist.h"
 #include "audio/sound/dtmf.h"
 #include "audio/ringbufferpool.h"
+#include "plugin/jamipluginmanager.h"
 
 #ifdef ENABLE_VIDEO
 #include "client/videomanager.h"
@@ -415,6 +416,8 @@ struct Manager::ManagerPimpl
 #endif
 
     std::unique_ptr<SIPVoIPLink> sipLink_;
+    /* Jami Plugin Manager */
+    JamiPluginManager jami_plugin_manager;
 };
 
 Manager::ManagerPimpl::ManagerPimpl(Manager& base)
@@ -3099,6 +3102,11 @@ Manager::sipVoIPLink() const
 }
 
 
+JamiPluginManager& Manager::getJamiPluginManager() const
+{
+    return pimpl_->jami_plugin_manager;
+}
+
 std::map<std::string, std::string>
 Manager::getNearbyPeers(const std::string& accountID)
 {
diff --git a/src/manager.h b/src/manager.h
index c50b7015937a811f1663dce8af6bb3a3b4a718c0..5db538e8e829c4f291aaea219cff012730b50a4c 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -61,6 +61,7 @@ class IceTransportFactory;
 class DataTransferFacade;
 class JamiAccount;
 class SIPVoIPLink;
+class JamiPluginManager;
 
 static constexpr uint64_t DRING_ID_MAX_VAL = 9007199254740992;
 
@@ -904,6 +905,7 @@ class DRING_TESTABLE Manager {
         std::vector<DRing::Message> getLastMessages(const std::string& accountID, const uint64_t& base_timestamp);
 
         SIPVoIPLink& sipVoIPLink() const;
+        JamiPluginManager& getJamiPluginManager() const;
 
 private:
         Manager();
diff --git a/src/plugin/jamiplugin.h b/src/plugin/jamiplugin.h
new file mode 100644
index 0000000000000000000000000000000000000000..563bbda2aa358180724369e2239ab728f217b229
--- /dev/null
+++ b/src/plugin/jamiplugin.h
@@ -0,0 +1,109 @@
+/*
+ *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+#define EXTERNAL_C_LINKAGE extern "C"
+#define C_INTERFACE_START EXTERNAL_C_LINKAGE {
+#define C_INTERFACE_END }
+#else
+#define C_LINKAGE
+#define C_INTERFACE_START
+#define C_INTERFACE_END
+#endif
+
+#define JAMI_PLUGIN_ABI_VERSION 1 /* 0 doesn't exist, considered as error */
+#define JAMI_PLUGIN_API_VERSION 1 /* 0 doesn't exist, considered as error */
+
+C_INTERFACE_START;
+
+typedef struct JAMI_PluginVersion {
+  /* plugin is not loadable if this number differs from one
+   * stored in the plugin loader */
+  uint32_t abi;
+
+  /* a difference on api number may be acceptable, see the loader code */
+  uint32_t api;
+} JAMI_PluginVersion;
+
+struct JAMI_PluginAPI;
+
+/* JAMI_PluginCreateFunc parameters */
+typedef struct JAMI_PluginObjectParams {
+  const JAMI_PluginAPI *pluginApi; /* this API */
+  const char *type;
+} JAMI_PluginObjectParams;
+
+typedef void *(*JAMI_PluginCreateFunc)(JAMI_PluginObjectParams *params,
+                                       void *closure);
+
+typedef void (*JAMI_PluginDestroyFunc)(void *object, void *closure);
+
+/* JAMI_PluginAPI.registerObjectFactory data */
+typedef struct JAMI_PluginObjectFactory {
+  JAMI_PluginVersion version;
+  void *closure; /* closure for create */
+  JAMI_PluginCreateFunc create;
+  JAMI_PluginDestroyFunc destroy;
+} JAMI_PluginObjectFactory;
+
+/* Plugins exposed API prototype */
+typedef int32_t (*JAMI_PluginFunc)(const JAMI_PluginAPI *api, const char *name,
+                                   void *data);
+
+/* JAMI_PluginInitFunc parameters.
+ * This structure is filled by the Plugin manager.
+ * For backware compatibility, never c
+ */
+typedef struct JAMI_PluginAPI {
+  JAMI_PluginVersion version; /* structure version, always the first data */
+  void *context;              /* opaque structure used by next functions */
+
+  /* API usable by plugin implementors */
+  JAMI_PluginFunc registerObjectFactory;
+  JAMI_PluginFunc invokeService;
+  JAMI_PluginFunc manageComponent;
+} JAMI_PluginAPI;
+
+typedef void (*JAMI_PluginExitFunc)(void);
+
+typedef JAMI_PluginExitFunc (*JAMI_PluginInitFunc)(const JAMI_PluginAPI *api);
+
+C_INTERFACE_END;
+
+#define JAMI_DYN_INIT_FUNC_NAME "JAMI_dynPluginInit"
+#define JAMI_PLUGIN_INIT_STATIC(fname, pname) JAMI_PLUGIN_INIT(fname, pname)
+#define JAMI_PLUGIN_INIT_DYNAMIC(pname)                                        \
+  JAMI_PLUGIN_INIT(JAMI_dynPluginInit, pname)
+
+/* Define here platform dependent way to export a declaration x to the dynamic
+ * loading system.
+ */
+
+/* Default case (like POSIX/.so) */
+
+#define JAMI_PLUGIN_INIT(fname, pname)                                         \
+  (EXTERNAL_C_LINKAGE JAMI_PluginExitFunc fname(const JAMI_PluginAPI *pname))
+#define JAMI_PLUGIN_EXIT(fname) (EXTERNAL_C_LINKAGE void fname(void))
+
diff --git a/src/plugin/jamipluginmanager.cpp b/src/plugin/jamipluginmanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb23e308f22b6b95ce679c406696d60f8c7d6a21
--- /dev/null
+++ b/src/plugin/jamipluginmanager.cpp
@@ -0,0 +1,452 @@
+/*
+ *  Copyright (C) 2004-2020 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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include "jamipluginmanager.h"
+#include "logger.h"
+
+#include <sstream>
+#include <fstream>
+#include <regex>
+#include <stdexcept>
+
+//Manager
+#include "manager.h"
+
+extern "C" {
+#include <archive.h>
+}
+
+#include <json/json.h>
+#include <msgpack.hpp>
+
+#if defined(__arm__)
+  #if defined(__ARM_ARCH_7A__)
+    #define ABI "armeabi-v7a"
+  #else
+   #define ABI "armeabi"
+  #endif
+#elif defined(__i386__)
+   #define ABI "x86"
+#elif defined(__x86_64__)
+   #define ABI "x86_64"
+#elif defined(__mips64)  /* mips64el-* toolchain defines __mips__ too */
+   #define ABI "mips64"
+#elif defined(__mips__)
+   #define ABI "mips"
+#elif defined(__aarch64__)
+   #define ABI "arm64-v8a"
+#else
+   #define ABI "unknown"
+#endif
+
+#define	PLUGIN_ALREADY_INSTALLED	  100	/* Plugin already installed with the same version */
+#define	PLUGIN_OLD_VERSION	  200	/* Plugin already installed with a newer version */
+
+namespace jami {
+
+std::map<std::string, std::string> checkManifestJsonContentValidity(const Json::Value& root) {
+    std::string name = root.get("name", "").asString();
+    std::string description = root.get("description", "").asString();
+    std::string version = root.get("version", "").asString();
+    if(!name.empty() || !version.empty()){
+        return {
+            {"name", name},
+            {"description", description},
+            {"version", version},
+        };
+    } else {
+        throw std::runtime_error("plugin manifest file: bad format");
+    }
+}
+
+std::map<std::string, std::string> checkManifestValidity(std::istream& stream) {
+    Json::Value root;
+    Json::CharReaderBuilder rbuilder;
+    rbuilder["collectComments"] = false;
+    std::string errs;
+
+    bool ok = Json::parseFromStream(rbuilder, stream, &root, &errs);
+
+    if(ok) {
+        return checkManifestJsonContentValidity(root);
+    } else{
+        throw std::runtime_error("failed to parse the plugin manifest file");
+    }
+}
+
+std::map<std::string, std::string> checkManifestValidity(const std::vector<uint8_t>& vec) {
+    Json::Value root;
+    std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder{}.newCharReader());
+    std::string errs;
+
+    bool ok = json_Reader->parse(reinterpret_cast<const char *>(vec.data()),
+                                 reinterpret_cast<const char *>(vec.data()+vec.size()),
+                                 &root,&errs);
+
+    if(ok) {
+        return checkManifestJsonContentValidity(root);
+    } else{
+        throw std::runtime_error("failed to parse the plugin manifest file");
+    }
+}
+
+static const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
+static const std::regex SO_REGEX("([a-z0-9]+(?:[_-]?[a-z0-9]+)*)" DIR_SEPARATOR_STR_ESC "([a-z0-9_]+\\.(so|dll))");
+
+std::pair<bool,const std::string>
+uncompressJplFunction(const std::string& relativeFileName)
+{
+    std::smatch match;
+    if (relativeFileName == "manifest.json" || std::regex_match(relativeFileName, DATA_REGEX)){
+        return std::make_pair(true, relativeFileName);
+    } else if (regex_search(relativeFileName, match, SO_REGEX) == true) {
+        if (match.str(1) == ABI) {
+            return std::make_pair(true, match.str(2));
+        }
+    }
+    return std::make_pair(false, std::string{""});
+}
+
+std::string convertArrayToString(const Json::Value& jsonArray)
+{
+    std::string stringArray = "[";
+
+    for(int i=0; i< static_cast<int>(jsonArray.size()) - 1; i++) {
+        if(jsonArray[i].isString()) {
+            stringArray+=jsonArray[i].asString()+",";
+        } else if(jsonArray[i].isArray()) {
+            stringArray+=convertArrayToString(jsonArray[i])+",";
+        }
+    }
+
+    int lastIndex = static_cast<int>(jsonArray.size()) - 1;
+    if(jsonArray[lastIndex].isString()) {
+        stringArray+=jsonArray[lastIndex].asString();
+    }
+
+    stringArray+="]";
+
+    return stringArray;
+}
+
+std::map<std::string, std::string> parsePreferenceConfig(const Json::Value &jsonPreference, const std::string &type)
+{
+    std::map<std::string, std::string> preferenceMap;
+    const auto& members = jsonPreference.getMemberNames();
+    // Insert other fields
+    for(const auto& member : members) {
+        const Json::Value& value = jsonPreference[member];
+        if(value.isString()) {
+            preferenceMap.emplace(member, jsonPreference[member].asString());
+        } else if (value.isArray()) {
+            preferenceMap.emplace(member, convertArrayToString(jsonPreference[member]));
+        }
+    }
+    return preferenceMap;
+}
+
+std::map<std::string, std::string> JamiPluginManager::getPluginDetails(const std::string &rootPath)
+{
+    auto detailsIt = pluginDetailsMap_.find(rootPath);
+    if (detailsIt != pluginDetailsMap_.end()) {
+        return detailsIt->second;
+    }
+
+    std::map<std::string, std::string> details = parseManifestFile(manifestPath(rootPath));
+    details["iconPath"] = rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "icon.png";
+    details["soPath"] = rootPath + DIR_SEPARATOR_CH + "lib" + details["name"] + ".so";
+    detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
+    return detailsIt->second;
+}
+
+std::vector<std::string> JamiPluginManager::listAvailablePlugins()
+{
+    std::string pluginsPath = fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins";
+    std::vector<std::string> pluginsPaths = fileutils::readDirectory(pluginsPath);
+    std::for_each(pluginsPaths.begin(), pluginsPaths.end(),
+                  [&pluginsPath](std::string& x){ x = pluginsPath + DIR_SEPARATOR_CH + x;});
+    auto predicate = [this](std::string path){ return !checkPluginValidity(path);};
+    auto returnIterator = std::remove_if(pluginsPaths.begin(),pluginsPaths.end(),predicate);
+    pluginsPaths.erase(returnIterator,std::end(pluginsPaths));
+    return pluginsPaths;
+}
+
+int JamiPluginManager::installPlugin(const std::string &jplPath, bool force)
+{
+    int r{0};
+    if(fileutils::isFile(jplPath)) {
+        try{
+            auto manifestMap = readPluginManifestFromArchive(jplPath);
+            std::string name = manifestMap["name"];
+            std::string version = manifestMap["version"];
+            const std::string destinationDir{fileutils::get_data_dir()
+                                             + DIR_SEPARATOR_CH + "plugins"
+                                             + DIR_SEPARATOR_CH + name};
+            // Find if there is an existing version of this plugin
+            const auto alreadyInstalledManifestMap = parseManifestFile(manifestPath(destinationDir));
+
+            if (!alreadyInstalledManifestMap.empty()) {
+                if (force) {
+                    r = uninstallPlugin(destinationDir);
+                    if(r == 0) {
+                        archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
+                    }
+                } else {
+                    std::string installedVersion = alreadyInstalledManifestMap.at("version");
+                    if (version > installedVersion) {
+                        r = uninstallPlugin(destinationDir);
+                        if(r == 0) {
+                            archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
+                        }
+                    } else if (version == installedVersion){
+                        // An error code of 100 to know that this version is the same as the one installed
+                        r = PLUGIN_ALREADY_INSTALLED;
+                    } else {
+                        // An error code of 100 to know that this version is older than the one installed
+                        r = PLUGIN_OLD_VERSION;
+                    }
+                }
+            } else {
+                archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
+            }
+        } catch(const std::exception& e) {
+            JAMI_ERR() << e.what();
+        }
+    }
+    return r;
+}
+
+int JamiPluginManager::uninstallPlugin(const std::string &rootPath)
+{
+    if(checkPluginValidity(rootPath)) {
+        return fileutils::removeAll(rootPath);
+    } else {
+        return -1;
+    }
+}
+
+bool JamiPluginManager::loadPlugin(const std::string &rootPath)
+{
+    try {
+        return pm_.load(getPluginDetails(rootPath).at("soPath"));
+    } catch(const std::exception& e) {
+        JAMI_ERR() << e.what();
+        return false;
+    }
+}
+
+bool JamiPluginManager::unloadPlugin(const std::string &rootPath)
+{
+    try {
+        return pm_.unload(getPluginDetails(rootPath).at("soPath"));
+    } catch(const std::exception& e) {
+        JAMI_ERR() << e.what();
+        return false;
+    }
+}
+
+void JamiPluginManager::togglePlugin(const std::string &rootPath, bool toggle)
+{
+    try {
+        std::string soPath = getPluginDetails(rootPath).at("soPath");
+        // remove the previous plugin object if it was registered
+        pm_.destroyPluginComponents(soPath);
+        // If toggle, register a new instance of the plugin
+        // function
+        if(toggle){
+            pm_.callPluginInitFunction(soPath);
+        }
+    } catch (const std::exception& e) {
+        JAMI_ERR() << e.what();
+    }
+}
+
+std::vector<std::string> JamiPluginManager::listLoadedPlugins() const
+{
+    std::vector<std::string> loadedSoPlugins = pm_.listLoadedPlugins();
+    std::vector<std::string> loadedPlugins{};
+    loadedPlugins.reserve(loadedSoPlugins.size());
+    std::transform(loadedSoPlugins.begin(), loadedSoPlugins.end(), std::back_inserter(loadedPlugins),
+    [this](const std::string& soPath) {
+        return getRootPathFromSoPath(soPath);
+    });
+    return loadedPlugins;
+}
+
+std::vector<std::map<std::string, std::string> > JamiPluginManager::getPluginPreferences(const std::string &rootPath)
+{
+    const std::string preferenceFilePath = getPreferencesConfigFilePath(rootPath);
+    std::ifstream file(preferenceFilePath);
+    Json::Value root;
+    Json::CharReaderBuilder rbuilder;
+    rbuilder["collectComments"] = false;
+    std::string errs;
+    std::set<std::string> keys;
+    std::vector<std::map<std::string, std::string>> preferences;
+    if(file) {
+        bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
+        if(ok && root.isArray()) {
+            for(int i=0; i< static_cast<int>(root.size()); i++) {
+                const Json::Value jsonPreference = root[i];
+                std::string category = jsonPreference.get("category", "NoCategory").asString();
+                std::string type = jsonPreference.get("type", "None").asString();
+                std::string key = jsonPreference.get("key", "None").asString();
+                if(type != "None" && key != "None") {
+                    if(keys.find(key) == keys.end()) {
+                       const auto& preferenceAttributes = parsePreferenceConfig(jsonPreference, type);
+                       // If the parsing of the attributes was successful, commit the map and the key
+                       if(!preferenceAttributes.empty()) {
+                           preferences.push_back(std::move(preferenceAttributes));
+                           keys.insert(key);
+                       }
+                    }
+                }
+            }
+        } else {
+            JAMI_ERR() << "PluginPreferencesParser:: Failed to parse preferences.json for plugin: "
+                       << preferenceFilePath;
+        }
+    }
+
+    return preferences;
+}
+
+bool JamiPluginManager::setPluginPreference(const std::string &rootPath, const std::string &key, const std::string &value)
+{
+    bool returnValue = true;
+    std::map<std::string, std::string> pluginPreferencesMap = getPluginPreferencesValuesMap(rootPath);
+    // Using [] instead of insert to get insert or update effect
+    pluginPreferencesMap[key] = value;
+
+    {
+        const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
+        std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
+        if(!fs.good()) {
+            return false;
+        }
+        try {
+            std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
+            msgpack::pack(fs, pluginPreferencesMap);
+        } catch (const std::exception& e) {
+            returnValue = false;
+            JAMI_ERR() << e.what();
+        }
+    }
+
+    return returnValue;
+}
+
+std::map<std::string, std::string> JamiPluginManager::getPluginPreferencesValuesMap(const std::string &rootPath)
+{
+    const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
+    std::ifstream file(preferencesValuesFilePath, std::ios::binary);
+    std::map<std::string, std::string>  rmap;
+    // If file is accessible
+    if(file.good()) {
+        std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
+        // Get file size
+        std::string str;
+        file.seekg(0, std::ios::end);
+        size_t fileSize = static_cast<size_t>(file.tellg());
+        // If not empty
+        if(fileSize > 0) {
+            // Read whole file content and put it in the string str
+            str.reserve(static_cast<size_t>(file.tellg()));
+            file.seekg(0, std::ios::beg);
+            str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+            file.close();
+            try {
+                // Unpack the string
+                msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
+                // Deserialized object is valid during the msgpack::object_handle instance is alive.
+                msgpack::object deserialized = oh.get();
+                deserialized.convert(rmap);
+            } catch (const std::exception& e) {
+                JAMI_ERR() << e.what();
+            }
+        }
+    }
+
+    return rmap;
+}
+
+bool JamiPluginManager::resetPluginPreferencesValuesMap(const std::string &rootPath)
+{
+    bool returnValue = true;
+    std::map<std::string, std::string> pluginPreferencesMap{};
+
+    {
+        const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
+        std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
+        if(!fs.good()) {
+            return false;
+        }
+        try {
+            std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
+            msgpack::pack(fs, pluginPreferencesMap);
+        } catch (const std::exception& e) {
+            returnValue = false;
+            JAMI_ERR() << e.what();
+        }
+    }
+
+    return returnValue;
+}
+
+std::map<std::string, std::string> JamiPluginManager::readPluginManifestFromArchive(const std::string &jplPath)
+{
+    try {
+        return checkManifestValidity(archiver::readFileFromArchive(jplPath,"manifest.json"));
+    } catch (const std::exception& e) {
+        JAMI_ERR() << e.what();
+    }
+}
+
+std::map<std::string, std::string> JamiPluginManager::parseManifestFile(const std::string &manifestFilePath)
+{
+    std::ifstream file(manifestFilePath);
+    if(file) {
+        try {
+            return checkManifestValidity(file);
+        } catch (const std::exception& e) {
+            JAMI_ERR() << e.what();
+        }
+    }
+
+    return {};
+}
+
+void JamiPluginManager::registerServices()
+{
+    // Register pluginPreferences
+    pm_.registerService("getPluginPreferences", [this](const DLPlugin* plugin, void* data) {
+        auto ppp = static_cast<std::map<std::string, std::string>*>(data);
+        *ppp = getPluginPreferencesValuesMap(
+            getRootPathFromSoPath(plugin->getPath()));
+        return 0;
+    });
+
+    pm_.registerService("getPluginDataPath", [this](const DLPlugin* plugin, void* data) {
+        auto dataPath_ = static_cast<std::string*>(data);
+        dataPath_->assign(dataPath(plugin->getPath()));
+        return 0;
+    });
+}
+
+}
+
diff --git a/src/plugin/jamipluginmanager.h b/src/plugin/jamipluginmanager.h
new file mode 100644
index 0000000000000000000000000000000000000000..34b951216cae70d85c58baaa7b28f401787872ce
--- /dev/null
+++ b/src/plugin/jamipluginmanager.h
@@ -0,0 +1,195 @@
+/*
+ *  Copyright (C) 2004-2020 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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+#include "noncopyable.h"
+#include "fileutils.h"
+#include "archiver.h"
+#include "pluginmanager.h"
+
+#include <vector>
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace jami {
+class JamiPluginManager
+{
+public:
+    JamiPluginManager() {
+        registerServices();
+    }
+    // TODO : improve getPluginDetails
+    /**
+     * @brief getPluginDetails
+     * Parses a manifest file and returns :
+     * The tuple (name, description, version, icon path, so path)
+     * The icon should ideally be 192x192 pixels or better 512x512 pixels
+     * In order to match with android specifications
+     * https://developer.android.com/google-play/resources/icon-design-specifications
+     * Saves the result in a map
+     * @param plugin rootPath (folder of the plugin)
+     * @return map where the keyset is {"name", "description", "iconPath"}
+     */
+    std::map<std::string, std::string> getPluginDetails(const std::string& rootPath);
+
+    /**
+     * @brief listAvailablePlugins
+     * Lists available plugins with valid manifest files
+     * @return list of plugin directory names
+     */
+    std::vector<std::string> listAvailablePlugins();
+
+    /**
+     * @brief installPlugin
+     * Checks if the plugin has a valid manifest, installs the plugin if not previously installed
+     * or if installing a newer version of it
+     * If force is true, we force install the plugin
+     * @param jplPath
+     * @param force
+     * @return + 0 if success
+     * 100 if already installed with similar version
+     * 200 if already installed with newer version
+     * libarchive error codes otherwise
+     */
+    int installPlugin(const std::string& jplPath, bool force);
+
+    /**
+     * @brief uninstallPlugin
+     * Checks if the plugin has a valid manifest then removes plugin folder
+     * @param rootPath
+     * @return 0 if success
+     */
+    int uninstallPlugin(const std::string& rootPath);
+
+    /**
+     * @brief loadPlugin
+     * @param rootPath of the plugin folder
+     * @return true is success
+     */
+    bool loadPlugin(const std::string& rootPath);
+
+    /**
+     * @brief unloadPlugin
+     * @param rootPath of the plugin folder
+     * @return true is success
+     */
+    bool unloadPlugin(const std::string& rootPath);
+
+    /**
+     * @brief togglePlugin
+     * @param rootPath of the plugin folder
+     * @param toggle: if true, register a new instance of the plugin
+     * else, remove the existing instance
+     * N.B: before adding a new instance, remove any existing one
+     */
+    void togglePlugin(const std::string& rootPath, bool toggle);
+
+    /**
+     * @brief listLoadedPlugins
+     * @return vector of rootpaths of the loaded plugins
+     */
+    std::vector<std::string> listLoadedPlugins() const;
+
+    std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& rootPath);
+
+    bool setPluginPreference(const std::string& rootPath,
+                             const std::string& key,
+                             const std::string& value);
+
+    std::map<std::string,std::string>
+    getPluginPreferencesValuesMap(const std::string& rootPath);
+
+    bool resetPluginPreferencesValuesMap(const std::string& rootPath);
+
+private:
+
+    NON_COPYABLE(JamiPluginManager);
+
+    /**
+     * @brief checkPluginValidity
+     * Checks if the plugin has a manifest file with a name and a version
+     * @return true if valid
+     */
+    bool checkPluginValidity(const std::string& rootPath) {
+        return !parseManifestFile(manifestPath(rootPath)).empty();
+    }
+
+    /**
+     * @brief readPluginManifestFromArchive
+     * Reads the manifest file content without uncompressing the whole archive
+     * Maps the manifest data to a map(string, string)
+     * @param jplPath
+     * @return manifest map
+     */
+    std::map<std::string, std::string> readPluginManifestFromArchive(const std::string &jplPath);
+
+    /**
+     * @brief parseManifestFile, parses the manifest file of an installed plugin
+     * @param manifestFilePath
+     * @return manifest map
+     */
+    std::map<std::string, std::string> parseManifestFile(const std::string &manifestFilePath);
+
+    std::string manifestPath(const std::string& rootPath) {
+        return rootPath + DIR_SEPARATOR_CH + "manifest.json";
+    }
+
+    std::string getRootPathFromSoPath(const std::string& soPath) const {
+        return soPath.substr(0,soPath.find_last_of(DIR_SEPARATOR_CH));
+    }
+
+    std::string manifestPath(const std::string& rootPath) const {
+        return rootPath + DIR_SEPARATOR_CH + "manifest.json";
+    }
+
+    std::string dataPath(const std::string& pluginSoPath) const {
+        return getRootPathFromSoPath(pluginSoPath) + DIR_SEPARATOR_CH + "data";
+    }
+
+    /**
+     * @brief getPreferencesConfigFilePath
+     * Returns the plugin preferences config file path from the plugin root path
+     * This is entirely defined by how the plugin files are structured
+     * @param plugin rootPath
+     * @return path of the preferences config
+     */
+    std::string getPreferencesConfigFilePath(const std::string& rootPath) const {
+        return rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "preferences.json";
+    }
+
+    /**
+     * @brief pluginPreferencesValuesFilePath
+     * Returns the plugin preferences values file path from the plugin root path
+     * This is entirely defined by how the plugin files are structured
+     * @param plugin rootPath
+     * @return path of the preferences values
+     */
+    std::string pluginPreferencesValuesFilePath(const std::string& rootPath) const {
+        return rootPath + DIR_SEPARATOR_CH + "preferences.msgpack";
+    }
+
+    void registerServices();
+
+private:
+    PluginManager pm_;
+    std::map<std::string, std::map<std::string, std::string>> pluginDetailsMap_;
+};
+}
+
+
diff --git a/src/plugin/pluginloader.h b/src/plugin/pluginloader.h
new file mode 100644
index 0000000000000000000000000000000000000000..a8b00e30ac378b34ed9251bfdefde6269578f47c
--- /dev/null
+++ b/src/plugin/pluginloader.h
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA.
+ */
+#pragma once
+
+#include "jamiplugin.h"
+#include <dlfcn.h>
+#include <string>
+#include <memory>
+
+namespace jami {
+
+class Plugin {
+public:
+  virtual ~Plugin() = default;
+
+  static Plugin *load(const std::string &path, std::string &error);
+  virtual void *getSymbol(const char *name) const = 0;
+  virtual JAMI_PluginInitFunc getInitFunction() const {
+    return reinterpret_cast<JAMI_PluginInitFunc>(
+        getSymbol(JAMI_DYN_INIT_FUNC_NAME));
+  }
+
+protected:
+  Plugin() = default;
+};
+
+class DLPlugin : public Plugin {
+public:
+    DLPlugin(void *handle, const std::string& path) : handle_(handle, ::dlclose), path_{path} {
+        api_.context = this;
+    }
+
+    virtual ~DLPlugin() { unload();}
+    bool unload() {
+        if(!handle_){
+            return false;
+        }
+        return ::dlclose(handle_.release());
+    }
+
+
+    void *getSymbol(const char *name) const {
+        if (!handle_)
+            return nullptr;
+
+        return ::dlsym(handle_.get(), name);
+    }
+
+    const std::string& getPath() const {
+        return path_;
+    }
+
+public:
+    void* apiContext_;
+    JAMI_PluginAPI api_;
+
+private:
+    std::unique_ptr<void, int (*)(void *)> handle_;
+    const std::string path_;
+};
+
+} // namespace jami
diff --git a/src/plugin/pluginloaderdl.cpp b/src/plugin/pluginloaderdl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..12df5714bafe1a1552fe0b55637d02d7b4f1a2e3
--- /dev/null
+++ b/src/plugin/pluginloaderdl.cpp
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA.
+ */
+
+#include "pluginloader.h"
+
+namespace jami {
+
+Plugin *Plugin::load(const std::string &path, std::string &error) {
+  if (path.empty()) {
+    error = "Empty path";
+    return nullptr;
+  }
+
+  // Clear any existing error
+  ::dlerror();
+
+  void *handle = ::dlopen(path.c_str(), RTLD_NOW);
+  if (!handle) {
+    error += "Failed to load \"" + path + '"';
+
+    std::string dlError = ::dlerror();
+    if (dlError.size())
+      error += " (" + dlError + ")";
+    return nullptr;
+  }
+
+  return new DLPlugin(handle, path);
+}
+
+} // namespace jami
diff --git a/src/plugin/pluginmanager.cpp b/src/plugin/pluginmanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..631271271dca65ea3f1319c4b0de42ad52393e00
--- /dev/null
+++ b/src/plugin/pluginmanager.cpp
@@ -0,0 +1,355 @@
+/*
+ *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA.
+ */
+
+#include "pluginmanager.h"
+#include "logger.h"
+
+#include <utility>
+
+namespace jami {
+
+PluginManager::PluginManager() {
+  pluginApi_.context = reinterpret_cast<void *>(this);
+}
+
+PluginManager::~PluginManager() {
+  for (auto func : exitFuncVec_) {
+    try {
+      (*func)();
+    } catch (...) {
+      JAMI_ERR() << "Exception caught during plugin exit";
+    }
+  }
+
+  dynPluginMap_.clear();
+  exactMatchMap_.clear();
+  wildCardVec_.clear();
+  exitFuncVec_.clear();
+}
+
+bool PluginManager::load(const std::string &path) {
+  // TODO: Resolve symbolic links and make path absolute
+
+  // Don't load the same dynamic library twice
+  if (dynPluginMap_.find(path) != dynPluginMap_.end()) {
+    JAMI_WARN() << "Plugin: already loaded";
+    return true;
+  }
+
+  std::string error;
+  std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
+  if (!plugin) {
+    JAMI_ERR() << "Plugin: " << error;
+    return false;
+  }
+
+  const auto &init_func = plugin->getInitFunction();
+  if (!init_func) {
+    JAMI_ERR() << "Plugin: no init symbol" << error;
+    return false;
+  }
+
+  if (!registerPlugin(plugin))
+    return false;
+
+  dynPluginMap_[path] = std::move(plugin);
+  return true;
+}
+
+bool PluginManager::unload(const std::string& path) {
+    bool returnValue{false};
+    destroyPluginComponents(path);
+    PluginMap::iterator it = dynPluginMap_.find(path);
+    if ( it != dynPluginMap_.end()) {
+        dynPluginMap_.erase(it);
+        returnValue = true;
+    }
+
+    return returnValue;
+}
+
+std::vector<std::string> PluginManager::listLoadedPlugins() const
+{
+    std::vector<std::string> res{};
+    for(const auto& pair : dynPluginMap_) {
+        res.push_back(pair.first);
+    }
+    return res;
+}
+
+void PluginManager::destroyPluginComponents(const std::string &path)
+{
+    auto itComponents = pluginComponentsMap_.find(path);
+    if(itComponents != pluginComponentsMap_.end()) {
+        for(const auto& pair : itComponents->second) {
+            auto clcm = componentsLifeCycleManagers_.find(pair.first);
+            if(clcm != componentsLifeCycleManagers_.end()) {
+                clcm->second.destroyComponent(pair.second);
+            }
+        }
+    }
+}
+
+
+bool PluginManager::callPluginInitFunction(const std::string &path){
+    bool returnValue{false};
+    PluginMap::iterator it = dynPluginMap_.find(path);
+    if ( it != dynPluginMap_.end()) {
+        // Plugin found
+        // Since the Plugin was found it is of type DLPlugin with a valid init symbol
+        std::shared_ptr<DLPlugin> plugin = std::static_pointer_cast<DLPlugin>(it->second);
+        const auto &initFunc = plugin->getInitFunction();
+        JAMI_PluginExitFunc exitFunc = nullptr;
+
+        try {
+            // Call Plugin Init function
+            exitFunc = initFunc(&plugin->api_);
+        } catch (const std::runtime_error &e) {
+            JAMI_ERR() << e.what();
+            return false;
+        }
+
+        if (!exitFunc) {
+            JAMI_ERR() << "Plugin: init failed";
+            returnValue = false;
+        } else {
+            returnValue = true;
+        }
+    }
+
+    return returnValue;
+}
+
+bool PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin) {
+  // Here we know that Plugin is of type DLPlugin with a valid init symbol
+  const auto &initFunc = plugin->getInitFunction();
+  JAMI_PluginExitFunc exitFunc = nullptr;
+
+  DLPlugin* pluginPtr = static_cast<DLPlugin*>(plugin.get());
+
+  pluginPtr->apiContext_ = this;
+  pluginPtr->api_.version = {JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION};
+  pluginPtr->api_.registerObjectFactory = registerObjectFactory_;
+  /**
+   * Implements JAMI_PluginAPI.invokeService().
+   * Must be C accessible.
+   */
+  pluginPtr->api_.invokeService = [](const JAMI_PluginAPI *api,
+                                const char *name, void *data) {
+      auto plugin = static_cast<DLPlugin*>(api->context);
+      auto manager = reinterpret_cast<PluginManager *>(plugin->apiContext_);
+      if (!manager) {
+          JAMI_ERR() << "invokeService called with null plugin API";
+          return -1;
+      }
+
+      return manager->invokeService(plugin, name, data);
+  };
+
+  /**
+   * Implements JAMI_PluginAPI.invokeService().
+   * Must be C accessible.
+   */
+  pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void *data){
+      auto plugin = static_cast<DLPlugin*>(api->context);
+      auto manager = reinterpret_cast<PluginManager *>(plugin->apiContext_);
+      if (!manager) {
+          JAMI_ERR() << "createComponent called with null plugin API";
+          return -1;
+      } else if(!plugin){
+          JAMI_ERR() << "createComponent called with null context";
+          return -1;
+      }
+
+      return manager->manageComponent(plugin, name, data);
+  };
+
+  try {
+      exitFunc = initFunc(&pluginPtr->api_);
+  } catch (const std::runtime_error &e) {
+    JAMI_ERR() << e.what();
+  }
+
+  if (!exitFunc) {
+    tempExactMatchMap_.clear();
+    tempWildCardVec_.clear();
+    JAMI_ERR() << "Plugin: init failed";
+    return false;
+  }
+
+  exitFuncVec_.push_back(exitFunc);
+  exactMatchMap_.insert(tempExactMatchMap_.begin(), tempExactMatchMap_.end());
+  wildCardVec_.insert(wildCardVec_.end(), tempWildCardVec_.begin(),
+                      tempWildCardVec_.end());
+  return true;
+}
+
+bool PluginManager::registerService(const std::string &name,
+                                    ServiceFunction &&func) {
+  services_[name] = std::forward<ServiceFunction>(func);
+  return true;
+}
+
+void PluginManager::unRegisterService(const std::string &name) {
+  services_.erase(name);
+}
+
+int32_t PluginManager::invokeService(const DLPlugin* plugin, const std::string &name, void *data) {
+  const auto &iterFunc = services_.find(name);
+  if (iterFunc == services_.cend()) {
+    JAMI_ERR() << "Services not found: " << name;
+    return -1;
+  }
+
+  const auto &func = iterFunc->second;
+
+  try {
+    return func(plugin, data);
+  } catch (const std::runtime_error &e) {
+    JAMI_ERR() << e.what();
+    return -1;
+  }
+}
+
+int32_t PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name, void *data) {
+    const auto& iter = componentsLifeCycleManagers_.find(name);
+    if(iter == componentsLifeCycleManagers_.end()) {
+        JAMI_ERR() << "Component lifecycle manager not found: " << name;
+        return -1;
+    }
+
+    const auto& componentLifecycleManager = iter->second;
+
+    try {
+        int32_t r =  componentLifecycleManager.takeComponentOwnership(data);
+        if(r == 0) {
+            pluginComponentsMap_[plugin->getPath()].emplace_back(name,data);
+        }
+        return r;
+    } catch(const std::runtime_error &e) {
+        JAMI_ERR() << e.what();
+        return -1;
+    }
+}
+
+/* WARNING: exposed to plugins through JAMI_PluginAPI */
+bool PluginManager::registerObjectFactory(
+    const char *type, const JAMI_PluginObjectFactory &factoryData) {
+  if (!type)
+    return false;
+
+  if (!factoryData.create || !factoryData.destroy)
+    return false;
+
+  // Strict compatibility on ABI
+  if (factoryData.version.abi != pluginApi_.version.abi)
+    return false;
+
+  // Backward compatibility on API
+  if (factoryData.version.api < pluginApi_.version.api)
+    return false;
+
+  const std::string key(type);
+  auto deleter = [factoryData](void *o) {
+    factoryData.destroy(o, factoryData.closure);
+  };
+  ObjectFactory factory = {factoryData, deleter};
+
+  // wildcard registration?
+  if (key == "*") {
+    wildCardVec_.push_back(factory);
+    return true;
+  }
+
+  // fails on duplicate for exactMatch map
+  if (exactMatchMap_.find(key) != exactMatchMap_.end())
+    return false;
+
+  exactMatchMap_[key] = factory;
+  return true;
+}
+
+bool PluginManager::registerComponentManager(const std::string &name,
+                                             ComponentFunction &&takeOwnership,
+                                             ComponentFunction &&destroyComponent)
+{
+    componentsLifeCycleManagers_[name] = {std::forward<ComponentFunction>(takeOwnership),
+                                          std::forward<ComponentFunction>(destroyComponent)};
+    return true;
+}
+
+std::unique_ptr<void, PluginManager::ObjectDeleter>
+PluginManager::createObject(const std::string &type) {
+  if (type == "*")
+    return {nullptr, nullptr};
+
+  JAMI_PluginObjectParams op = {
+      /*.pluginApi = */ &pluginApi_,
+      /*.type = */ type.c_str(),
+  };
+
+  // Try to find an exact match
+  const auto &factoryIter = exactMatchMap_.find(type);
+  if (factoryIter != exactMatchMap_.end()) {
+    const auto &factory = factoryIter->second;
+    auto object = factory.data.create(&op, factory.data.closure);
+    if (object)
+      return {object, factory.deleter};
+  }
+
+  // Try to find a wildcard match
+  for (const auto &factory : wildCardVec_) {
+    auto object = factory.data.create(&op, factory.data.closure);
+    if (object) {
+      // promote registration to exactMatch_
+      // (but keep also wildcard registration for other object types)
+      int32_t res = registerObjectFactory(op.type, factory.data);
+      if (res < 0) {
+        JAMI_ERR() << "failed to register object " << op.type;
+        return {nullptr, nullptr};
+      }
+
+      return {object, factory.deleter};
+    }
+  }
+
+  return {nullptr, nullptr};
+}
+
+/* WARNING: exposed to plugins through JAMI_PluginAPI */
+int32_t PluginManager::registerObjectFactory_(const JAMI_PluginAPI *api,
+                                              const char *type, void *data) {
+  auto manager = reinterpret_cast<PluginManager *>(api->context);
+  if (!manager) {
+    JAMI_ERR() << "registerObjectFactory called with null plugin API";
+    return -1;
+  }
+
+  if (!data) {
+    JAMI_ERR() << "registerObjectFactory called with null factory data";
+    return -1;
+  }
+
+  const auto factory = reinterpret_cast<JAMI_PluginObjectFactory *>(data);
+  return manager->registerObjectFactory(type, *factory) ? 0 : -1;
+}
+
+} // namespace jami
diff --git a/src/plugin/pluginmanager.h b/src/plugin/pluginmanager.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d47925ba82acf2a0488e91c631f166634689bbe
--- /dev/null
+++ b/src/plugin/pluginmanager.h
@@ -0,0 +1,192 @@
+/*
+ *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA.
+ */
+#pragma once
+
+#include "noncopyable.h"
+#include "jamiplugin.h"
+#include "pluginloader.h"
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <inttypes.h>
+
+namespace jami {
+
+class Plugin;
+
+class PluginManager {
+public:
+  using ObjectDeleter = std::function<void(void *)>;
+  using ServiceFunction = std::function<int32_t(const DLPlugin*, void *)>;
+  using ComponentFunction = std::function<int32_t(void*)>;
+  //A vector to a pair<componentType, componentPtr>
+  using ComponentTypePtrVector = std::vector<std::pair<std::string, void*>>;
+private:
+  struct ObjectFactory {
+    JAMI_PluginObjectFactory data;
+    ObjectDeleter deleter;
+  };
+
+  struct ComponentLifeCycleManager{
+      ComponentFunction takeComponentOwnership;
+      ComponentFunction destroyComponent;
+  };
+
+  using PluginMap = std::map<std::string, std::shared_ptr<Plugin>>;
+  using PluginComponentsMap = std::map<std::string, ComponentTypePtrVector>;
+  using ExitFuncVec = std::vector<JAMI_PluginExitFunc>;
+  using ObjectFactoryVec = std::vector<ObjectFactory>;
+  using ObjectFactoryMap = std::map<std::string, ObjectFactory>;
+
+public:
+  PluginManager();
+  ~PluginManager();
+
+  /**
+   * Load a dynamic plugin by filename.
+   *
+   * @param path fully qualified pathname on a loadable plugin binary
+   * @return true if success
+   */
+  bool load(const std::string &path);
+
+  /**
+   * @brief unloads the plugin with pathname path
+   * @param path
+   * @return true if success
+   */
+  bool unload(const std::string& path);
+
+  /**
+   * @brief listLoadedPlugins
+   * @return vector of strings of so files of the loaded plugins
+   */
+  std::vector<std::string> listLoadedPlugins() const;
+
+  /**
+   * @brief destroyPluginComponents
+   * @param path
+   */
+  void destroyPluginComponents(const std::string& path);
+
+  /**
+   * @brief callPluginInitFunction
+   * @param path: plugin path used as an id in the plugin map
+   * @return true if succes
+   */
+  bool callPluginInitFunction(const std::string& path);
+  /**
+   * Register a plugin.
+   *
+   * @param initFunc plugin init function
+   * @return true if success
+   */
+  bool registerPlugin(std::unique_ptr<Plugin>& plugin);
+
+  /**
+   * Register a new service for plugin.
+   *
+   * @param name The service name
+   * @param func The function called by Ring_PluginAPI.invokeService
+   * @return true if success
+   */
+  bool registerService(const std::string &name, ServiceFunction &&func);
+
+  void unRegisterService(const std::string &name);
+
+  /**
+   * Register a new public objects factory.
+   *
+   * @param type unique identifier of the object
+   * @param params object factory details
+   * @return true if success
+   *
+   * Note: type can be the string "*" meaning that the factory
+   * will be called if no exact match factories are found for a given type.
+   */
+  bool registerObjectFactory(const char *type,
+                             const JAMI_PluginObjectFactory &factory);
+  /**
+   * @brief registerComponentManager
+   * Registers a component manager that will have two functions, one to take
+   * ownership of the component and the other one to destroy it
+   * @param name : name of the component manager
+   * @param takeOwnership function that takes ownership on created objet in memory
+   * @param destroyComponent desotry the component
+   * @return true if success
+   */
+  bool registerComponentManager(const std::string& name, ComponentFunction&& takeOwnership,
+                                ComponentFunction&& destroyComponent);
+
+  /**
+   * Create a new plugin's exported object.
+   *
+   * @param type unique identifier of the object to create.
+   * @return unique pointer on created object.
+   */
+  std::unique_ptr<void, ObjectDeleter> createObject(const std::string &type);
+
+private:
+  NON_COPYABLE(PluginManager);
+
+  /**
+   * Implements JAMI_PluginAPI.registerObjectFactory().
+   * Must be C accessible.
+   */
+  static int32_t registerObjectFactory_(const JAMI_PluginAPI *api,
+                                 const char *type, void *data);
+  int32_t invokeService(const DLPlugin* plugin, const std::string &name, void *data);
+
+  int32_t manageComponent(const DLPlugin* plugin, const std::string& name, void *data);
+
+  std::mutex mutex_{};
+  JAMI_PluginAPI pluginApi_ = {
+      {JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION},
+      nullptr, // set by PluginManager constructor
+      registerObjectFactory_,
+      nullptr,
+      nullptr
+  };
+  PluginMap dynPluginMap_{}; // Only dynamic loaded plugins
+  ExitFuncVec exitFuncVec_{};
+  ObjectFactoryMap exactMatchMap_{};
+  ObjectFactoryVec wildCardVec_{};
+
+  // Storage used during plugin initialisation.
+  // Will be copied into previous ones only if the initialisation success.
+  ObjectFactoryMap tempExactMatchMap_{};
+  ObjectFactoryVec tempWildCardVec_{};
+
+  // registered services
+  std::map<std::string, ServiceFunction> services_{};
+  // registered component lifecycle managers
+  std::map<std::string, ComponentLifeCycleManager> componentsLifeCycleManagers_ {};
+
+  // references to plugins components, used for cleanup
+  PluginComponentsMap pluginComponentsMap_ {};
+};
+
+} // namespace jami