diff --git a/frontend-js/package.json b/frontend-js/package.json
index 3ef40c46bae2aa51b177d90f0a69c3c9a0cb0b95..d042631c548bdf2e1572c92cb86070d3abc5f134 100644
--- a/frontend-js/package.json
+++ b/frontend-js/package.json
@@ -22,8 +22,8 @@
     "browserify": "^13.1.1",
     "chai": "^3.5.0",
     "clean-css-cli": "^4.1.10",
-    "del": "^3.0.0",
     "datatables.net": "^1.10.13",
+    "del": "^3.0.0",
     "exorcist": "^0.4.0",
     "file-url": "^2.0.0",
     "istanbul": "0.4.5",
diff --git a/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js b/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
index 033761cb459461969bd3e7476027be35e5222e7e..5ce556159b468e99eed2e403db839f87131446f2 100644
--- a/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
+++ b/frontend-js/src/main/js/gui/leftPanel/GuiUtils.js
@@ -11,6 +11,7 @@ var logger = require('../../logger');
 function GuiUtils(configuration) {
   var self = this;
   self.setConfiguration(configuration);
+  self._tabIdCounter = 0;
 }
 
 GuiUtils.prototype = Object.create(AbstractGuiElement.prototype);
@@ -376,7 +377,7 @@ GuiUtils.prototype.createAliasElement = function (params) {
   div.appendChild(self.createParamLine("Formula: ", alias.getFormula()));
   div.appendChild(self.createArrayParamLine("Former symbols: ", alias.getFormerSymbols()));
   div.appendChild(self.createPostTranslationalModifications("PostTranslational modifications: ", alias
-      .getOther('modifications')));
+    .getOther('modifications')));
   div.appendChild(self.createParamLine("Charge: ", alias.getCharge()));
   div.appendChild(self.createArrayParamLine("Synonyms: ", alias.getSynonyms()));
   div.appendChild(self.createLabelText(alias.getDescription()));
@@ -416,7 +417,7 @@ GuiUtils.prototype.createModifiersLine = function (label, value) {
   return result;
 };
 
-GuiUtils.prototype.createTabMenuObject = function(params) {
+GuiUtils.prototype.createTabMenuObject = function (params) {
   var name = params.name;
   var id = params.id;
   var navigationBar = params.navigationBar;
@@ -434,7 +435,7 @@ GuiUtils.prototype.createTabMenuObject = function(params) {
   if (name !== undefined) {
     navLink.innerHTML = name;
   }
-  navLink.onclick = function() {
+  navLink.onclick = function () {
     $(this).tab('show');
   };
   navLi.appendChild(navLink);
@@ -444,7 +445,7 @@ GuiUtils.prototype.createTabMenuObject = function(params) {
   return navLi;
 };
 
-GuiUtils.prototype.createTabContentObject = function(params) {
+GuiUtils.prototype.createTabContentObject = function (params) {
   var navigationObject = params.navigationObject;
   var tabId = params.id;
   var result = document.createElement("div");
@@ -460,4 +461,81 @@ GuiUtils.prototype.createTabContentObject = function(params) {
   return result;
 };
 
+GuiUtils.prototype.createTabDiv = function (params) {
+  var tabDiv = Functions.createElement({
+    type: "div",
+    name: "tabView",
+    className: "tabbable boxed parentTabs"
+  });
+
+  var tabMenuDiv = Functions.createElement({
+    type: "ul",
+    className: "nav nav-tabs"
+  });
+  tabDiv.appendChild(tabMenuDiv);
+
+  var tabContentDiv = Functions.createElement({
+    type: "div",
+    className: "tab-content"
+  });
+  tabDiv.appendChild(tabContentDiv);
+
+  if (params !== undefined && params.element !== undefined) {
+    params.element.appendChild(tabDiv);
+  }
+
+  return {
+    element: tabDiv,
+    menu: tabMenuDiv,
+    content: tabContentDiv,
+    tabId: params.id
+  }
+};
+
+GuiUtils.prototype.createTab = function (params) {
+  var self = this;
+  var tabData = params.tabData;
+
+  var tabId = tabData.tabId + "_tab_" + self._tabIdCounter;
+  self._tabIdCounter++;
+
+  var navClass = '';
+  var contentClass = 'tab-pane';
+  if (tabData.menu.children.length === 0) {
+    navClass = "active";
+    contentClass = "tab-pane active";
+  }
+
+  var navLi = document.createElement("li");
+  navLi.className = navClass;
+
+  var navLink = document.createElement("a");
+  navLink.href = "#" + tabId;
+
+  navLink.innerHTML = params.title;
+
+  navLink.onclick = function () {
+    $(this).tab('show');
+  };
+  navLi.appendChild(navLink);
+  tabData.menu.appendChild(navLi);
+
+  var contentDiv = document.createElement("div");
+  contentDiv.style.height = "100%";
+  contentDiv.className = contentClass;
+  contentDiv.id = tabId;
+
+  if (Functions.isDomElement(params.content)) {
+    contentDiv.appendChild(params.content);
+  } else {
+    contentDiv.innerHTML = params.content;
+  }
+  tabData.content.appendChild(contentDiv);
+
+  return {
+    title: navLink,
+    content: contentDiv
+  }
+};
+
 module.exports = GuiUtils;
diff --git a/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js b/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js
index f3b3203fa21e750190e9f38c7c56d02bee5560c7..6e87c331574d71621b4d26703621d2c3cc3245f9 100644
--- a/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js
+++ b/frontend-js/src/main/js/gui/leftPanel/LeftPanel.js
@@ -21,13 +21,13 @@ var Functions = require('../../Functions');
 var logger = require('../../logger');
 
 function LeftPanel(params) {
-    AbstractGuiElement.call(this, params);
-    var self = this;
+  AbstractGuiElement.call(this, params);
+  var self = this;
 
-    this._tabIdCount = 0;
-    this._panels = [];
+  this._tabIdCount = 0;
+  this._panels = [];
 
-    self._createPanelGui();
+  self._createPanelGui();
 
 }
 
@@ -35,297 +35,281 @@ LeftPanel.prototype = Object.create(AbstractGuiElement.prototype);
 LeftPanel.prototype.constructor = LeftPanel;
 
 LeftPanel.prototype._createPanelGui = function () {
-    var self = this;
-    var panels = self.getPanelsDefinition();
-
-    var headerDiv = Functions.createElement({
-        type: "div",
-        id: "headerPanel"
-    });
-    var header = new Header({
-        element: headerDiv,
-        customMap: self.getMap(),
-    });
-    self.getElement().appendChild(headerDiv);
-
-    var loginDialogDiv = Functions.createElement({
-        type: "div",
-        name: "loginDialog",
-        style: "display:none",
-    });
-    self.getElement().appendChild(loginDialogDiv);
-    this.setLoginDialog(new LoginDialog({
-        element: loginDialogDiv,
-        customMap: self.getMap(),
-    }));
-
-    var tabDiv = Functions.createElement({
-        type: "div",
-        name: "tabView",
-        className: "tabbable boxed parentTabs"
-    });
-    self.getElement().appendChild(tabDiv);
-
-    var tabMenuDiv = Functions.createElement({
-        type: "ul",
-        className: "nav nav-tabs"
-    });
-    tabDiv.appendChild(tabMenuDiv);
-
-    var tabContentDiv = Functions.createElement({
-        type: "div",
-        className: "tab-content"
-    });
-    tabDiv.appendChild(tabContentDiv);
-
-    self.setHeader(header);
-
-    var elementInfoDiv = Functions.createElement({
-        name: "elementInfoDiv",
-        type: "div",
-        style: "background-color:#f3f3f3",
-        className: "minerva-element-info-div",
-    });
-    self.elementInfoDiv = elementInfoDiv;
-
-    for (var i = 0; i < panels.length; i++) {
-        self.addTab(panels[i], tabMenuDiv, tabContentDiv);
-    }
+  var self = this;
+  var panels = self.getPanelsDefinition();
+
+  var headerDiv = Functions.createElement({
+    type: "div",
+    id: "headerPanel"
+  });
+  var header = new Header({
+    element: headerDiv,
+    customMap: self.getMap()
+  });
+  self.getElement().appendChild(headerDiv);
+
+  var loginDialogDiv = Functions.createElement({
+    type: "div",
+    name: "loginDialog",
+    style: "display:none"
+  });
+  self.getElement().appendChild(loginDialogDiv);
+  this.setLoginDialog(new LoginDialog({
+    element: loginDialogDiv,
+    customMap: self.getMap()
+  }));
+
+  var guiUtils = self.getGuiUtils();
+  var tabData = guiUtils.createTabDiv({
+    element: self.getElement(),
+    id: "left_panel"
+  });
+
+  self.setHeader(header);
+
+  var elementInfoDiv = Functions.createElement({
+    name: "elementInfoDiv",
+    type: "div",
+    style: "background-color:#f3f3f3",
+    className: "minerva-element-info-div"
+  });
+  self.elementInfoDiv = elementInfoDiv;
+
+  for (var i = 0; i < panels.length; i++) {
+    self.addTab(panels[i], tabData);
+  }
 };
 
 LeftPanel.prototype.getPanelsDefinition = function () {
-    return [{
-        name: "SEARCH",
-        className: "fa-search",
-        panelClass: SearchPanel,
-    }, {
-        name: "OVERLAYS",
-        className: "fa-th-list",
-        panelClass: OverlayPanel,
-    }, {
-        name: "SUBMAPS",
-        className: "fa-sitemap",
-        panelClass: SubmapPanel,
-    }, {
-        name: "INFO",
-        className: "fa-info",
-        panelClass: ProjectInfoPanel,
-    }];
+  return [{
+    name: "SEARCH",
+    className: "fa-search",
+    panelClass: SearchPanel
+  }, {
+    name: "OVERLAYS",
+    className: "fa-th-list",
+    panelClass: OverlayPanel
+  }, {
+    name: "SUBMAPS",
+    className: "fa-sitemap",
+    panelClass: SubmapPanel
+  }, {
+    name: "INFO",
+    className: "fa-info",
+    panelClass: ProjectInfoPanel
+  }];
 };
 
 LeftPanel.prototype.hideTab = function (panel) {
-    var self = this;
-    var panelDefinitions = self.getPanelsDefinition();
-    for (var i = 0; i < panelDefinitions.length; i++) {
-        if (panel instanceof panelDefinitions[i].panelClass) {
-            var liElement = $("li:has(a[href='#left_panel_tab_" + i + "'])", $(self.getElement()))[0];
-            if (liElement !== undefined) {
-                liElement.style.display = "none";
-            } else {
-                logger.warn("Cannot find tab link for panel: " + panel.getPanelName());
-            }
-        }
+  var self = this;
+  var panelDefinitions = self.getPanelsDefinition();
+  for (var i = 0; i < panelDefinitions.length; i++) {
+    if (panel instanceof panelDefinitions[i].panelClass) {
+      var liElement = $("li:has(a[href='#left_panel_tab_" + i + "'])", $(self.getElement()))[0];
+      if (liElement !== undefined) {
+        liElement.style.display = "none";
+      } else {
+        logger.warn("Cannot find tab link for panel: " + panel.getPanelName());
+      }
     }
+  }
 };
 
 LeftPanel.prototype.init = function () {
-    var self = this;
+  var self = this;
 
-    var promises = [];
-    for (var i = 0; i < self._panels.length; i++) {
-        promises.push(self._panels[i].init());
-    }
-    promises.push(self.getHeader().init());
-
-    var initEvents = new Promise(function (resolve) {
-        self.getMap().addListener("onBioEntityClick", function (e) {
-            return self.showElementDetails(e.arg);
-        });
-        self.getMap().getOverlayByName("search").addListener("onSearch", function (e) {
-            if (e.arg.type === AbstractDbOverlay.QueryType.SEARCH_BY_COORDINATES) {
-                return self.showElementDetails(e.arg.identifiedElements[0][0]);
-            } else {
-                return self.showElementDetails(undefined);
-            }
-        });
-        resolve();
+  var promises = [];
+  for (var i = 0; i < self._panels.length; i++) {
+    promises.push(self._panels[i].init());
+  }
+  promises.push(self.getHeader().init());
+
+  var initEvents = new Promise(function (resolve) {
+    self.getMap().addListener("onBioEntityClick", function (e) {
+      return self.showElementDetails(e.arg);
+    });
+    self.getMap().getOverlayByName("search").addListener("onSearch", function (e) {
+      if (e.arg.type === AbstractDbOverlay.QueryType.SEARCH_BY_COORDINATES) {
+        return self.showElementDetails(e.arg.identifiedElements[0][0]);
+      } else {
+        return self.showElementDetails(undefined);
+      }
     });
-    promises.push(initEvents);
+    resolve();
+  });
+  promises.push(initEvents);
   promises.push(self.getLoginDialog().init());
-    return Promise.all(promises);
+  return Promise.all(promises);
 };
 
 LeftPanel.prototype.showElementDetails = function (element) {
-    var self = this;
-    var div = self.elementInfoDiv;
-    if (!$(div).hasClass("ui-dialog-content")) {
-        $(div).dialog({
-            resizable: false,
-            width: $(self.getElement()).width(),
-            height: 200,
-            beforeclose: function () {
-                $(this).dialog('option', 'position', [$(this).offset().left, $(this).offset().top]);
-                $(this).dialog('option', 'width', $(this).width());
-                $(this).dialog('option', 'height', $(this).height());
-            },
-            position: {
-                my: "left bottom",
-                at: "left bottom",
-                of: $(self.getElement())
-            }
-        }).siblings('.ui-dialog-titlebar').css("background", "gray");
-    }
-
-    var openTabName = $("[name='tabView'] > ul li.active a > .maintabdiv")[0].innerHTML;
-    var searchTabName = $("[name='tabView'] > ul li.active a > .maintabdiv")[1].innerHTML;
-    var isPanelHidden = (self.getElement().style.display === "none");
-    if (isPanelHidden) {
-        openTabName = undefined;
-    }
+  var self = this;
+  var div = self.elementInfoDiv;
+  if (!$(div).hasClass("ui-dialog-content")) {
+    $(div).dialog({
+      resizable: false,
+      width: $(self.getElement()).width(),
+      height: 200,
+      beforeclose: function () {
+        $(this).dialog('option', 'position', [$(this).offset().left, $(this).offset().top]);
+        $(this).dialog('option', 'width', $(this).width());
+        $(this).dialog('option', 'height', $(this).height());
+      },
+      position: {
+        my: "left bottom",
+        at: "left bottom",
+        of: $(self.getElement())
+      }
+    }).siblings('.ui-dialog-titlebar').css("background", "gray");
+  }
+
+  var openTabName = $("[name='tabView'] > ul li.active a > .maintabdiv")[0].innerHTML;
+  var searchTabName = $("[name='tabView'] > ul li.active a > .maintabdiv")[1].innerHTML;
+  var isPanelHidden = (self.getElement().style.display === "none");
+  if (isPanelHidden) {
+    openTabName = undefined;
+  }
 
   if (element !== undefined && openTabName !== undefined && (openTabName.indexOf("SEARCH") === -1 || searchTabName !== "GENERIC")) {
     var model = self.getMap().getSubmapById(element.getModelId()).getModel();
     return model.getByIdentifiedElement(element, true).then(function (bioEntity) {
       div.innerHTML = "";
       div.appendChild(self.prepareElementDetailsContent(bioEntity));
-            $(div).dialog("open");
-            $(div).dialog("option", "title", self.getElementTitle(bioEntity));
-            $(div).scrollTop(0);
-        });
-    } else {
-        $(div).dialog("close");
-        return Promise.resolve();
-    }
+      $(div).dialog("open");
+      $(div).dialog("option", "title", self.getElementTitle(bioEntity));
+      $(div).scrollTop(0);
+    });
+  } else {
+    $(div).dialog("close");
+    return Promise.resolve();
+  }
 };
 
 LeftPanel.prototype.prepareElementDetailsContent = function (bioEntity) {
-    var guiUtils = new GuiUtils(this.getMap().getConfiguration());
-    guiUtils.setMap(this.getMap());
-    if (bioEntity instanceof Reaction) {
-        return guiUtils.createReactionElement({
-            reaction: bioEntity,
-            showTitle: false,
-        });
-    } else if (bioEntity instanceof Alias) {
-        return guiUtils.createAliasElement({
-            alias: bioEntity,
-            showTitle: false,
-        });
-    } else if (bioEntity instanceof PointData) {
-        return Functions.createElement({
-            type: "div"
-        });
-    } else {
-        throw new Error("Unknown element type:" + bioEntity);
-    }
+  var guiUtils = this.getGuiUtils();
+  if (bioEntity instanceof Reaction) {
+    return guiUtils.createReactionElement({
+      reaction: bioEntity,
+      showTitle: false
+    });
+  } else if (bioEntity instanceof Alias) {
+    return guiUtils.createAliasElement({
+      alias: bioEntity,
+      showTitle: false
+    });
+  } else if (bioEntity instanceof PointData) {
+    return Functions.createElement({
+      type: "div"
+    });
+  } else {
+    throw new Error("Unknown element type:" + bioEntity);
+  }
+};
+
+LeftPanel.prototype.getGuiUtils = function () {
+  var self = this;
+  if (self._guiUtils === undefined) {
+    self._guiUtils = new GuiUtils(self.getMap().getConfiguration());
+    self._guiUtils.setMap(self.getMap());
+  }
+  return self._guiUtils;
 };
+
 LeftPanel.prototype.getElementTitle = function (bioEntity) {
-    if (bioEntity instanceof Reaction) {
-        return bioEntity.getType() + ": " + bioEntity.getReactionId();
-    } else if (bioEntity instanceof Alias) {
-        return bioEntity.getType() + ": " + bioEntity.getName();
-    } else if (bioEntity instanceof PointData) {
-        return "POINT: " + bioEntity.getId();
-    } else {
-        throw new Error("Unknown element type:" + bioEntity);
-    }
+  if (bioEntity instanceof Reaction) {
+    return bioEntity.getType() + ": " + bioEntity.getReactionId();
+  } else if (bioEntity instanceof Alias) {
+    return bioEntity.getType() + ": " + bioEntity.getName();
+  } else if (bioEntity instanceof PointData) {
+    return "POINT: " + bioEntity.getId();
+  } else {
+    throw new Error("Unknown element type:" + bioEntity);
+  }
 };
 
-LeftPanel.prototype.addTab = function (params, navElement, contentElement) {
-    var self = this;
+LeftPanel.prototype.addTab = function (params, tabData) {
+  var self = this;
 
-    var name = params.name;
+  var name = params.name;
+  if (name !== undefined) {
+    if (name.length > 12) {
+      name = name.substring(0, 10) + "...";
+    }
+  } else {
+    name = "";
+  }
 
-    var tabId = "left_panel_tab_" + this._tabIdCount;
-    self._tabIdCount++;
 
-    var navClass = '';
-    var contentClass = 'tab-pane';
-    if (navElement.children.length === 0) {
-        navClass = "active";
-        contentClass = "tab-pane active";
-    }
+  var guiUtils = self.getGuiUtils();
+  var data = guiUtils.createTab({
+    tabData: tabData,
+    title: '<div class="maintabdiv"><i class="fa ' + params.className + ' maintab"></i><br>' + name
+    + '</div>',
+    content: ""
+  });
+
+  this._panels.push(new params.panelClass({
+    element: data.content,
+    customMap: self.getMap(),
+    parent: self
+  }));
 
-    var navLi = document.createElement("li");
-    navLi.className = navClass;
-
-    var navLink = document.createElement("a");
-    navLink.href = "#" + tabId;
-    if (name !== undefined) {
-        if (name.length > 12) {
-            name = name.substring(0, 10) + "...";
-        }
-    } else {
-        name = "";
-    }
-    // add this when we want to have triangle below
-    // '<div class="tngContainer"><div class="tng"></div></div>'
-
-  navLink.innerHTML = '<div class="maintabdiv"><i class="fa ' + params.className + ' maintab"></i><br>' + name
-      + '</div>';
-
-    navLink.onclick = function () {
-        $(this).tab('show');
-    };
-    navLi.appendChild(navLink);
-    navElement.appendChild(navLi);
-
-    var contentDiv = document.createElement("div");
-    contentDiv.style.height = "100%";
-    contentDiv.className = contentClass;
-    contentDiv.id = tabId;
-
-    contentElement.appendChild(contentDiv);
-
-    this._panels.push(new params.panelClass({
-        element: contentDiv,
-        customMap: self.getMap(),
-        parent: self
-    }));
 };
 
 LeftPanel.prototype.hide = function () {
-    this.getElement().style.display = "none";
+  this.getElement().style.display = "none";
 };
 LeftPanel.prototype.show = function () {
-    this.getElement().style.display = "block";
+  this.getElement().style.display = "block";
 };
 
 LeftPanel.prototype.setHeader = function (header) {
-    this._header = header;
+  this._header = header;
 };
 
 LeftPanel.prototype.getHeader = function () {
-    return this._header;
+  return this._header;
 };
 
 LeftPanel.prototype.setLoginDialog = function (loginDialog) {
-    this._loginDialog = loginDialog;
+  this._loginDialog = loginDialog;
 };
 
 LeftPanel.prototype.getLoginDialog = function () {
-    return this._loginDialog;
+  return this._loginDialog;
+};
+
+LeftPanel.prototype.setPluginManager = function (pluginManager) {
+  this._pluginManager = pluginManager;
+};
+
+LeftPanel.prototype.getPluginManager = function () {
+  return this._pluginManager;
 };
 
 LeftPanel.prototype.destroy = function () {
-    var self = this;
-    var promises = [];
-    promises.push(self.getHeader().destroy());
-    var div = self.elementInfoDiv;
-
-    var destroyPanel = new Promise(function (resolve) {
-        if ($(div).hasClass("ui-dialog-content")) {
-            $(div).dialog("destroy");
-        }
-        resolve();
-    });
-    promises.push(destroyPanel);
-    promises.push(self.getLoginDialog().destroy());
-    for (var i = 0; i < self._panels.length; i++) {
-        promises.push(self._panels[i].destroy());
+  var self = this;
+  var promises = [];
+  promises.push(self.getHeader().destroy());
+  var div = self.elementInfoDiv;
+
+  var destroyPanel = new Promise(function (resolve) {
+    if ($(div).hasClass("ui-dialog-content")) {
+      $(div).dialog("destroy");
     }
-
-    return Promise.all(promises);
+    resolve();
+  });
+  promises.push(destroyPanel);
+  promises.push(self.getLoginDialog().destroy());
+  for (var i = 0; i < self._panels.length; i++) {
+    promises.push(self._panels[i].destroy());
+  }
+
+  if (self._pluginManager !== undefined) {
+    promises.push(self._pluginManager.destroy());
+  }
+
+  return Promise.all(promises);
 };
 
 module.exports = LeftPanel;
diff --git a/frontend-js/src/main/js/gui/leftPanel/ProjectInfoPanel.js b/frontend-js/src/main/js/gui/leftPanel/ProjectInfoPanel.js
index 02a6ba3c99d4c402811aa41d33cea96fb902c9f9..977d2d1da1de9a9d63650cec03bf6436329b67d6 100644
--- a/frontend-js/src/main/js/gui/leftPanel/ProjectInfoPanel.js
+++ b/frontend-js/src/main/js/gui/leftPanel/ProjectInfoPanel.js
@@ -20,6 +20,7 @@ function ProjectInfoPanel(params) {
   self._createInfoPanelLogic();
 
   self._createUserDataTab();
+  self._createPluginGui();
   var logoutButton = self.getControlElement(PanelControlElementType.USER_TAB_LOGOUT_BUTTON);
 
   logoutButton.onclick = function () {
@@ -328,6 +329,55 @@ ProjectInfoPanel.prototype._createUserDataTab = function () {
 
 };
 
+ProjectInfoPanel.prototype._createPluginGui = function () {
+  var self = this;
+  var guiUtils = self.getGuiUtils();
+  var pluginDataDiv = Functions.createElement({
+    type: "div",
+    className: "searchPanel"
+  });
+  self.getElement().appendChild(pluginDataDiv);
+
+  var pluginTitle = Functions.createElement({
+    type: "h3",
+    content: '<br/>Plugins<br/>'
+  });
+  pluginDataDiv.appendChild(pluginTitle);
+
+  var pluginFormTab = Functions.createElement({
+    type: "div",
+    style: "width:100%;display: table;border-spacing: 10px;"
+  });
+  pluginDataDiv.appendChild(pluginFormTab);
+
+  var pluginUrlLabel = Functions.createElement({
+    type: "span",
+    content: "URL:",
+    style: "color:#999999"
+  });
+  var pluginUrlInput = Functions.createElement({
+    type: "input",
+    name: "pluginUrlValue"
+  });
+  pluginDataDiv.appendChild(guiUtils.createTableRow([pluginUrlLabel, pluginUrlInput]));
+
+  var centerTag = Functions.createElement({
+    type: "center"
+  });
+  pluginDataDiv.appendChild(centerTag);
+
+  var logoutButton = Functions.createElement({
+    type: "button",
+    name: "loadPluginButton",
+    content: "LOAD PLUGIN"
+  });
+  centerTag.appendChild(logoutButton);
+  logoutButton.onclick = function () {
+    return self.getParent().getPluginManager().addPlugin({url: pluginUrlInput.value}).then(null, GuiConnector.alert);
+  }
+
+};
+
 ProjectInfoPanel.prototype.showUserProfilePage = function (user) {
 
   var self = this;
diff --git a/frontend-js/src/main/js/minerva.js b/frontend-js/src/main/js/minerva.js
index 938bb1c8ca1d52b1b37f7ab872539d3cd5ef57f7..fa515ec945176bdf6eb100ea0a3b7711d09d8383 100644
--- a/frontend-js/src/main/js/minerva.js
+++ b/frontend-js/src/main/js/minerva.js
@@ -11,6 +11,7 @@ var ConfigurationType = require('./ConfigurationType');
 var CustomMap = require('./map/CustomMap');
 var CustomMapOptions = require('./map/CustomMapOptions');
 var Export = require('./Export');
+var PluginManager = require('./plugin/PluginManager');
 var SearchDbOverlay = require('./map/overlay/SearchDbOverlay');
 
 var LeftPanel = require('./gui/leftPanel/LeftPanel');
@@ -89,17 +90,24 @@ function createDivStructure(element) {
     className: "minerva-left-panel",
   });
   element.appendChild(leftPanelDiv);
-  var rightPanelDiv = functions.createElement({
+  var middlePanelDiv = functions.createElement({
     type: "div",
     style: "display: table-cell;height:100%;width:100%;",
   });
-  element.appendChild(rightPanelDiv);
+  element.appendChild(middlePanelDiv);
 
   var rightPanelContainerDiv = functions.createElement({
     type: "div",
     style: "height:100%;width:100%;position:relative",
   });
-  rightPanelDiv.appendChild(rightPanelContainerDiv);
+  middlePanelDiv.appendChild(rightPanelContainerDiv);
+
+  var rightPanelDiv = functions.createElement({
+    type: "div",
+    className: "minerva-plugin",
+    name: "plugin-div"
+  });
+  element.appendChild(rightPanelDiv);
 
   var menuDiv = functions.createElement({
     type: "div",
@@ -529,6 +537,14 @@ function create(params) {
       customMap: customMap
     });
 
+    var pluginManager = new PluginManager({
+      element: functions.getElementByName(element, "plugin-div"),
+      map: customMap,
+      project: params.getProject(),
+      configuration: params.getConfiguration()
+    });
+    leftPanel.setPluginManager(pluginManager);
+
     topMenu = new TopMenu({
       element: functions.getElementByName(element, "menuDiv"),
       customMap: customMap
diff --git a/frontend-js/src/main/js/plugin/MinervaPluginProxy.js b/frontend-js/src/main/js/plugin/MinervaPluginProxy.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ea647889c7eb2df4a144fda49aff7e1ab0b6aec
--- /dev/null
+++ b/frontend-js/src/main/js/plugin/MinervaPluginProxy.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var ObjectWithListeners = require('../ObjectWithListeners');
+
+var Promise = require("bluebird");
+
+var logger = require('../logger');
+var Functions = require('../Functions');
+
+var id = 0;
+
+function createProject(options) {
+  return {};
+}
+
+function createConfiguration(options) {
+  return {};
+}
+
+function MinervaPluginProxy(options) {
+  return {
+    pluginId: options.pluginId,
+    element: options.element,
+    project: createProject(options),
+    configuration: createConfiguration(options)
+  };
+}
+
+module.exports = MinervaPluginProxy;
diff --git a/frontend-js/src/main/js/plugin/Plugin.js b/frontend-js/src/main/js/plugin/Plugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0652a669739af66f342c0b76fe1834e47921078
--- /dev/null
+++ b/frontend-js/src/main/js/plugin/Plugin.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var MinervaPluginProxy = require('./MinervaPluginProxy');
+var ObjectWithListeners = require('../ObjectWithListeners');
+
+var Promise = require("bluebird");
+
+var logger = require('../logger');
+var Functions = require('../Functions');
+
+var pluginId = 0;
+
+function Plugin(options) {
+  ObjectWithListeners.call(this);
+  var self = this;
+  self.setOptions(options);
+}
+
+Plugin.prototype = Object.create(ObjectWithListeners.prototype);
+Plugin.prototype.constructor = ObjectWithListeners;
+
+Plugin.prototype.setOptions = function (options) {
+  this._options = options;
+};
+
+Plugin.prototype.getOptions = function () {
+  return this._options;
+};
+
+Plugin.prototype.setLoadedPluginData = function (loadedPluginData) {
+  this._loadedPluginData = loadedPluginData;
+};
+Plugin.prototype.getLoadedPluginData = function () {
+  return this._loadedPluginData;
+};
+
+Plugin.prototype.setMinervaPluginProxy = function (minervaPluginProxy) {
+  this._minervaPluginProxy = minervaPluginProxy;
+};
+Plugin.prototype.getMinervaPluginProxy = function () {
+  return this._minervaPluginProxy;
+};
+
+Plugin.prototype.load = function () {
+  var self = this;
+  var options = self.getOptions();
+
+
+  return ServerConnector.sendRequest({
+    url: options.url,
+    description: "Loading plugin: " + options.url,
+    method: "GET"
+  }).then(function (content) {
+    var oldDefine = global.define;
+    var pluginData = undefined;
+    var error = false;
+    global.define = function (pluginFunction) {
+      try {
+        if (typeof pluginFunction === "function") {
+          pluginData = pluginFunction();
+        } else {
+          pluginData = pluginFunction;
+        }
+
+        var minervaPluginProxy = new MinervaPluginProxy({
+          map: options.map,
+          pluginId: "plugin" + (pluginId++)
+        });
+        self.setLoadedPluginData(pluginData);
+        self.setMinervaPluginProxy(minervaPluginProxy);
+        pluginData.register(minervaPluginProxy);
+      } catch (e) {
+        error = e;
+      }
+    };
+    define = global.define;
+    try {
+      eval(content);
+    } catch (e) {
+      error = e;
+    }
+    global.define = oldDefine;
+    if (error) {
+      return Promise.reject(error);
+    } else if (pluginData === undefined) {
+      return Promise.reject("Invalid plugin. Expected 'define(...)' expression.");
+    }
+  })
+
+};
+
+Plugin.prototype.unload = function () {
+  var self = this;
+  return Promise.resolve().then(function () {
+    return self.getLoadedPluginData().unregister();
+  }).then(function () {
+    return self.destroy();
+  });
+};
+
+Plugin.prototype.getName = function () {
+  return this.getLoadedPluginData().getName();
+};
+
+Plugin.prototype.getVersion = function () {
+  return this.getLoadedPluginData().getVersion();
+};
+
+Plugin.prototype.destroy = function () {
+  return Promise.resolve();
+};
+
+module.exports = Plugin;
diff --git a/frontend-js/src/main/js/plugin/PluginManager.js b/frontend-js/src/main/js/plugin/PluginManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..076bb0679230a1fd63b5c2a5b10457f6e39c5b85
--- /dev/null
+++ b/frontend-js/src/main/js/plugin/PluginManager.js
@@ -0,0 +1,111 @@
+"use strict";
+
+var ObjectWithListeners = require('../ObjectWithListeners');
+var Plugin = require('./Plugin');
+var GuiUtils = require('../gui/leftPanel/GuiUtils');
+
+var Promise = require("bluebird");
+
+var logger = require('../logger');
+var Functions = require('../Functions');
+
+
+function PluginManager(options) {
+  ObjectWithListeners.call(this);
+  var self = this;
+  self.setProject(options.project);
+  self.setMap(options.map);
+  self.setElement(options.element);
+  self.setConfiguration(options.configuration);
+  self._plugins = [];
+}
+
+PluginManager.prototype = Object.create(ObjectWithListeners.prototype);
+PluginManager.prototype.constructor = ObjectWithListeners;
+
+PluginManager.prototype.setProject = function (project) {
+  this._project = project;
+};
+PluginManager.prototype.setMap = function (map) {
+  this._map = map;
+};
+PluginManager.prototype.getMap = function () {
+  return this._map;
+};
+PluginManager.prototype.setElement = function (element) {
+  this._element = element;
+};
+PluginManager.prototype.getElement = function () {
+  return this._element;
+};
+
+PluginManager.prototype.setConfiguration = function (configuration) {
+  this._configuration = configuration;
+};
+
+PluginManager.prototype.getConfiguration = function () {
+  return this._configuration;
+};
+
+PluginManager.prototype.getPlugins = function () {
+  return this._plugins;
+};
+
+PluginManager.prototype.addPlugin = function (options) {
+  var self = this;
+  var tabData = self.createTabForPlugin();
+  var plugin = new Plugin({
+    url: options.url,
+    element: tabData.content,
+    configuration: self.getConfiguration(),
+    map: self.getMap()
+  });
+  self._plugins.push(plugin);
+  return plugin.load().then(function () {
+    tabData.title.innerHTML = plugin.getName();
+    return plugin;
+  });
+};
+
+PluginManager.prototype.createTabForPlugin = function () {
+  var self = this;
+  var tabData = self._tabData;
+  var guiUtils = new GuiUtils(self.getConfiguration());
+  if (tabData === undefined) {
+    self.getElement().style.width = "300px";
+    self.getElement().style.height = "100%";
+    self._tabData = guiUtils.createTabDiv({element: self.getElement(), id: "plugin_tab"});
+    tabData = self._tabData;
+  }
+  return guiUtils.createTab({
+    title: "",
+    content: "",
+    tabData: tabData
+  });
+};
+PluginManager.prototype.removePlugin = function (plugin) {
+  var self = this;
+  var found = false;
+  for (var i = 0; i < self._plugins.length; i++) {
+    if (plugin === self._plugins[i]) {
+      self._plugins.splice(i, 1);
+      found = true;
+    }
+  }
+  if (!found) {
+    throw new Error("Plugin not registered");
+  }
+  return plugin.unload();
+};
+
+PluginManager.prototype.destroy = function () {
+  var self = this;
+  var promises = [];
+  for (var i = 0; i < self._plugins.length; i++) {
+    promises.push(self._plugins[i].unload());
+  }
+  return Promise.all(promises);
+};
+
+
+module.exports = PluginManager;
diff --git a/frontend-js/src/test/js/gui/leftPanel/LeftPanel-test.js b/frontend-js/src/test/js/gui/leftPanel/LeftPanel-test.js
index ec31fe885e847a742fd04558cfe8c3000cbf6b79..a1157cfb8acfefab4d562d4732f5c12181daaf85 100644
--- a/frontend-js/src/test/js/gui/leftPanel/LeftPanel-test.js
+++ b/frontend-js/src/test/js/gui/leftPanel/LeftPanel-test.js
@@ -89,11 +89,11 @@ describe('LeftPanel', function () {
     });
   });
 
-  describe('showElementDetails', function() {
-    it('default', function() {
+  describe('showElementDetails', function () {
+    it('default', function () {
       var map;
       var panel;
-      return ServerConnector.getProject().then(function(project) {
+      return ServerConnector.getProject().then(function (project) {
         map = helper.createCustomMap(project);
 
         helper.createSearchDbOverlay(map);
@@ -102,31 +102,31 @@ describe('LeftPanel', function () {
         helper.createMiRnaDbOverlay(map);
 
         panel = new LeftPanel({
-          element : testDiv,
-          customMap : map
+          element: testDiv,
+          customMap: map
         });
 
         return panel.init();
-      }).then(function() {
+      }).then(function () {
 
         var element = new IdentifiedElement({
-          id : 329163,
-          type : "ALIAS",
-          modelId : map.getId()
+          id: 329163,
+          type: "ALIAS",
+          modelId: map.getId()
         });
         return map.getModel().getByIdentifiedElement(element, true);
-      }).then(function(alias) {
+      }).then(function (alias) {
         return panel.showElementDetails(alias);
-      }).then(function() {
+      }).then(function () {
         assert.notOk($(panel.elementInfoDiv).dialog('isOpen'));
         return panel.destroy();
       });
     });
 
-    it('when panel is hidden', function() {
+    it('when panel is hidden', function () {
       var map;
       var panel;
-      return ServerConnector.getProject().then(function(project) {
+      return ServerConnector.getProject().then(function (project) {
         map = helper.createCustomMap(project);
 
         helper.createSearchDbOverlay(map);
@@ -135,22 +135,22 @@ describe('LeftPanel', function () {
         helper.createMiRnaDbOverlay(map);
 
         panel = new LeftPanel({
-          element : testDiv,
-          customMap : map
+          element: testDiv,
+          customMap: map
         });
         return panel.init();
-      }).then(function() {
+      }).then(function () {
         panel.hide();
 
         var element = new IdentifiedElement({
-          id : 329163,
-          type : "ALIAS",
-          modelId : map.getId()
+          id: 329163,
+          type: "ALIAS",
+          modelId: map.getId()
         });
         return map.getModel().getByIdentifiedElement(element, true);
-      }).then(function(alias) {
+      }).then(function (alias) {
         return panel.showElementDetails(alias);
-      }).then(function() {
+      }).then(function () {
         assert.notOk($(panel.elementInfoDiv).dialog('isOpen'));
         return panel.destroy();
       });
diff --git a/frontend-js/src/test/js/mocha-config.js b/frontend-js/src/test/js/mocha-config.js
index a65e6f684a8ad0322d458827db3be8eef2a1b3a5..9822eb85167b59678c335bbc85b94560cbcd60a4 100644
--- a/frontend-js/src/test/js/mocha-config.js
+++ b/frontend-js/src/test/js/mocha-config.js
@@ -7,6 +7,8 @@ var Helper = require('./helper');
 
 var GuiConnector = require('./GuiConnector-mock');
 
+var path = require('path');
+
 // -----------------------------
 
 var logger = require('./logger');
@@ -25,11 +27,15 @@ function mockBootstrap() {
 }
 
 before(function () {
+  // requirejs.config({
+  //   baseUrl: path.dirname(require.main.filename) + "/../../../"
+  // });
+
 // GLOBAL configuration
   global.navigator = {
     userAgent: 'node.js',
     appName: 'MinervaUnitTest',
-    appVersion: '0.0.1',
+    appVersion: '0.0.1'
 
   };
 
@@ -93,7 +99,7 @@ before(function () {
 
   require('datatables.net')(window, $);
   require('spectrum-colorpicker');
-  global.tinycolor= window.tinycolor;
+  global.tinycolor = window.tinycolor;
   require('jstree');
 
   global.google = require('./google-map-mock');
diff --git a/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js b/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..97acbd9403c77e19fc0758d30a025295c402952c
--- /dev/null
+++ b/frontend-js/src/test/js/plugin/MinervaPluginProxy-test.js
@@ -0,0 +1,28 @@
+"use strict";
+
+/* exported logger */
+/* exported assert */
+
+require("../mocha-config");
+
+var MinervaPluginProxy = require('../../../main/js/plugin/MinervaPluginProxy');
+
+var logger = require('../logger');
+var assert = require('assert');
+
+describe('MinervaPluginProxy', function () {
+  it('constructor', function () {
+    var map = helper.createCustomMap();
+    var proxy = new MinervaPluginProxy({
+      map: map,
+      element: testDiv,
+      pluginId: "xx",
+      configuration: helper.getConfiguration()
+    });
+    assert.ok(proxy);
+    assert.ok(proxy.pluginId);
+    assert.ok(proxy.element);
+    assert.ok(proxy.project);
+    assert.ok(proxy.configuration);
+  });
+});
diff --git a/frontend-js/src/test/js/plugin/Plugin-test.js b/frontend-js/src/test/js/plugin/Plugin-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..5aa485d681ff3e3d57161e7f3af9ec310908219c
--- /dev/null
+++ b/frontend-js/src/test/js/plugin/Plugin-test.js
@@ -0,0 +1,27 @@
+"use strict";
+
+/* exported logger */
+/* exported assert */
+
+require("../mocha-config");
+
+var Plugin = require('../../../main/js/plugin/Plugin');
+
+var logger = require('../logger');
+var assert = require('assert');
+
+describe('PluginManager', function () {
+  it('constructor', function () {
+    var plugin = new Plugin({url: "./testFiles/plugin/empty.js"});
+    assert.ok(plugin);
+  });
+  it('load', function () {
+    var plugin = new Plugin({url: "./testFiles/plugin/empty.js"});
+    return plugin.load().then(function () {
+      assert.equal("test plugin", plugin.getName());
+      assert.equal("0.0.1", plugin.getVersion());
+      assert.equal(0, logger.getWarnings().length);
+
+    });
+  });
+});
diff --git a/frontend-js/src/test/js/plugin/PluginManager-test.js b/frontend-js/src/test/js/plugin/PluginManager-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd8430290b1c3c2b16cc6cfa19a287980298e0b7
--- /dev/null
+++ b/frontend-js/src/test/js/plugin/PluginManager-test.js
@@ -0,0 +1,60 @@
+"use strict";
+
+/* exported logger */
+/* exported assert */
+
+require("../mocha-config");
+
+var PluginManager = require('../../../main/js/plugin/PluginManager');
+
+var logger = require('../logger');
+var assert = require('assert');
+
+describe('PluginManager', function () {
+  var createParams = function () {
+    var map = helper.createCustomMap();
+    return {
+      map: map,
+      element: testDiv
+    };
+  };
+  it('constructor', function () {
+    var manager = new PluginManager(createParams());
+    assert.ok(manager);
+    assert.equal(0, logger.getWarnings().length);
+  });
+
+  it('getPlugins', function () {
+    var manager = new PluginManager(createParams());
+    assert.equal(0, manager.getPlugins().length);
+  });
+
+  describe('addPlugin', function () {
+    it('default', function () {
+      var manager = new PluginManager(createParams());
+      return manager.addPlugin({url: "./testFiles/plugin/empty.js"}).then(function () {
+        assert.equal(1, manager.getPlugins().length);
+      });
+    });
+    it('after removal', function () {
+      var manager = new PluginManager(createParams());
+      return manager.addPlugin({url: "./testFiles/plugin/empty.js"}).then(function (plugin) {
+        return manager.removePlugin(plugin);
+      }).then(function () {
+        return manager.addPlugin({url: "./testFiles/plugin/empty.js"});
+      }).then(function () {
+        assert.equal(1, manager.getPlugins().length);
+      });
+    });
+  });
+
+  it('removePlugin', function () {
+    var manager = new PluginManager(createParams());
+    return manager.addPlugin({url: "testFiles/plugin/empty.js"}).then(function (plugin) {
+      return manager.removePlugin(plugin);
+    }).then(function () {
+      assert.equal(0, manager.getPlugins().length);
+    });
+  });
+
+});
diff --git a/web/src/main/webapp/index.xhtml b/web/src/main/webapp/index.xhtml
index 086a2b1b27aeeeda422c626983d0a9a2d79269c6..15e033cd63a3a9f9a5aafbc156a8ef2bdc10994d 100644
--- a/web/src/main/webapp/index.xhtml
+++ b/web/src/main/webapp/index.xhtml
@@ -19,7 +19,7 @@
 
   <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
   <script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script>
-
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.js"></script>
 
 	<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css"/>	
 	<link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"/>