diff --git a/openbis_ng_ui/src/js/common/consts/ids.js b/openbis_ng_ui/src/js/common/consts/ids.js
index 861192cce233b931e17fa3928068c6d966af2ffd..592d244613a745d22fd0fdfe259c19538a6d0065 100644
--- a/openbis_ng_ui/src/js/common/consts/ids.js
+++ b/openbis_ng_ui/src/js/common/consts/ids.js
@@ -31,6 +31,7 @@ const PERSONAL_ACCESS_TOKEN_GRID_ID = 'personal_access_token_grid'
 const DATABASE_BROWSER_ID = 'database_browser'
 const TYPE_BROWSER_ID = 'type_browser'
 const USER_BROWSER_ID = 'user_browser'
+const TOOL_BROWSER_ID = 'tool_browser'
 
 export default {
   // app
@@ -67,5 +68,6 @@ export default {
   // browsers
   DATABASE_BROWSER_ID,
   TYPE_BROWSER_ID,
-  USER_BROWSER_ID
+  USER_BROWSER_ID,
+  TOOL_BROWSER_ID
 }
diff --git a/openbis_ng_ui/src/js/components/common/browser2/BrowserTreeController.js b/openbis_ng_ui/src/js/components/common/browser2/BrowserTreeController.js
index f652ff4813d90c041df0032d130729b8881ac54b..122fac46af371c195491fdb880d3d8da65ea9ee7 100644
--- a/openbis_ng_ui/src/js/components/common/browser2/BrowserTreeController.js
+++ b/openbis_ng_ui/src/js/components/common/browser2/BrowserTreeController.js
@@ -240,7 +240,7 @@ export default class BrowserTreeController {
     const node = state.nodes[nodeId]
 
     if (node) {
-      if (!node.loaded) {
+      if (!node.loaded && node.canHaveChildren) {
         await this._doLoadNode(state, nodeId, 0)
       }
 
diff --git a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodePath.js b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodePath.js
index 6730fa1d78751da452e41426c62e5d60b2eb1f6e..3053f7a1b2ac70b988e4904b71975006d9dff90a 100644
--- a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodePath.js
+++ b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodePath.js
@@ -3,7 +3,7 @@ import DatabaseBrowserConsts from '@src/js/components/database/browser/DatabaseB
 import openbis from '@src/js/services/openbis.js'
 import objectType from '@src/js/common/consts/objectType.js'
 
-export default class DatabaseBrowserConstsLoadNodePath {
+export default class DatabaseBrowserControllerLoadNodePath {
   async doLoadNodePath(params) {
     const { root, object } = params
 
diff --git a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesFiltered.js b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesFiltered.js
index f99216acbd4de4c732555b86990ae4f6b52ed72c..8b96c90d0520867ec81eae7da4c54dc57f54ba53 100644
--- a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesFiltered.js
+++ b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesFiltered.js
@@ -8,7 +8,7 @@ import compare from '@src/js/common/compare.js'
 const LOAD_LIMIT = 50
 const TOTAL_LOAD_LIMIT = 500
 
-export default class DatabaseBrowserConstsLoadNodesFiltered {
+export default class DatabaseBrowserControllerLoadNodesFiltered {
   async doLoadFilteredNodes(params) {
     const { node } = params
 
diff --git a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesUnfiltered.js b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesUnfiltered.js
index d0333e9553105e3f72f8d1fa00ff73b69cf39d03..fddd5f1f3c648142edf338cf4149be559b43f7eb 100644
--- a/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesUnfiltered.js
+++ b/openbis_ng_ui/src/js/components/database/browser/DatabaseBrowserControllerLoadNodesUnfiltered.js
@@ -5,7 +5,7 @@ import objectType from '@src/js/common/consts/objectType.js'
 
 const LOAD_LIMIT = 50
 
-export default class DatabaseBrowserConstsLoadNodesUnfiltered {
+export default class DatabaseBrowserControllerLoadNodesUnfiltered {
   async doLoadUnfilteredNodes(params) {
     const { node } = params
 
diff --git a/openbis_ng_ui/src/js/components/tools/Tools.jsx b/openbis_ng_ui/src/js/components/tools/Tools.jsx
index 050cf87f885ecbf494a3e001ca55a61b193745b2..095cf13b1de6904960f62e1a1436c63510e6de1e 100644
--- a/openbis_ng_ui/src/js/components/tools/Tools.jsx
+++ b/openbis_ng_ui/src/js/components/tools/Tools.jsx
@@ -5,7 +5,7 @@ import pages from '@src/js/common/consts/pages.js'
 import objectType from '@src/js/common/consts/objectType.js'
 import Content from '@src/js/components/common/content/Content.jsx'
 import ContentTab from '@src/js/components/common/content/ContentTab.jsx'
-import ToolBrowser from '@src/js/components/tools/browser/ToolBrowser.jsx'
+import ToolBrowser from '@src/js/components/tools/browser2/ToolBrowser.jsx'
 import ToolSearch from '@src/js/components/tools/search/ToolSearch.jsx'
 import PluginForm from '@src/js/components/tools/form/plugin/PluginForm.jsx'
 import QueryForm from '@src/js/components/tools/form/query/QueryForm.jsx'
@@ -88,7 +88,9 @@ class Tools extends React.PureComponent {
         ),
         [objectType.QUERY]: messages.get(messages.QUERIES),
         [objectType.HISTORY]: messages.get(messages.HISTORY),
-        [objectType.ACTIVE_USERS_REPORT]: messages.get(messages.ACTIVE_USERS_REPORT),
+        [objectType.ACTIVE_USERS_REPORT]: messages.get(
+          messages.ACTIVE_USERS_REPORT
+        ),
         [objectType.PERSONAL_ACCESS_TOKEN]: messages.get(
           messages.PERSONAL_ACCESS_TOKENS
         )
@@ -109,7 +111,9 @@ class Tools extends React.PureComponent {
         [objectType.HISTORY]: messages.get(messages.HISTORY) + ': ',
         [objectType.IMPORT]: messages.get(messages.IMPORT) + ': ',
         [objectType.SEARCH]: messages.get(messages.SEARCH) + ': ',
-        [objectType.ACTIVE_USERS_REPORT]: messages.get(messages.ACTIVE_USERS_REPORT)
+        [objectType.ACTIVE_USERS_REPORT]: messages.get(
+          messages.ACTIVE_USERS_REPORT
+        )
       }
 
       let suffix = object.id
@@ -125,7 +129,7 @@ class Tools extends React.PureComponent {
           suffix = messages.get(messages.ALL)
         }
       } else if (object.type === objectType.ACTIVE_USERS_REPORT) {
-        suffix = ""
+        suffix = ''
       }
 
       label = prefixes[object.type] + suffix
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowser.jsx b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowser.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..128063ae9380ade0268e2c0b30858d9e3d1c7b29
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowser.jsx
@@ -0,0 +1,78 @@
+import _ from 'lodash'
+import React from 'react'
+import autoBind from 'auto-bind'
+import BrowserWithOpenbis from '@src/js/components/common/browser2/BrowserWithOpenbis.jsx'
+import BrowserButtonsAddRemove from '@src/js/components/common/browser2/BrowserButtonsAddRemove.jsx'
+import ToolBrowserController from '@src/js/components/tools/browser2/ToolBrowserController.js'
+import AppController from '@src/js/components/AppController.js'
+import pages from '@src/js/common/consts/pages.js'
+import ids from '@src/js/common/consts/ids.js'
+import logger from '@src/js/common/logger.js'
+
+export class ToolBrowser extends React.Component {
+  constructor(props) {
+    super(props)
+    autoBind(this)
+    this.controller = this.props.controller || new ToolBrowserController()
+  }
+
+  componentDidMount() {
+    this.componentDidUpdate({})
+  }
+
+  componentDidUpdate(prevProps) {
+    if (!_.isEqual(this.props.selectedObject, prevProps.selectedObject)) {
+      this.controller.selectObject(this.props.selectedObject)
+    }
+
+    if (
+      !_.isEqual(
+        this.props.lastObjectModifications,
+        prevProps.lastObjectModifications
+      )
+    ) {
+      this.controller.reload(this.props.lastObjectModifications)
+    }
+  }
+
+  render() {
+    logger.log(logger.DEBUG, 'ToolBrowser.render')
+
+    return (
+      <BrowserWithOpenbis
+        id={ids.TOOL_BROWSER_ID}
+        controller={this.controller}
+        renderFooter={this.renderFooter}
+        onSelectedChange={selectedObject => {
+          if (selectedObject) {
+            AppController.getInstance().objectOpen(
+              pages.TOOLS,
+              selectedObject.type,
+              selectedObject.id
+            )
+          }
+        }}
+      />
+    )
+  }
+
+  renderFooter() {
+    return (
+      <div>
+        <BrowserButtonsAddRemove
+          selectedObject={this.controller.getSelectedObject()}
+          addEnabled={this.controller.canAddNode()}
+          removeEnabled={this.controller.canRemoveNode()}
+          onAdd={this.controller.addNode}
+          onRemove={this.controller.removeNode}
+        />
+      </div>
+    )
+  }
+}
+
+export default AppController.getInstance().withState(() => ({
+  selectedObject: AppController.getInstance().getSelectedObject(pages.TOOLS),
+  lastObjectModifications:
+    AppController.getInstance().getLastObjectModifications()
+}))(ToolBrowser)
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserConsts.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserConsts.js
new file mode 100644
index 0000000000000000000000000000000000000000..87982bcb4109144b4e661f2beee97f9e3abdc5e0
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserConsts.js
@@ -0,0 +1,41 @@
+import messages from '@src/js/common/messages.js'
+
+const TYPE_ROOT = 'root'
+const TYPE_HISTORY = 'history'
+const TYPE_IMPORT = 'import'
+const TYPE_ACCESS = 'access'
+const TYPE_ACTIVE_USERS_REPORT = 'active_users_report'
+const TYPE_WARNING = 'warning'
+
+const TEXT_DYNAMIC_PROPERTY_PLUGINS = messages.get(
+  messages.DYNAMIC_PROPERTY_PLUGINS
+)
+const TEXT_ENTITY_VALIDATION_PLUGINS = messages.get(
+  messages.ENTITY_VALIDATION_PLUGINS
+)
+const TEXT_QUERIES = messages.get(messages.QUERIES)
+const TEXT_HISTORY = messages.get(messages.HISTORY)
+const TEXT_IMPORT = messages.get(messages.IMPORT)
+const TEXT_ACCESS = messages.get(messages.ACCESS)
+const TEXT_ACTIVE_USERS_REPORT = messages.get(messages.ACTIVE_USERS_REPORT)
+
+function nodeId(...parts) {
+  return parts.join('__')
+}
+
+export default {
+  nodeId,
+  TYPE_ROOT,
+  TYPE_HISTORY,
+  TYPE_IMPORT,
+  TYPE_ACCESS,
+  TYPE_ACTIVE_USERS_REPORT,
+  TYPE_WARNING,
+  TEXT_DYNAMIC_PROPERTY_PLUGINS,
+  TEXT_ENTITY_VALIDATION_PLUGINS,
+  TEXT_QUERIES,
+  TEXT_HISTORY,
+  TEXT_IMPORT,
+  TEXT_ACCESS,
+  TEXT_ACTIVE_USERS_REPORT
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserController.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserController.js
new file mode 100644
index 0000000000000000000000000000000000000000..92643cca727e29486ecf4efb9325badce8e42b16
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserController.js
@@ -0,0 +1,42 @@
+import BrowserController from '@src/js/components/common/browser2/BrowserController.js'
+import ToolBrowserControllerLoadNodePath from '@src/js/components/tools/browser2/ToolBrowserControllerLoadNodePath.js'
+import ToolBrowserControllerLoadNodes from '@src/js/components/tools/browser2/ToolBrowserControllerLoadNodes.js'
+import ToolBrowserControllerAddNode from '@src/js/components/tools/browser2/ToolBrowserControllerAddNode.js'
+import ToolBrowserControllerRemoveNode from '@src/js/components/tools/browser2/ToolBrowserControllerRemoveNode.js'
+import ToolBrowserControllerReload from '@src/js/components/tools/browser2/ToolBrowserControllerReload.js'
+
+export default class TypeBrowserController extends BrowserController {
+  async doLoadNodePath(params) {
+    return await new ToolBrowserControllerLoadNodePath().doLoadNodePath(params)
+  }
+
+  async doLoadNodes(params) {
+    return await new ToolBrowserControllerLoadNodes().doLoadNodes(params)
+  }
+
+  async reload(objectModifications) {
+    new ToolBrowserControllerReload(this).reload(objectModifications)
+  }
+
+  canAddNode() {
+    return new ToolBrowserControllerAddNode().canAddNode(
+      this.getSelectedObject()
+    )
+  }
+
+  async addNode() {
+    await new ToolBrowserControllerAddNode().doAddNode(this.getSelectedObject())
+  }
+
+  canRemoveNode() {
+    return new ToolBrowserControllerRemoveNode().canRemoveNode(
+      this.getSelectedObject()
+    )
+  }
+
+  async removeNode() {
+    await new ToolBrowserControllerRemoveNode().doRemoveNode(
+      this.getSelectedObject()
+    )
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerAddNode.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerAddNode.js
new file mode 100644
index 0000000000000000000000000000000000000000..73fd44646819482deb6d6bc23a8976ddf87b9da9
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerAddNode.js
@@ -0,0 +1,30 @@
+import AppController from '@src/js/components/AppController.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import pages from '@src/js/common/consts/pages.js'
+
+const NEW_OBJECT_TYPES = {
+  [objectType.DYNAMIC_PROPERTY_PLUGIN]: objectType.NEW_DYNAMIC_PROPERTY_PLUGIN,
+  [objectType.ENTITY_VALIDATION_PLUGIN]:
+    objectType.NEW_ENTITY_VALIDATION_PLUGIN,
+  [objectType.QUERY]: objectType.NEW_QUERY
+}
+
+export default class TypeBrowserControllerAddNode {
+  canAddNode(selectedObject) {
+    return (
+      selectedObject &&
+      selectedObject.type === objectType.OVERVIEW &&
+      NEW_OBJECT_TYPES[selectedObject.id]
+    )
+  }
+
+  async doAddNode(selectedObject) {
+    if (!this.canAddNode(selectedObject)) {
+      return
+    }
+    await AppController.getInstance().objectNew(
+      pages.TYPES,
+      NEW_OBJECT_TYPES[selectedObject.id]
+    )
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodePath.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodePath.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1ecb7d1db039e57e17949403fccd7bd970ddd4d
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodePath.js
@@ -0,0 +1,189 @@
+import ToolBrowserConsts from '@src/js/components/tools/browser2/ToolBrowserConsts.js'
+import openbis from '@src/js/services/openbis.js'
+import objectType from '@src/js/common/consts/objectType.js'
+
+export default class ToolBrowserControllerLoadNodePath {
+  async doLoadNodePath(params) {
+    const { object } = params
+
+    if (object.type === objectType.OVERVIEW) {
+      if (object.id === objectType.DYNAMIC_PROPERTY_PLUGIN) {
+        return this.createFolderPath(
+          objectType.DYNAMIC_PROPERTY_PLUGIN,
+          ToolBrowserConsts.TEXT_DYNAMIC_PROPERTY_PLUGINS
+        )
+      } else if (object.id === objectType.ENTITY_VALIDATION_PLUGIN) {
+        return this.createFolderPath(
+          objectType.ENTITY_VALIDATION_PLUGIN,
+          ToolBrowserConsts.TEXT_ENTITY_VALIDATION_PLUGINS
+        )
+      } else if (object.id === objectType.QUERY) {
+        return this.createFolderPath(
+          objectType.QUERY,
+          ToolBrowserConsts.TEXT_QUERIES
+        )
+      } else if (object.id === objectType.PERSONAL_ACCESS_TOKEN) {
+        return [
+          {
+            id: ToolBrowserConsts.nodeId(
+              ToolBrowserConsts.TYPE_ROOT,
+              ToolBrowserConsts.TYPE_ACCESS
+            ),
+            object: {
+              type: ToolBrowserConsts.TYPE_ACCESS,
+              id: ToolBrowserConsts.TYPE_ACCESS
+            },
+            text: ToolBrowserConsts.TEXT_ACCESS
+          },
+          {
+            id: ToolBrowserConsts.nodeId(
+              ToolBrowserConsts.TYPE_ROOT,
+              ToolBrowserConsts.TYPE_ACCESS,
+              object.id
+            ),
+            object,
+            text: object.id
+          }
+        ]
+      }
+    } else if (
+      object.type === objectType.DYNAMIC_PROPERTY_PLUGIN ||
+      object.type === objectType.ENTITY_VALIDATION_PLUGIN
+    ) {
+      const id = new openbis.PluginPermId(object.id)
+      const fetchOptions = new openbis.PluginFetchOptions()
+
+      const plugins = await openbis.getPlugins([id], fetchOptions)
+      const plugin = plugins[object.id]
+
+      if (plugin) {
+        if (plugin.getPluginType() === openbis.PluginType.DYNAMIC_PROPERTY) {
+          return this.createNodePath(
+            object,
+            plugin,
+            objectType.DYNAMIC_PROPERTY_PLUGIN,
+            ToolBrowserConsts.TEXT_DYNAMIC_PROPERTY_PLUGINS
+          )
+        } else if (
+          plugin.getPluginType() === openbis.PluginType.ENTITY_VALIDATION
+        ) {
+          return this.createNodePath(
+            object,
+            plugin,
+            objectType.ENTITY_VALIDATION_PLUGIN,
+            ToolBrowserConsts.TEXT_ENTITY_VALIDATION_PLUGINS
+          )
+        }
+      }
+    } else if (object.type === objectType.QUERY) {
+      const id = new openbis.QueryName(object.id)
+      const fetchOptions = new openbis.QueryFetchOptions()
+
+      const queries = await openbis.getQueries([id], fetchOptions)
+      const query = queries[object.id]
+
+      return this.createNodePath(
+        object,
+        query,
+        objectType.QUERY,
+        ToolBrowserConsts.TEXT_QUERIES
+      )
+    } else if (object.type === objectType.HISTORY) {
+      return [
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            ToolBrowserConsts.TYPE_HISTORY
+          ),
+          object: {
+            type: ToolBrowserConsts.TYPE_HISTORY,
+            id: ToolBrowserConsts.TYPE_HISTORY
+          },
+          text: ToolBrowserConsts.TEXT_HISTORY
+        },
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            ToolBrowserConsts.TYPE_HISTORY,
+            object.id
+          ),
+          object,
+          text: object.id
+        }
+      ]
+    } else if (object.type === objectType.IMPORT) {
+      return [
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            ToolBrowserConsts.TYPE_IMPORT
+          ),
+          object: {
+            type: ToolBrowserConsts.TYPE_IMPORT,
+            id: ToolBrowserConsts.TYPE_IMPORT
+          },
+          text: ToolBrowserConsts.TEXT_IMPORT
+        },
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            ToolBrowserConsts.TYPE_IMPORT,
+            object.id
+          ),
+          object,
+          text: object.id
+        }
+      ]
+    } else if (object.type === objectType.ACTIVE_USERS_REPORT) {
+      return [
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            ToolBrowserConsts.TYPE_ACTIVE_USERS_REPORT
+          ),
+          object: {
+            type: objectType.ACTIVE_USERS_REPORT,
+            id: objectType.ACTIVE_USERS_REPORT
+          },
+          text: ToolBrowserConsts.TEXT_ACTIVE_USERS_REPORT
+        }
+      ]
+    } else {
+      return null
+    }
+  }
+
+  createFolderPath(folderObjectType, folderText) {
+    return [
+      {
+        id: ToolBrowserConsts.nodeId(
+          ToolBrowserConsts.TYPE_ROOT,
+          folderObjectType
+        ),
+        object: { type: objectType.OVERVIEW, id: folderObjectType },
+        text: folderText
+      }
+    ]
+  }
+
+  createNodePath(object, loadedObject, folderObjectType, folderText) {
+    if (loadedObject) {
+      const folderPath = this.createFolderPath(folderObjectType, folderText)
+      return [
+        ...folderPath,
+        {
+          id: ToolBrowserConsts.nodeId(
+            ToolBrowserConsts.TYPE_ROOT,
+            folderObjectType,
+            folderObjectType,
+            object.id
+          ),
+          object,
+          text: object.id
+        }
+      ]
+    } else {
+      return null
+    }
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodes.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodes.js
new file mode 100644
index 0000000000000000000000000000000000000000..b7760a600224b5381f8cb645a4121dcedcec6baa
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerLoadNodes.js
@@ -0,0 +1,387 @@
+import _ from 'lodash'
+import ImportType from '@src/js/components/tools/form/import/ImportType.js'
+import ToolBrowserConsts from '@src/js/components/tools/browser2/ToolBrowserConsts.js'
+import ServerInformation from '@src/js/components/common/dto/ServerInformation.js'
+import AppController from '@src/js/components/AppController.js'
+import openbis from '@src/js/services/openbis.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import messages from '@src/js/common/messages.js'
+import compare from '@src/js/common/compare.js'
+
+const LOAD_LIMIT = 100
+const TOTAL_LOAD_LIMIT = 500
+
+export default class ToolBrowserControllerLoadNodes {
+  async doLoadNodes(params) {
+    const { node } = params
+
+    if (node.internalRoot) {
+      return {
+        nodes: [
+          {
+            id: ToolBrowserConsts.TYPE_ROOT,
+            object: {
+              type: ToolBrowserConsts.TYPE_ROOT
+            },
+            canHaveChildren: true
+          }
+        ]
+      }
+    } else if (node.object.type === ToolBrowserConsts.TYPE_ROOT) {
+      const [dynamicPropertyPlugins, entityValidationPlugins, queries] =
+        await Promise.all([
+          this.searchDynamicPropertyPlugins(params),
+          this.searchEntityValidationPlugins(params),
+          this.searchQueries(params)
+        ])
+
+      if (params.filter) {
+        const totalCount =
+          dynamicPropertyPlugins.totalCount +
+          entityValidationPlugins.totalCount +
+          queries.totalCount
+
+        if (totalCount > TOTAL_LOAD_LIMIT) {
+          return this.tooManyResultsFound(node)
+        }
+      }
+
+      let nodes = [
+        this.createDynamicPropertyPluginsNode(node, dynamicPropertyPlugins),
+        this.createEntityValidationPluginsNode(node, entityValidationPlugins),
+        this.createQueriesNode(node, queries),
+        this.createHistoryNode(node, params),
+        this.createImportNode(node, params),
+        this.createAccessNode(node, params),
+        this.createActiveUsersReportNode(node, params)
+      ]
+
+      nodes = nodes.filter(node => !!node)
+
+      if (params.filter) {
+        nodes = nodes.filter(node => !_.isEmpty(node.children))
+      }
+
+      return {
+        nodes: nodes
+      }
+    } else if (node.object.type === objectType.OVERVIEW) {
+      let types = null
+
+      if (node.object.id === objectType.DYNAMIC_PROPERTY_PLUGIN) {
+        types = await this.searchDynamicPropertyPlugins(params)
+      } else if (node.object.id === objectType.ENTITY_VALIDATION_PLUGIN) {
+        types = await this.searchEntityValidationPlugins(params)
+      } else if (node.object.id === objectType.QUERY) {
+        types = await this.searchQueries(params)
+      }
+
+      if (types) {
+        return this.createNodes(node, types, node.object.id)
+      } else {
+        return {
+          nodes: []
+        }
+      }
+    } else {
+      return null
+    }
+  }
+
+  tooManyResultsFound(node) {
+    return {
+      nodes: [
+        {
+          id: ToolBrowserConsts.nodeId(node.id, ToolBrowserConsts.TYPE_WARNING),
+          message: {
+            type: 'warning',
+            text: messages.get(messages.TOO_MANY_FILTERED_RESULTS_FOUND)
+          },
+          selectable: false
+        }
+      ]
+    }
+  }
+
+  async searchDynamicPropertyPlugins(params) {
+    return await this.searchPlugins(params, openbis.PluginType.DYNAMIC_PROPERTY)
+  }
+
+  async searchEntityValidationPlugins(params) {
+    return await this.searchPlugins(
+      params,
+      openbis.PluginType.ENTITY_VALIDATION
+    )
+  }
+
+  async searchPlugins(params, pluginType) {
+    const { filter, offset } = params
+
+    const criteria = new openbis.PluginSearchCriteria()
+    criteria.withPluginType().thatEquals(pluginType)
+    if (filter) {
+      criteria.withName().thatContains(filter)
+    }
+    const fetchOptions = new openbis.PluginFetchOptions()
+
+    const result = await openbis.searchPlugins(criteria, fetchOptions)
+
+    return {
+      objects: result.getObjects().map(o => ({
+        id: o.getName(),
+        text: o.getName()
+      })),
+      totalCount: result.getTotalCount(),
+      filter,
+      offset
+    }
+  }
+
+  async searchQueries(params) {
+    const { filter, offset } = params
+
+    const criteria = new openbis.QuerySearchCriteria()
+    if (filter) {
+      criteria.withName().thatContains(filter)
+    }
+    const fetchOptions = new openbis.QueryFetchOptions()
+
+    const result = await openbis.searchQueries(criteria, fetchOptions)
+
+    return {
+      objects: result.getObjects().map(o => ({
+        id: o.getName(),
+        text: o.getName()
+      })),
+      totalCount: result.getTotalCount(),
+      filter,
+      offset
+    }
+  }
+
+  createDynamicPropertyPluginsNode(parent, result) {
+    return this.createFolderNode(
+      parent,
+      result,
+      objectType.DYNAMIC_PROPERTY_PLUGIN,
+      ToolBrowserConsts.TEXT_DYNAMIC_PROPERTY_PLUGINS
+    )
+  }
+
+  createEntityValidationPluginsNode(parent, result) {
+    return this.createFolderNode(
+      parent,
+      result,
+      objectType.ENTITY_VALIDATION_PLUGIN,
+      ToolBrowserConsts.TEXT_ENTITY_VALIDATION_PLUGINS
+    )
+  }
+
+  createQueriesNode(parent, result) {
+    return this.createFolderNode(
+      parent,
+      result,
+      objectType.QUERY,
+      ToolBrowserConsts.TEXT_QUERIES
+    )
+  }
+
+  createHistoryNode(parent, params) {
+    if (params.filter) {
+      return null
+    }
+
+    const folderNode = {
+      id: ToolBrowserConsts.nodeId(parent.id, ToolBrowserConsts.TYPE_HISTORY),
+      text: ToolBrowserConsts.TEXT_HISTORY,
+      object: {
+        type: ToolBrowserConsts.TYPE_HISTORY,
+        id: ToolBrowserConsts.TYPE_HISTORY
+      },
+      canHaveChildren: true,
+      selectable: false,
+      children: {
+        nodes: [
+          {
+            id: ToolBrowserConsts.nodeId(
+              parent.id,
+              ToolBrowserConsts.TYPE_HISTORY,
+              openbis.EventType.DELETION
+            ),
+            text: messages.get(messages.DELETION),
+            object: {
+              type: objectType.HISTORY,
+              id: openbis.EventType.DELETION
+            }
+          },
+          {
+            id: ToolBrowserConsts.nodeId(
+              parent.id,
+              ToolBrowserConsts.TYPE_HISTORY,
+              openbis.EventType.FREEZING
+            ),
+            text: messages.get(messages.FREEZING),
+            object: {
+              type: objectType.HISTORY,
+              id: openbis.EventType.FREEZING
+            }
+          }
+        ]
+      }
+    }
+
+    return folderNode
+  }
+
+  createImportNode(parent, params) {
+    if (params.filter) {
+      return null
+    }
+
+    const folderNode = {
+      id: ToolBrowserConsts.nodeId(parent.id, ToolBrowserConsts.TYPE_IMPORT),
+      text: ToolBrowserConsts.TEXT_IMPORT,
+      object: {
+        type: ToolBrowserConsts.TYPE_IMPORT,
+        id: ToolBrowserConsts.TYPE_IMPORT
+      },
+      canHaveChildren: true,
+      selectable: false,
+      children: {
+        nodes: [
+          {
+            id: ToolBrowserConsts.nodeId(
+              parent.id,
+              ToolBrowserConsts.TYPE_IMPORT,
+              ImportType.ALL
+            ),
+            text: messages.get(messages.ALL),
+            object: {
+              type: objectType.IMPORT,
+              id: ImportType.ALL
+            }
+          }
+        ]
+      }
+    }
+
+    return folderNode
+  }
+
+  createAccessNode(parent, params) {
+    if (params.filter) {
+      return null
+    }
+
+    const personalAccessTokensEnabled =
+      AppController.getInstance().getServerInformation(
+        ServerInformation.PERSONAL_ACCESS_TOKENS_ENABLED
+      )
+
+    if (personalAccessTokensEnabled === 'true') {
+      const folderNode = {
+        id: ToolBrowserConsts.nodeId(parent.id, ToolBrowserConsts.TYPE_ACCESS),
+        text: ToolBrowserConsts.TEXT_ACCESS,
+        object: {
+          type: ToolBrowserConsts.TYPE_ACCESS,
+          id: ToolBrowserConsts.TYPE_ACCESS
+        },
+        canHaveChildren: true,
+        selectable: false,
+        children: {
+          nodes: [
+            {
+              id: ToolBrowserConsts.nodeId(
+                parent.id,
+                ToolBrowserConsts.TYPE_ACCESS,
+                objectType.PERSONAL_ACCESS_TOKEN
+              ),
+              text: messages.get(messages.PERSONAL_ACCESS_TOKENS),
+              object: {
+                type: objectType.OVERVIEW,
+                id: objectType.PERSONAL_ACCESS_TOKEN
+              }
+            }
+          ]
+        }
+      }
+
+      return folderNode
+    } else {
+      return null
+    }
+  }
+
+  createActiveUsersReportNode(parent, params) {
+    if (params.filter) {
+      return null
+    }
+
+    const node = {
+      id: ToolBrowserConsts.nodeId(
+        parent.id,
+        ToolBrowserConsts.TYPE_ACTIVE_USERS_REPORT
+      ),
+      text: ToolBrowserConsts.TEXT_ACTIVE_USERS_REPORT,
+      object: {
+        type: objectType.ACTIVE_USERS_REPORT,
+        id: objectType.ACTIVE_USERS_REPORT
+      }
+    }
+
+    return node
+  }
+
+  createFolderNode(parent, result, folderObjectType, folderText) {
+    const folderNode = {
+      id: ToolBrowserConsts.nodeId(parent.id, folderObjectType),
+      text: folderText,
+      object: {
+        type: objectType.OVERVIEW,
+        id: folderObjectType
+      },
+      canHaveChildren: !!result,
+      selectable: true,
+      expanded: result && result.filter
+    }
+
+    if (result) {
+      folderNode.children = this.createNodes(
+        folderNode,
+        result,
+        folderObjectType
+      )
+    }
+
+    return folderNode
+  }
+
+  createNodes(parent, result, objectType) {
+    let objects = result.objects
+    objects.sort((o1, o2) => compare(o1.text, o2.text))
+    objects = objects.slice(result.offset, result.offset + LOAD_LIMIT)
+
+    let nodes = objects.map(object => ({
+      id: ToolBrowserConsts.nodeId(parent.id, objectType, object.id),
+      text: object.text,
+      object: {
+        type: objectType,
+        id: object.id
+      }
+    }))
+
+    if (_.isEmpty(nodes)) {
+      return null
+    } else {
+      return {
+        nodes: nodes,
+        loadMore: {
+          offset: result.offset + nodes.length,
+          loadedCount: result.offset + nodes.length,
+          totalCount: result.totalCount,
+          append: true
+        }
+      }
+    }
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerReload.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerReload.js
new file mode 100644
index 0000000000000000000000000000000000000000..723cc847159e9fe9eeaf8eaa80a22e1f2af2559d
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerReload.js
@@ -0,0 +1,23 @@
+import BrowserControllerReload from '@src/js/components/common/browser2/BrowserControllerReload.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import objectOperation from '@src/js/common/consts/objectOperation.js'
+
+export default class TypeBrowserControllerReload extends BrowserControllerReload {
+  constructor(controller) {
+    super(controller)
+  }
+
+  doGetObservedModifications() {
+    return {
+      [objectType.DYNAMIC_PROPERTY_PLUGIN]: [
+        objectOperation.CREATE,
+        objectOperation.DELETE
+      ],
+      [objectType.ENTITY_VALIDATION_PLUGIN]: [
+        objectOperation.CREATE,
+        objectOperation.DELETE
+      ],
+      [objectType.QUERY]: [objectOperation.CREATE, objectOperation.DELETE]
+    }
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerRemoveNode.js b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerRemoveNode.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8765063e9ce91c0fbeec0ac85ca3458bd3bda90
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/tools/browser2/ToolBrowserControllerRemoveNode.js
@@ -0,0 +1,72 @@
+import AppController from '@src/js/components/AppController.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import openbis from '@src/js/services/openbis.js'
+import pages from '@src/js/common/consts/pages.js'
+
+const REMOVABLE_OBJECT_TYPES = [
+  objectType.DYNAMIC_PROPERTY_PLUGIN,
+  objectType.ENTITY_VALIDATION_PLUGIN,
+  objectType.QUERY
+]
+
+export default class TypeBrowserControllerRemoveNode {
+  canRemoveNode(selectedObject) {
+    return (
+      selectedObject && REMOVABLE_OBJECT_TYPES.includes(selectedObject.type)
+    )
+  }
+
+  async doRemoveNode(selectedObject) {
+    if (!this.canRemoveNode(selectedObject)) {
+      return
+    }
+
+    const { type, id } = selectedObject
+    const reason = 'deleted via ng_ui'
+
+    return this._prepareRemoveOperations(type, id, reason)
+      .then(operations => {
+        const options = new openbis.SynchronousOperationExecutionOptions()
+        options.setExecuteInOrder(true)
+        return openbis.executeOperations(operations, options)
+      })
+      .then(() => {
+        AppController.getInstance().objectDelete(pages.TOOLS, type, id)
+      })
+      .catch(error => {
+        AppController.getInstance().errorChange(error)
+      })
+  }
+
+  _prepareRemoveOperations(type, id, reason) {
+    if (
+      type === objectType.DYNAMIC_PROPERTY_PLUGIN ||
+      type === objectType.ENTITY_VALIDATION_PLUGIN
+    ) {
+      return this._prepareRemovePluginOperations(id, reason)
+    } else if (type === objectType.QUERY) {
+      return this._prepareRemoveQueryOperations(id, reason)
+    } else {
+      throw new Error('Unsupported type: ' + type)
+    }
+  }
+
+  _prepareRemovePluginOperations(id, reason) {
+    const options = new openbis.PluginDeletionOptions()
+    options.setReason(reason)
+    return Promise.resolve([
+      new openbis.DeletePluginsOperation(
+        [new openbis.PluginPermId(id)],
+        options
+      )
+    ])
+  }
+
+  _prepareRemoveQueryOperations(id, reason) {
+    const options = new openbis.QueryDeletionOptions()
+    options.setReason(reason)
+    return Promise.resolve([
+      new openbis.DeleteQueriesOperation([new openbis.QueryName(id)], options)
+    ])
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodePath.js b/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodePath.js
index 6cf990d8d0bdcff788fe2fe2d13e15de1f50d111..7b7c24314b86ae0fc52e9b7e5f88f3563a08d92c 100644
--- a/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodePath.js
+++ b/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodePath.js
@@ -2,7 +2,7 @@ import TypeBrowserConsts from '@src/js/components/types/browser/TypeBrowserConst
 import openbis from '@src/js/services/openbis.js'
 import objectType from '@src/js/common/consts/objectType.js'
 
-export default class TypeBrowserConstsLoadNodePath {
+export default class TypeBrowserControllerLoadNodePath {
   async doLoadNodePath(params) {
     const { object } = params
 
diff --git a/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodes.js b/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodes.js
index 4cea1ec8391747805a3410db115375a6f06f0bdb..8b582999dbf0f95c2b533af4c874d5f3221d325c 100644
--- a/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodes.js
+++ b/openbis_ng_ui/src/js/components/types/browser/TypeBrowserControllerLoadNodes.js
@@ -8,7 +8,7 @@ import compare from '@src/js/common/compare.js'
 const LOAD_LIMIT = 100
 const TOTAL_LOAD_LIMIT = 500
 
-export default class TypeBrowserConstsLoadNodesUnfiltered {
+export default class TypeBrowserControllerLoadNodes {
   async doLoadNodes(params) {
     const { node } = params
 
diff --git a/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodePath.js b/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodePath.js
index 4e17665fdce91f76cbf9eda25e367984363a0b17..98a8de4296fc49e07f31e1e466ebbc6661b6672f 100644
--- a/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodePath.js
+++ b/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodePath.js
@@ -2,7 +2,7 @@ import UserBrowserConsts from '@src/js/components/users/browser/UserBrowserConst
 import openbis from '@src/js/services/openbis.js'
 import objectType from '@src/js/common/consts/objectType.js'
 
-export default class TypeBrowserConstsLoadNodePath {
+export default class TypeBrowserControllerLoadNodePath {
   async doLoadNodePath(params) {
     const { object } = params
 
diff --git a/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodes.js b/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodes.js
index 0ea2fa6a2bdf884e57ffcf6ccdcce01c588e0227..a0010fb142c248a4cccf4764b453ef8240651d26 100644
--- a/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodes.js
+++ b/openbis_ng_ui/src/js/components/users/browser/UserBrowserControllerLoadNodes.js
@@ -8,7 +8,7 @@ import compare from '@src/js/common/compare.js'
 const LOAD_LIMIT = 100
 const TOTAL_LOAD_LIMIT = 500
 
-export default class UserBrowserConstsLoadNodesUnfiltered {
+export default class UserBrowserControllerLoadNodes {
   async doLoadNodes(params) {
     const { node } = params