diff --git a/.env b/.env
index b3e48b427a49bb631ab1a95914bf2c1beec5fe37..295a27f95dd5991ff3b301c00f11049113762b2b 100644
--- a/.env
+++ b/.env
@@ -1,6 +1 @@
-
-NEXT_PUBLIC_BASE_API_URL = 'https://lux1.atcomp.pl/minerva/api'
-NEXT_PUBLIC_BASE_NEW_API_URL = 'https://lux1.atcomp.pl/minerva/new_api/'
-BASE_MAP_IMAGES_URL = 'https://lux1.atcomp.pl/'
-NEXT_PUBLIC_PROJECT_ID = 'pdmap_appu_test'
 ZOD_SEED = 997
diff --git a/docs/data/bioentities.md b/docs/plugins/data/bioentities.md
similarity index 100%
rename from docs/data/bioentities.md
rename to docs/plugins/data/bioentities.md
diff --git a/docs/plugins/errors.md b/docs/plugins/errors.md
index fca95f8b1558a7e48087d53b5a8b736cc549eea8..25f1210aeafb1852777856d94544782ab1bb3246 100644
--- a/docs/plugins/errors.md
+++ b/docs/plugins/errors.md
@@ -17,3 +17,33 @@
 ## Project Errors
 
 - **Project does not exist**: This error occurs when the project data is not available.
+
+- **Project ID does not exist**: This error occurs when the project ID is not available.
+
+## Overlay Errors
+
+- **Overlay name is not provided**: This error occurs when the name of the overlay is missing or not provided.
+
+- **Failed to read file**: This error occurs when there is an issue reading the content of a file. Check if it's text file.
+
+- **Invalid type of fileContent**: This error occurs when the fileContent parameter is of an invalid type.
+
+- **Overlay with provided id does not exist**: This error occurs when the provided overlay id does not correspond to any existing overlay.
+
+- **Overlay with provided id is not active**: This error occurs when the provided overlay id corresponds to an overlay that is not currently active.
+
+- **Overlay with provided id is already active**: This error occurs when the provided overlay id corresponds to an overlay that is already active.
+
+## Zoom errors
+
+- **Provided zoom value exeeds max zoom of ...**: This error occurs when `zoom` param of `setZoom` exeeds max zoom value of the selected map
+
+- **Provided zoom value exceeds min zoom of ...**: This error occurs when `zoom` param of `setZoom` exceeds min zoom value of the selected map
+
+## Event Errors
+
+- **Invalid event type: ...**: This error occurs when an event type is not allowed or recognized.
+
+## Plugin Errors
+
+- **Plugin "..." has crashed. Please contact the plugin developer for assistance**: This error occurs when a plugin encounters an unexpected error and crashes. Users are advised to contact the plugin developer for assistance.
diff --git a/docs/plugins/legend.md b/docs/plugins/legend.md
new file mode 100644
index 0000000000000000000000000000000000000000..dcc3e588e0066a7438e0978e2ef454d8e9c45618
--- /dev/null
+++ b/docs/plugins/legend.md
@@ -0,0 +1,45 @@
+### Legend
+
+#### Set Legend
+
+To set a legend for a specific plugin, plugins can use the `setLegend` method in `legend` object returned by `window.minerva.plugins.registerPlugin`. This method takes one argument:
+
+- legend: an array containing image urls
+
+##### Example of setLegend method usage:
+
+```javascript
+const {
+  element,
+  legend: { setLegend },
+} = minerva.plugins.registerPlugin({
+  pluginName,
+  pluginVersion,
+  pluginUrl,
+});
+
+setLegend([
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_d.png',
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_a.png',
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_b.png',
+]);
+```
+
+#### Remove Legend
+
+To remove a legend associated with a specific plugin, plugins can use the `removeLegend` method in the `legend` object returned by `window.minerva.plugins.registerPlugin`.
+
+##### Example of removeLegend method usage:
+
+```javascript
+const {
+  element,
+  legend: { removeLegend },
+} = minerva.plugins.registerPlugin({
+  pluginName,
+  pluginVersion,
+  pluginUrl,
+});
+
+removeLegend();
+```
diff --git a/docs/plugins/map/position.md b/docs/plugins/map/position.md
new file mode 100644
index 0000000000000000000000000000000000000000..a08872c6e6c00f67b041ef4b97641ef3ff84580e
--- /dev/null
+++ b/docs/plugins/map/position.md
@@ -0,0 +1,63 @@
+### Map positon
+
+With use of the methods below plugins can access and modify user position data.
+
+#### Get zoom
+
+To get current zoom value, plugins can use the `window.minerva.map.getZoom()` method, which returns current zoom value as a number.
+
+**Example:**
+
+```ts
+const currentZoom = window.minerva.map.getZoom();
+console.log(currentZoom); // 5
+```
+
+#### Set zoom
+
+To modify current zoom value, plugins can use the `window.minerva.map.setZoom(zoom)` method. This function accepts non-negative number as an argument and returns nothing. If argument is invalid, `setZoom` method throws an error.
+
+**Valid example:**
+
+```ts
+window.minerva.map.setZoom(7.54);
+console.log(window.minerva.map.getZoom()); // 7.54
+```
+
+**Invalid example:**
+
+```ts
+window.minerva.map.setZoom(-14);
+// Uncaught ZodError: [...]
+```
+
+#### Get center
+
+User position is defined as center coordinate. It's value is defined as x/y/z points of current viewport center translated to map position. Plugins can access center value and modify it.
+
+To get current position value, plugins can use the `window.minerva.map.getCenter()` method which returns current position value as an object containing `x`, `y` and `z` fields. All of them are non-negative numbers but `z` is an optional field and it defines current zoom value. If argument is invalid, `getCenter` method throws an error.
+
+**Valid example:**
+
+```ts
+const currentCenter = window.minerva.map.getCenter();
+console.log(currentCenter); // {x: 13256, y: 8118, z: 5}
+```
+
+#### Set center
+
+To modify position center value plugins can use `window.minerva.map.setCenter(positionObject)` which accepts single object as an argument and returns nothing. This object should contain `x`, `y` fields and `z` optionally. All of them are non-negative numbers. If argument is invalid, `setCenter` method throws an error.
+
+**Valid example:**
+
+```ts
+window.minerva.map.setCenter({ x: 13256, y: 8118, z: 5 });
+console.log(window.minerva.map.getCenter()); // {x: 13256, y: 8118, z: 5}
+```
+
+**Invalid example:**
+
+```ts
+window.minerva.map.setCenter({ x: 13256, y: 8118, z: -5 });
+// Uncaught ZodError: [...]
+```
diff --git a/docs/plugins/overlays.md b/docs/plugins/overlays.md
new file mode 100644
index 0000000000000000000000000000000000000000..b03b1ea17d69ad8e2981987d9e5f62e5521f41e9
--- /dev/null
+++ b/docs/plugins/overlays.md
@@ -0,0 +1,79 @@
+### Overlays
+
+#### Get list of available data overlays
+
+To get list of available data overlays, plugins can use the `getDataOverlays` method defined in `window.minerva.overlays.data`. This method returns array with all overlays.
+
+##### Example of getDataOverlays usage:
+
+```javascript
+window.minerva.overlays.data.getDataOverlays();
+```
+
+#### Get list of visible data overlays
+
+To get list of visible data overlays, plugins can use the `getVisibleDataOverlays` method defined in `window.minerva.overlays.data`. This method returns array with all visible data overlays.
+
+##### Example of getVisibleDataOverlays usage:
+
+```javascript
+window.minerva.overlays.data.getVisibleDataOverlays();
+```
+
+#### Show an overlay
+
+To show an overlay, plugins can use the `showDataOverlay` method defined in `window.minerva.overlays`. This method takes following arguments:
+
+- overlayId - the ID of the overlay that the plugin wants to show.
+- setBackgroundEmpty (optional) - whether `showDataOverlay` should set the background to empty if available when it shows overlay. Its value should be a boolean type.
+
+##### Example of showDataOverlay usage:
+
+```javascript
+window.minerva.overlays.showDataOverlay(109);
+
+window.minerva.overlays.showDataOverlay(112, true);
+```
+
+#### Hide an overlay
+
+To hide an overlay, plugins can use the `hideDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the ID of the overlay that the plugin wants to hide.
+
+##### Example of showDataOverlay usage:
+
+```javascript
+window.minerva.overlays.hideDataOverlay(109);
+```
+
+#### Add an overlay
+
+To add an overlay, plugins can use the `addDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the object with the following properties:
+
+- name (string): The name of the overlay.
+
+- description (optional string): A description of the overlay.
+
+- filename (optional string): The filename of the overlay data.
+
+- fileContent (string or text File): The content of the overlay data.
+
+- type (optional string): The type of overlay data.
+
+##### Example of addDataOverlay usage:
+
+```javascript
+window.minerva.overlays.addDataOverlay({
+  name: 'Plugin Test',
+  fileContent: 'plugin test content',
+});
+```
+
+#### Remove an overlay
+
+To remove an overlay, plugins can use the `removeDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the ID of the overlay that the plugin wants to remove.
+
+##### Example of removeDataOverlay usage:
+
+```javascript
+window.minerva.overlays.removeDataOverlay(129);
+```
diff --git a/docs/plugins/overview-images.md b/docs/plugins/overview-images.md
new file mode 100644
index 0000000000000000000000000000000000000000..b425d071b1aa1cdbf327a383e28b986e51a1eb91
--- /dev/null
+++ b/docs/plugins/overview-images.md
@@ -0,0 +1,46 @@
+### Overview images
+
+The methods contained within 'Overview images' are used to access data on Overview images and modify behavior of Overview images modal.
+
+Below is a description of the methods, as well as the types they return. description of the object types can be found in folder `/docs/types/`.
+
+**Available data access methods include:**
+
+- `getCurrentOverviewImage`
+  - gets currently selected overview image
+  - returns `OverviewImageView` or `undefined`
+- `getOverviewImage`
+  - gets all loaded overview images
+  - returns array of `OverviewImageView`
+
+**Available data modify methods include:**
+
+##### `hideOverviewImageModal`
+
+- accepts no arguments
+- hides overview image modal if opened
+- returns nothing
+- example:
+  ```ts
+  window.minerva.overviewImage.hideOverviewImageModal();
+  ```
+
+##### `selectOverviewImage`
+
+- accepts single argument of number representing id of one of loaded overview images
+- selects overview image of provided id as current, if image does not exists throws an error
+- returns nothing
+- example:
+  ```ts
+  window.minerva.overviewImage.selectOverviewImage(42);
+  ```
+
+##### `showOverviewImageModal`
+
+- accepts single argument of number representing id of one of loaded overview images
+- selects overview image of provided id as current and opens overview image modal, if image does not exists throws an error
+- returns nothing
+- example:
+  ```ts
+  window.minerva.overviewImage.showOverviewImageModal(24);
+  ```
diff --git a/docs/plugins/project.md b/docs/plugins/project.md
index a972b4a6da7f820b17e22bf9b304149be61433d7..3a152c9a2a48c2a1b242a973308aa7db4474c617 100644
--- a/docs/plugins/project.md
+++ b/docs/plugins/project.md
@@ -46,3 +46,12 @@ To get organism identifier associated with the project, plugins can use the `get
 ```javascript
 window.minerva.project.data.getOrganism();
 ```
+
+**Get Api Urls:**
+To get Api urls associated with the project, plugins can use the `getApiUrls` method defined in `window.minerva.project.data` object.
+
+##### Example usage of getApiUrls method:
+
+```javascript
+window.minerva.project.data.getApiUrls();
+```
diff --git a/docs/types/OverviewImageView.md b/docs/types/OverviewImageView.md
new file mode 100644
index 0000000000000000000000000000000000000000..33cf4cce60d81e907af4d6cd37132b2e1aff6347
--- /dev/null
+++ b/docs/types/OverviewImageView.md
@@ -0,0 +1,88 @@
+```json
+{
+  "type": "object",
+  "properties": {
+    "idObject": {
+      "type": "number"
+    },
+    "filename": {
+      "type": "string"
+    },
+    "width": {
+      "type": "number"
+    },
+    "height": {
+      "type": "number"
+    },
+    "links": {
+      "type": "array",
+      "items": {
+        "anyOf": [
+          {
+            "type": "object",
+            "properties": {
+              "idObject": {
+                "type": "number"
+              },
+              "polygon": {
+                "type": "array",
+                "items": {
+                  "type": "object",
+                  "properties": {
+                    "x": {
+                      "type": "number"
+                    },
+                    "y": {
+                      "type": "number"
+                    }
+                  },
+                  "required": ["x", "y"],
+                  "additionalProperties": false
+                }
+              },
+              "imageLinkId": {
+                "type": "number"
+              },
+              "type": {
+                "type": "string"
+              }
+            },
+            "required": ["idObject", "polygon", "imageLinkId", "type"],
+            "additionalProperties": false
+          },
+          {
+            "type": "object",
+            "properties": {
+              "idObject": {
+                "type": "number"
+              },
+              "polygon": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/overviewImageView/properties/links/items/anyOf/0/properties/polygon/items"
+                }
+              },
+              "zoomLevel": {
+                "type": "number"
+              },
+              "modelPoint": {
+                "$ref": "#/definitions/overviewImageView/properties/links/items/anyOf/0/properties/polygon/items"
+              },
+              "modelLinkId": {
+                "type": "number"
+              },
+              "type": {
+                "type": "string"
+              }
+            },
+            "required": ["idObject", "polygon", "zoomLevel", "modelPoint", "modelLinkId", "type"],
+            "additionalProperties": false
+          }
+        ]
+      }
+    }
+  },
+  "required": ["idObject", "filename", "width", "height", "links"],
+  "additionalProperties": false
+}
+```
diff --git a/index.d.ts b/index.d.ts
index 3cd0efd1a8805d41f089cca9aa3f9a7fd87171d8..81c13458a4c42d358385479cba29272ea3939267 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -5,15 +5,25 @@ import { triggerSearch } from '@/services/pluginsManager/map/triggerSearch';
 import { MinervaConfiguration } from '@/services/pluginsManager/pluginsManager';
 import { MapInstance } from '@/types/map';
 import { getModels } from '@/services/pluginsManager/map/models/getModels';
-import { OpenMapArgs, openMap } from '@/services/pluginsManager/map/openMap';
+import { openMap } from '@/services/pluginsManager/map/openMap';
+import { getCenter } from '@/services/pluginsManager/map/position/getCenter';
+import { setCenter } from '@/services/pluginsManager/map/position/setCenter';
 import { triggerSearch } from '@/services/pluginsManager/map/triggerSearch';
+import { getZoom } from '@/services/pluginsManager/map/zoom/getZoom';
+import { setZoom } from '@/services/pluginsManager/map/zoom/setZoom';
 import { MinervaConfiguration } from '@/services/pluginsManager/pluginsManager';
-import { MapModel } from '@/types/models';
 import { getDisease } from '@/services/pluginsManager/project/data/getDisease';
 import { getName } from '@/services/pluginsManager/project/data/getName';
 import { getOrganism } from '@/services/pluginsManager/project/data/getOrganism';
 import { getProjectId } from '@/services/pluginsManager/project/data/getProjectId';
 import { getVersion } from '@/services/pluginsManager/project/data/getVersion';
+import { getDataOverlays } from '@/services/pluginsManager/map/overlays/getDataOverlays';
+import { getVisibleDataOverlays } from '@/services/pluginsManager/map/overlays/getVisibleDataOverlays';
+import { showDataOverlay } from '@/services/pluginsManager/map/overlays/showDataOverlay';
+import { hideDataOverlay } from '@/services/pluginsManager/map/overlays/hideDataOverlay';
+import { removeDataOverlay } from '@/services/pluginsManager/map/overlays/removeDataOverlay';
+import { addDataOverlay } from '@/services/pluginsManager/map/overlays/addDataOverlay';
+import { getApiUrls } from '@/services/pluginsManager/project/data/getApiUrls';
 
 type Plugin = {
   pluginName: string;
@@ -32,6 +42,13 @@ type HashPlugin = {
 
 declare global {
   interface Window {
+    config: {
+      BASE_API_URL: string;
+      BASE_NEW_API_URL: string;
+      BASE_MAP_IMAGES_URL: string;
+      DEFAULT_PROJECT_ID: string;
+    };
+
     minerva: {
       configuration?: MinervaConfiguration;
       plugins: {
@@ -49,6 +66,27 @@ declare global {
         fitBounds: typeof fitBounds;
         openMap: typeof openMap;
         triggerSearch: typeof triggerSearch;
+        getZoom: typeof getZoom;
+        setZoom: typeof setZoom;
+        getCenter: typeof getCenter;
+        setCenter: typeof setCenter;
+      };
+      overviewImage: {
+        getCurrentOverviewImage: typeof getCurrentOverviewImage;
+        getOverviewImages: typeof getOverviewImages;
+        hideOverviewImageModal: typeof hideOverviewImageModal;
+        selectOverviewImage: typeof selectOverviewImage;
+        showOverviewImageModal: typeof showOverviewImageModal;
+      };
+      overlays: {
+        data: {
+          getDataOverlays: typeof getDataOverlays;
+          getVisibleDataOverlays: typeof getVisibleDataOverlays;
+        };
+        showDataOverlay: typeof showDataOverlay;
+        hideDataOverlay: typeof hideDataOverlay;
+        removeDataOverlay: typeof removeDataOverlay;
+        addDataOverlay: typeof addDataOverlay;
       };
       project: {
         data: {
@@ -57,6 +95,7 @@ declare global {
           getVersion: typeof getVersion;
           getDisease: typeof getDisease;
           getOrganism: typeof getOrganism;
+          getApiUrls: typeof getApiUrls;
         };
       };
     };
diff --git a/next.config.js b/next.config.js
index eebeaf4de2ae021a46f39f16ce07cdfdacc14066..c451e7002585f5a9b72903cac11fe8cc4c37beac 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,13 +1,11 @@
 /** @type {import('next').NextConfig} */
 const nextConfig = {
   reactStrictMode: true,
-  experimental: {
-    fontLoaders: [{ loader: '@next/font/google', options: { subsets: ['latin'] } }],
+  output: 'export',
+  images: {
+    unoptimized: true,
   },
   env: {
-    BASE_API_URL: process.env.NEXT_PUBLIC_BASE_API_URL || '',
-    BASE_MAP_IMAGES_URL: process.env.BASE_MAP_IMAGES_URL || '',
-    PROJECT_ID: process.env.NEXT_PUBLIC_PROJECT_ID || '',
     ZOD_SEED: process.env.ZOD_SEED || 123,
   },
 };
diff --git a/package.json b/package.json
index 72f51b8d83f11523c2886d9e81c7961c52d4206f..54dfdef6f81b5365f64141c7b74a92f419bdae94 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
     "@types/react-dom": "18.2.7",
     "autoprefixer": "10.4.15",
     "axios": "^1.5.1",
-    "axios-cache-interceptor": "^1.5.1",
     "axios-hooks": "^5.0.0",
     "crypto-js": "^4.2.0",
     "downshift": "^8.2.3",
diff --git a/pages/_document.tsx b/pages/_document.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..eaa2d812b5c31b75f80c9a6d030a774e6aaf24e0
--- /dev/null
+++ b/pages/_document.tsx
@@ -0,0 +1,15 @@
+import { Html, Head, Main, NextScript } from 'next/document';
+import Script from 'next/script';
+
+const Document = (): React.ReactNode => (
+  <Html>
+    <Head />
+    <body>
+      <Main />
+      <NextScript />
+      <Script src="/config.js" strategy="beforeInteractive" />
+    </body>
+  </Html>
+);
+
+export default Document;
diff --git a/public/config.js b/public/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..c30b99cc0500cddb066bffed35eff932c6723eb5
--- /dev/null
+++ b/public/config.js
@@ -0,0 +1,6 @@
+window.config = {
+  BASE_API_URL: 'https://lux1.atcomp.pl/minerva/api',
+  BASE_NEW_API_URL: 'https://lux1.atcomp.pl/minerva/new_api/',
+  BASE_MAP_IMAGES_URL: 'https://lux1.atcomp.pl/',
+  DEFAULT_PROJECT_ID: 'pdmap_appu_test',
+};
diff --git a/setupTests.ts b/setupTests.ts
index 1d944c81f3e4f47f19d599c4f702725886518791..7a4cf03f3cc96608bd1af1f7ec6a3aef7941948a 100644
--- a/setupTests.ts
+++ b/setupTests.ts
@@ -36,3 +36,12 @@ const localStorageMock = (() => {
 Object.defineProperty(global, 'localStorage', {
   value: localStorageMock,
 });
+
+Object.defineProperty(window, 'config', {
+  value: {
+    BASE_API_URL: 'https://lux1.atcomp.pl/minerva/api',
+    BASE_NEW_API_URL: 'https://lux1.atcomp.pl/minerva/new_api/',
+    BASE_MAP_IMAGES_URL: 'https://lux1.atcomp.pl/',
+    DEFAULT_PROJECT_ID: 'pdmap_appu_test',
+  },
+});
diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
index d944235a29d04b7a1c376869c37cdc29e502ed2d..4fae37530de53dd219cd0d5118bbe12aded2d3ae 100644
--- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
+++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
@@ -1,14 +1,14 @@
 /* eslint-disable no-magic-numbers */
+import { MODELS_MOCK } from '@/redux/compartmentPathways/compartmentPathways.mock';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
+import { StoreType } from '@/redux/store';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import {
   InitialStoreState,
   getReduxWrapperWithStore,
 } from '@/utils/testing/getReduxWrapperWithStore';
-import { StoreType } from '@/redux/store';
-import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { act, render, screen, within } from '@testing-library/react';
-import { MODELS_MOCK } from '@/redux/compartmentPathways/compartmentPathways.mock';
-import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { MapNavigation } from './MapNavigation.component';
 
 const MAIN_MAP_ID = 5053;
diff --git a/src/components/FunctionalArea/Modal/Modal.component.tsx b/src/components/FunctionalArea/Modal/Modal.component.tsx
index e6500c336107407611d0c6b1646b67dccce12202..2570bc5fab1e1cff71e9010cc2ad321d78a9a182 100644
--- a/src/components/FunctionalArea/Modal/Modal.component.tsx
+++ b/src/components/FunctionalArea/Modal/Modal.component.tsx
@@ -1,11 +1,11 @@
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { modalSelector } from '@/redux/modal/modal.selector';
 import dynamic from 'next/dynamic';
+import { EditOverlayModal } from './EditOverlayModal';
 import { LoginModal } from './LoginModal';
+import { ModalLayout } from './ModalLayout';
 import { OverviewImagesModal } from './OverviewImagesModal';
 import { PublicationsModal } from './PublicationsModal';
-import { ModalLayout } from './ModalLayout';
-import { EditOverlayModal } from './EditOverlayModal';
 
 const MolArtModal = dynamic(
   () => import('./MolArtModal/MolArtModal.component').then(mod => mod.MolArtModal),
diff --git a/src/components/FunctionalArea/Modal/MolArtModal/MolArtModal.component.tsx b/src/components/FunctionalArea/Modal/MolArtModal/MolArtModal.component.tsx
index a7cdd7eb9b9aa871613e8d8cf1e39250973104d9..73a3e0f00a6a92c1374cc84bd2b554e5e181404f 100644
--- a/src/components/FunctionalArea/Modal/MolArtModal/MolArtModal.component.tsx
+++ b/src/components/FunctionalArea/Modal/MolArtModal/MolArtModal.component.tsx
@@ -1,5 +1,9 @@
+import { MOLART_ERRORS } from '@/constants/errors';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { currentSelectedBioEntityIdSelector } from '@/redux/modal/modal.selector';
+import { closeModal } from '@/redux/modal/modal.slice';
+import { showToast } from '@/utils/showToast';
 import * as MolArt from 'molart';
 import * as React from 'react';
 import { useEffect } from 'react';
@@ -8,12 +12,25 @@ const containerId = 'molart-container';
 
 export const MolArtModal: React.FC = () => {
   const uniprot = useAppSelector(currentSelectedBioEntityIdSelector);
+  const dispatch = useAppDispatch();
 
   useEffect(() => {
-    const molart = new MolArt({
-      uniprotId: uniprot,
-      containerId,
-    });
+    let molart: typeof MolArt;
+
+    try {
+      molart = new MolArt({
+        uniprotId: uniprot,
+        containerId,
+      });
+    } catch (e) {
+      // eslint-disable-next-line no-console
+      console.error(e);
+      dispatch(closeModal());
+      showToast({
+        type: 'error',
+        message: MOLART_ERRORS.UNEXPECTED_MOLART_ERROR,
+      });
+    }
 
     return () => {
       try {
@@ -24,7 +41,7 @@ export const MolArtModal: React.FC = () => {
         console.error(e);
       }
     };
-  }, [uniprot]);
+  }, [uniprot, dispatch]);
 
   return (
     <div className="flex h-full w-full items-center justify-center bg-white" id={containerId} />
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx b/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
index b8c92be95826c1dd894928b759f8ea36dd29f218..5b7ed93ec271b0a2f1cdb22bb76ee8f38feb0e3e 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
@@ -45,6 +45,7 @@ describe('OverviewImagesModal - component', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -72,6 +73,7 @@ describe('OverviewImagesModal - component', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
index cd7468fd69680ce98645251e955ece392dc61994..f5ae9c32e2391b4331d69a60d184d318192a7d22 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
@@ -18,6 +18,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -50,6 +51,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -89,6 +91,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
index 55c6230d3e238a7a7595957d217c1025aafaaa65..375cd187e778f6bde32a59de14f341dce953d63d 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
@@ -12,11 +12,11 @@ import {
 } from '@/redux/map/map.fixtures';
 import { MODAL_INITIAL_STATE_MOCK } from '@/redux/modal/modal.mock';
 import { PROJECT_OVERVIEW_IMAGE_MOCK } from '@/redux/project/project.mock';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { OverviewImageLink } from '@/types/models';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import {
   FIRST_ARRAY_ELEMENT,
   NOOP,
@@ -43,6 +43,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -92,6 +93,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -148,6 +150,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             },
             loading: 'succeeded',
             error: { message: '', name: '' },
+            projectId: '',
           },
           modal: {
             ...MODAL_INITIAL_STATE_MOCK,
@@ -225,6 +228,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             },
             loading: 'succeeded',
             error: { message: '', name: '' },
+            projectId: '',
           },
           modal: {
             ...MODAL_INITIAL_STATE_MOCK,
@@ -313,7 +317,6 @@ describe('useOverviewImageLinkActions - hook', () => {
       jest.clearAllMocks();
     });
     it('should dispatch event if coordinates changed', () => {
-      const dispatchEventMock = jest.spyOn(PluginsEventBus, 'dispatchEvent');
       const { Wrapper } = getReduxStoreWithActionsListener({
         project: {
           data: {
@@ -329,6 +332,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -361,14 +365,6 @@ describe('useOverviewImageLinkActions - hook', () => {
       });
 
       handleLinkClick(OVERVIEW_LINK_MODEL_MOCK);
-
-      expect(dispatchEventMock).toHaveBeenCalledTimes(2);
-      expect(dispatchEventMock).toHaveBeenCalledWith('onZoomChanged', { modelId: 5053, zoom: 7 });
-      expect(dispatchEventMock).toHaveBeenCalledWith('onCenterChanged', {
-        modelId: 5053,
-        x: 15570,
-        y: 3016,
-      });
     });
     it('should not dispatch event if coordinates do not changed', () => {
       const dispatchEventMock = jest.spyOn(PluginsEventBus, 'dispatchEvent');
@@ -387,6 +383,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
@@ -447,6 +444,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
@@ -509,6 +507,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.ts
index 8a8689aa8029ca6877508bd0c00bea85cc350571..5059ea594c02245a181a02f3e0fe8e00407c82db 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.ts
@@ -1,13 +1,13 @@
 import { NOOP } from '@/constants/common';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { mapDataLastPositionSelector, mapOpenedMapsSelector } from '@/redux/map/map.selectors';
+import { mapOpenedMapsSelector } from '@/redux/map/map.selectors';
 import { openMapAndSetActive, setActiveMap, setMapPosition } from '@/redux/map/map.slice';
 import { closeModal, setOverviewImageId } from '@/redux/modal/modal.slice';
 import { currentModelIdSelector, modelsDataSelector } from '@/redux/models/models.selectors';
 import { projectOverviewImagesSelector } from '@/redux/project/project.selectors';
-import { MapModel, OverviewImageLink, OverviewImageLinkModel } from '@/types/models';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { MapModel, OverviewImageLink, OverviewImageLinkModel } from '@/types/models';
 import {
   OverviewImageLinkImageHandler,
   OverviewImageLinkModelHandler,
@@ -23,7 +23,6 @@ export const useOverviewImageLinkActions = (): UseOverviewImageLinkActionsResult
   const models = useAppSelector(modelsDataSelector);
   const overviewImages = useAppSelector(projectOverviewImagesSelector);
   const currentMapModelId = useAppSelector(currentModelIdSelector);
-  const mapLastPosition = useAppSelector(mapDataLastPositionSelector);
 
   const checkIfImageIsAvailable = (imageId: number): boolean =>
     overviewImages.some(image => image.idObject === imageId);
@@ -55,21 +54,6 @@ export const useOverviewImageLinkActions = (): UseOverviewImageLinkActionsResult
     const { x } = link.modelPoint;
     const { y } = link.modelPoint;
 
-    if (mapLastPosition.z !== zoom) {
-      PluginsEventBus.dispatchEvent('onZoomChanged', {
-        modelId: currentMapModelId,
-        zoom,
-      });
-    }
-
-    if (mapLastPosition.x !== x || mapLastPosition.y !== y) {
-      PluginsEventBus.dispatchEvent('onCenterChanged', {
-        modelId: currentMapModelId,
-        x,
-        y,
-      });
-    }
-
     dispatch(
       setMapPosition({
         x,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
index 308e52405d55345fc2345155ff60abe1abc25da5..61169d09657b1c2fe0672cb10e928733067d71c4 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
@@ -17,6 +17,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -53,6 +54,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -89,6 +91,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
index b5c8b6b555fc20703fdc2b0344fc1dbc3b37020f..f1cfe2e75b46061f6149f2e1a03b121d0bf9bb34 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
@@ -18,6 +18,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -49,6 +50,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -77,6 +79,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
index b5e9beb81e816e0c24690dc03128b31d86f26c17..b257be4c01c5af410019f0e4699a8fbecf063ff3 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
@@ -17,6 +17,7 @@ describe('useOverviewImageUrl - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -43,6 +44,7 @@ describe('useOverviewImageUrl - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0180c8a68058e01002993b279d338b03ff0b027b
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx
@@ -0,0 +1,83 @@
+import { AppDispatch, RootState } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
+import { ClearAnchorsButton } from './ClearAnchorsButton.component';
+
+const renderComponent = (
+  initialStore: InitialStoreState = {},
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+
+  return (
+    render(
+      <Wrapper>
+        <ClearAnchorsButton />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('ClearAnchorsButton - component', () => {
+  it('should trigger clear actions on clear button click and close result drawer if it is open', () => {
+    const { store } = renderComponent({
+      drawer: {
+        ...DRAWER_INITIAL_STATE,
+        drawerName: 'bio-entity',
+        isOpen: true,
+      },
+    });
+
+    const button = screen.getByTitle('Clear');
+
+    act(() => {
+      fireEvent.click(button);
+    });
+
+    const actions = store.getActions();
+
+    expect(actions).toEqual([
+      { payload: undefined, type: 'drawer/closeDrawer' },
+      { payload: undefined, type: 'contextMenu/closeContextMenu' },
+      { payload: undefined, type: 'reactions/resetReactionsData' },
+      { payload: undefined, type: 'search/clearSearchData' },
+      { payload: undefined, type: 'bioEntityContents/clearBioEntitiesData' },
+      { payload: undefined, type: 'drugs/clearDrugsData' },
+      { payload: undefined, type: 'chemicals/clearChemicalsData' },
+    ]);
+  });
+  it('should trigger clear actions on clear button click and not close result drawer if it is not open', () => {
+    const { store } = renderComponent({
+      drawer: {
+        ...DRAWER_INITIAL_STATE,
+        drawerName: 'bio-entity',
+        isOpen: false,
+      },
+    });
+
+    const button = screen.getByTitle('Clear');
+
+    act(() => {
+      fireEvent.click(button);
+    });
+
+    const actions = store.getActions();
+
+    expect(actions).toEqual([
+      { payload: undefined, type: 'contextMenu/closeContextMenu' },
+      { payload: undefined, type: 'reactions/resetReactionsData' },
+      { payload: undefined, type: 'search/clearSearchData' },
+      { payload: undefined, type: 'bioEntityContents/clearBioEntitiesData' },
+      { payload: undefined, type: 'drugs/clearDrugsData' },
+      { payload: undefined, type: 'chemicals/clearChemicalsData' },
+    ]);
+  });
+});
diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3be4a77026bbe811877e473dbf4bee1ff76b86d7
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx
@@ -0,0 +1,54 @@
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+import { clearChemicalsData } from '@/redux/chemicals/chemicals.slice';
+import { closeContextMenu } from '@/redux/contextMenu/contextMenu.slice';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
+import { closeDrawer } from '@/redux/drawer/drawer.slice';
+import { clearDrugsData } from '@/redux/drugs/drugs.slice';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { clearSearchData } from '@/redux/search/search.slice';
+import { Button } from '@/shared/Button';
+import { Icon } from '@/shared/Icon';
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+export const ClearAnchorsButton = (): React.ReactNode => {
+  const dispatch = useAppDispatch();
+  const isResultDrawerOpen = useSelector(resultDrawerOpen);
+
+  const closeInterfaceElements = (): void => {
+    if (isResultDrawerOpen) {
+      dispatch(closeDrawer());
+    }
+    dispatch(closeContextMenu());
+  };
+
+  const resetData = (): void => {
+    // Reset reactions list to prevent keeping the old selected reaction rendered
+    dispatch(resetReactionsData());
+
+    // Reset search data to prevent invalid filtering of the click-search ()
+    dispatch(clearSearchData());
+
+    // Reset old pins data
+    dispatch(clearBioEntitiesData());
+    dispatch(clearDrugsData());
+    dispatch(clearChemicalsData());
+  };
+
+  const onClearAnchorsClick = (): void => {
+    closeInterfaceElements();
+    resetData();
+  };
+
+  return (
+    <Button
+      className="ml-2 hover:bg-transparent active:bg-transparent"
+      onClick={onClearAnchorsClick}
+      title="Clear"
+      variantStyles="quiet"
+    >
+      <Icon name="clear" />
+    </Button>
+  );
+};
diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b04f2cc7fbb7f322d5495fa5be727bf9f2034ab
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts
@@ -0,0 +1 @@
+export { ClearAnchorsButton } from './ClearAnchorsButton.component';
diff --git a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.test.tsx b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.test.tsx
index 4925b9c9b892ab79626a1f0832aeb5f31c6c6fca..53b8716b44e52f463718690853c455dfed68eb92 100644
--- a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.test.tsx
+++ b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.test.tsx
@@ -1,11 +1,16 @@
+import { reactionsFixture } from '@/models/fixtures/reactionFixture';
+import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
 import { StoreType } from '@/redux/store';
-import { useRouter } from 'next/router';
-import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
 import { fireEvent, render, screen } from '@testing-library/react';
+import { useRouter } from 'next/router';
 import { SearchBar } from './SearchBar.component';
 
-const renderComponent = (): { store: StoreType } => {
-  const { Wrapper, store } = getReduxWrapperWithStore();
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
 
   return (
     render(
@@ -89,4 +94,24 @@ describe('SearchBar - component', () => {
 
     expect(selectedSearchElement).toBe('nadh');
   });
+
+  it('should reset reactions data on search click', () => {
+    const { store } = renderComponent({
+      reactions: {
+        ...INITIAL_STORE_STATE_MOCK.reactions,
+        data: reactionsFixture,
+      },
+    });
+    const input = screen.getByTestId<HTMLInputElement>('search-input');
+
+    fireEvent.change(input, { target: { value: 'nadh' } });
+
+    const button = screen.getByRole('button');
+
+    fireEvent.click(button);
+
+    const { reactions } = store.getState();
+
+    expect(reactions.data).toStrictEqual([]);
+  });
 });
diff --git a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.tsx b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.tsx
index ee5d31369ed9062989ef97c07c4d762c1933cb1c..5671f01fdd381b6c182e666094c2a1d535795502 100644
--- a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.tsx
+++ b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.component.tsx
@@ -2,14 +2,15 @@ import lensIcon from '@/assets/vectors/icons/lens.svg';
 import { isDrawerOpenSelector } from '@/redux/drawer/drawer.selectors';
 import { openSearchDrawerWithSelectedTab, selectTab } from '@/redux/drawer/drawer.slice';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
 import {
   isPendingSearchStatusSelector,
   perfectMatchSelector,
 } from '@/redux/search/search.selectors';
 import { getSearchData } from '@/redux/search/search.thunks';
 import Image from 'next/image';
-import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react';
 import { useRouter } from 'next/router';
+import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react';
 import { useSelector } from 'react-redux';
 import { getDefaultSearchTab, getSearchValuesArrayAndTrimToSeven } from './SearchBar.utils';
 
@@ -45,6 +46,7 @@ export const SearchBar = (): JSX.Element => {
   const onSearchClick = (): void => {
     const searchValues = getSearchValuesArrayAndTrimToSeven(searchValue);
 
+    dispatch(resetReactionsData());
     dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch }));
     openSearchDrawerIfClosed(getDefaultSearchTab(searchValues));
   };
@@ -53,6 +55,7 @@ export const SearchBar = (): JSX.Element => {
     const searchValues = getSearchValuesArrayAndTrimToSeven(searchValue);
 
     if (event.code === ENTER_KEY_CODE) {
+      dispatch(resetReactionsData());
       dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch }));
       openSearchDrawerIfClosed(getDefaultSearchTab(searchValues));
     }
diff --git a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils.ts b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils.ts
index 9b2eceb4c2ecec3144cc4899e6872ae9d9aa55b8..de535a1ffeeb102dc7075e6945bc1264f1f71f8e 100644
--- a/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils.ts
+++ b/src/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils.ts
@@ -1,8 +1,9 @@
 const BEGINNING = 0;
 const END = 6;
+const SEPARATOR = ';';
 
 export const getSearchValuesArrayAndTrimToSeven = (searchString: string): string[] =>
-  searchString.split(';').slice(BEGINNING, END);
+  searchString.split(SEPARATOR).slice(BEGINNING, END);
 
 export const getDefaultSearchTab = (searchValues: string[]): string => {
   const FIRST = 0;
diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.tsx
index 2ce8270c5d371b53aaec5e10c6157a045ebb8b21..e67e8e4d9f9e88170798973a1f22018b3f7e3eaa 100644
--- a/src/components/FunctionalArea/TopBar/TopBar.component.tsx
+++ b/src/components/FunctionalArea/TopBar/TopBar.component.tsx
@@ -5,6 +5,7 @@ import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { projectDataSelector } from '@/redux/project/project.selectors';
 import { Button } from '@/shared/Button';
+import { ClearAnchorsButton } from './ClearAnchorsButton';
 
 export const TopBar = (): JSX.Element => {
   const dispatch = useAppDispatch();
@@ -23,11 +24,12 @@ export const TopBar = (): JSX.Element => {
       <div className="flex flex-row items-center">
         <UserAvatar />
         <SearchBar />
+        <ClearAnchorsButton />
         <Button
           icon="plus"
           isIcon
           isFrontIcon
-          className="ml-8 mr-4"
+          className="ml-5 mr-4"
           onClick={onSubmapsClick}
           title="Submaps"
         >
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts
index f54c8f5bd2c0bf9ce531f8482d7c0cee13928338..3b826b1aba1924eb0bdabfc5807dd9e2153fe956 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts
@@ -8,55 +8,38 @@ import axios, { HttpStatusCode } from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { act } from 'react-dom/test-utils';
 import { showToast } from '@/utils/showToast';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { useLoadPlugin } from './useLoadPlugin';
 
 const mockedAxiosClient = new MockAdapter(axios);
 jest.mock('../../../../../../services/pluginsManager/pluginsManager');
 jest.mock('../../../../../../utils/showToast');
 
+const STATE_MOCK = {
+  ...INITIAL_STORE_STATE_MOCK,
+  plugins: {
+    ...INITIAL_STORE_STATE_MOCK.plugins,
+    activePlugins: {
+      pluginsId: [pluginFixture.hash],
+      data: {
+        [pluginFixture.hash]: pluginFixture,
+      },
+    },
+    list: INITIAL_STORE_STATE_MOCK.plugins.list,
+  },
+  legend: {
+    ...LEGEND_INITIAL_STATE_MOCK,
+    pluginLegend: {
+      [pluginFixture.hash]: ['url1', 'url2'],
+    },
+  },
+};
+
 describe('useLoadPlugin', () => {
   afterEach(() => {
     jest.restoreAllMocks();
   });
-  it('should unload plugin successfully', async () => {
-    const { Wrapper, store } = getReduxStoreWithActionsListener({
-      ...INITIAL_STORE_STATE_MOCK,
-      plugins: {
-        ...INITIAL_STORE_STATE_MOCK.plugins,
-        activePlugins: {
-          pluginsId: [pluginFixture.hash],
-          data: {
-            [pluginFixture.hash]: pluginFixture,
-          },
-        },
-        list: INITIAL_STORE_STATE_MOCK.plugins.list,
-      },
-    });
-
-    const {
-      result: {
-        current: { isPluginActive, isPluginLoading, togglePlugin },
-      },
-    } = renderHook(
-      () => useLoadPlugin({ hash: pluginFixture.hash, pluginUrl: pluginFixture.urls[0] }),
-      {
-        wrapper: Wrapper,
-      },
-    );
-
-    expect(isPluginActive).toBe(true);
-    expect(isPluginLoading).toBe(false);
 
-    act(() => {
-      togglePlugin();
-    });
-
-    const actions = store.getActions();
-    expect(actions[0]).toEqual({
-      payload: { pluginId: pluginFixture.hash },
-      type: 'plugins/removePlugin',
-    });
-  });
   it('should load plugin successfully', async () => {
     const hash = 'pluginHash';
     const pluginUrl = 'http://example.com/plugin.js';
@@ -115,4 +98,106 @@ describe('useLoadPlugin', () => {
       });
     });
   });
+  describe('when unload plugin', () => {
+    it('should unload plugin successfully', async () => {
+      const { Wrapper, store } = getReduxStoreWithActionsListener(STATE_MOCK);
+
+      const {
+        result: {
+          current: { isPluginActive, isPluginLoading, togglePlugin },
+        },
+      } = renderHook(
+        () => useLoadPlugin({ hash: pluginFixture.hash, pluginUrl: pluginFixture.urls[0] }),
+        {
+          wrapper: Wrapper,
+        },
+      );
+
+      expect(isPluginActive).toBe(true);
+      expect(isPluginLoading).toBe(false);
+
+      act(() => {
+        togglePlugin();
+      });
+
+      const actions = store.getActions();
+      expect(actions[0]).toEqual({
+        payload: { pluginId: pluginFixture.hash },
+        type: 'plugins/removePlugin',
+      });
+    });
+    it('should remove plugin legend', () => {
+      const { Wrapper, store } = getReduxStoreWithActionsListener(STATE_MOCK);
+
+      const {
+        result: {
+          current: { togglePlugin },
+        },
+      } = renderHook(
+        () => useLoadPlugin({ hash: pluginFixture.hash, pluginUrl: pluginFixture.urls[0] }),
+        {
+          wrapper: Wrapper,
+        },
+      );
+
+      act(() => {
+        togglePlugin();
+      });
+
+      const actions = store.getActions();
+
+      expect(actions).toEqual([
+        {
+          payload: { pluginId: pluginFixture.hash },
+          type: 'plugins/removePlugin',
+        },
+        {
+          payload: pluginFixture.hash,
+          type: 'legend/removePluginLegend',
+        },
+      ]);
+    });
+
+    it('should set active legend to main legend if plugin legend was active one', () => {
+      const { Wrapper, store } = getReduxStoreWithActionsListener({
+        ...STATE_MOCK,
+        legend: {
+          ...STATE_MOCK.legend,
+          activeLegendId: pluginFixture.hash,
+        },
+      });
+
+      const {
+        result: {
+          current: { togglePlugin },
+        },
+      } = renderHook(
+        () => useLoadPlugin({ hash: pluginFixture.hash, pluginUrl: pluginFixture.urls[0] }),
+        {
+          wrapper: Wrapper,
+        },
+      );
+
+      act(() => {
+        togglePlugin();
+      });
+
+      const actions = store.getActions();
+
+      expect(actions).toEqual([
+        {
+          payload: { pluginId: pluginFixture.hash },
+          type: 'plugins/removePlugin',
+        },
+        {
+          payload: undefined,
+          type: 'legend/setDefaultLegendId',
+        },
+        {
+          payload: pluginFixture.hash,
+          type: 'legend/removePluginLegend',
+        },
+      ]);
+    });
+  });
 });
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
index ebce11ec92f224772b10aa40c48e33b1cc9051b7..6ebfcea929a1c0472f8cf3493ab39056b475b823 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
@@ -1,5 +1,7 @@
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { isActiveLegendSelector } from '@/redux/legend/legend.selectors';
+import { removePluginLegend, setDefaultLegendId } from '@/redux/legend/legend.slice';
 import {
   isPluginActiveSelector,
   isPluginLoadingSelector,
@@ -7,9 +9,10 @@ import {
 } from '@/redux/plugins/plugins.selectors';
 import { removePlugin } from '@/redux/plugins/plugins.slice';
 import { PluginsManager } from '@/services/pluginsManager';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { showToast } from '@/utils/showToast';
 import axios from 'axios';
-import { getErrorMessage } from '@/utils/getErrorMessage';
 import { PLUGIN_LOADING_ERROR_PREFIX } from '../../AvailablePluginsDrawer.constants';
 
 type UseLoadPluginReturnType = {
@@ -36,22 +39,25 @@ export const useLoadPlugin = ({
   const isPluginActive = useAppSelector(state => isPluginActiveSelector(state, hash));
   const isPluginLoading = useAppSelector(state => isPluginLoadingSelector(state, hash));
   const isPluginSelected = useAppSelector(state => isPluginSelectedSelector(state, hash));
+  const isActivePluginLegend = useAppSelector(state => isActiveLegendSelector(state, hash));
 
   const dispatch = useAppDispatch();
 
   const handleLoadPlugin = async (): Promise<void> => {
     try {
       const response = await axios(pluginUrl);
-      const pluginScript = response.data;
-
-      /* eslint-disable no-new-func */
-      const loadPlugin = new Function(pluginScript);
+      let pluginScript = response.data;
 
       PluginsManager.setHashedPlugin({
         pluginUrl,
         pluginScript,
       });
 
+      pluginScript += `//# sourceURL=${pluginUrl}`;
+
+      /* eslint-disable no-new-func */
+      const loadPlugin = new Function(pluginScript);
+
       loadPlugin();
 
       if (onPluginLoaded) {
@@ -66,9 +72,21 @@ export const useLoadPlugin = ({
     }
   };
 
+  const handleRemoveLegend = (): void => {
+    if (isActivePluginLegend) {
+      dispatch(setDefaultLegendId());
+    }
+
+    dispatch(removePluginLegend(hash));
+  };
+
   const handleUnloadPlugin = (): void => {
     dispatch(removePlugin({ pluginId: hash }));
+
+    handleRemoveLegend();
+
     PluginsManager.removePluginContent({ hash });
+    PluginsEventBus.dispatchEvent('onPluginUnload', { hash });
   };
 
   const handleReloadPlugin = async (): Promise<void> => {
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
index f95bdf7e4beaa78a655090c648652a8ff20fa388..1b7c935f9365a150d9c0abfff44f20643d4c0d0f 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
@@ -35,16 +35,18 @@ export const useLoadPluginFromUrl = (): UseLoadPluginReturnType => {
     try {
       setIsLoading(true);
       const response = await axios(pluginUrl);
-      const pluginScript = response.data;
-
-      /* eslint-disable no-new-func */
-      const loadPlugin = new Function(pluginScript);
+      let pluginScript = response.data;
 
       const hash = PluginsManager.setHashedPlugin({
         pluginUrl,
         pluginScript,
       });
 
+      pluginScript += `//# sourceURL=${pluginUrl}`;
+
+      /* eslint-disable no-new-func */
+      const loadPlugin = new Function(pluginScript);
+
       if (!(hash in activePlugins)) {
         loadPlugin();
       }
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
index b9966147dd4a91994d39bf8b39a03f66f9e6224c..0d0561aa585f5c3aebc20f4a67bdc0cc6334d2fa 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
@@ -88,6 +88,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -111,6 +112,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -181,6 +183,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -225,6 +228,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: DEFAULT_ERROR,
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -283,6 +287,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: DEFAULT_ERROR,
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
diff --git a/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts b/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts
index fb2eae6c68e0e26cd2c240a3b2d17adaf75c266f..2536fa84e049dd7dc659040a96933d3287639436 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts
+++ b/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts
@@ -3,7 +3,6 @@ import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.select
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { setMapBackground } from '@/redux/map/map.slice';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 
 type UseEmptyBackgroundReturn = {
   setBackgroundtoEmptyIfAvailable: () => void;
@@ -16,8 +15,6 @@ export const useEmptyBackground = (): UseEmptyBackgroundReturn => {
   const setBackgroundtoEmptyIfAvailable = useCallback(() => {
     if (emptyBackgroundId) {
       dispatch(setMapBackground(emptyBackgroundId));
-
-      PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', emptyBackgroundId);
     }
   }, [dispatch, emptyBackgroundId]);
 
diff --git a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
index bfad37058b0e1ef0cb5c84863aaba191676735c1..e7e1a3ef90baa3db35e2a7779e0661d633a3a7ea 100644
--- a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
+++ b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
@@ -14,6 +14,7 @@ const MOCKED_STORE: InitialStoreState = {
     data: { ...projectFixture },
     loading: 'idle',
     error: new Error(),
+    projectId: '',
   },
   models: {
     data: [MODEL_WITH_DESCRIPTION],
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.test.tsx
index a9ec1f4c277df5cce0a1dbdd444a0a59a139f78d..6447864167fa0759a12d4ca0c95ca98671a0291b 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.test.tsx
@@ -1,7 +1,9 @@
 /* eslint-disable no-magic-numbers */
 import { DEFAULT_MAX_ZOOM } from '@/constants/map';
 import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { reactionsFixture } from '@/models/fixtures/reactionFixture';
 import { MAP_INITIAL_STATE } from '@/redux/map/map.constants';
+import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
 import { AppDispatch, RootState, StoreType } from '@/redux/store';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import {
@@ -276,4 +278,57 @@ describe('BioEntitiesPinsListItem - component ', () => {
       ]),
     );
   });
+
+  it('should reset reactions on fullName click', async () => {
+    const { store } = renderComponent(
+      BIO_ENTITY.name,
+      {
+        ...BIO_ENTITY,
+        x: undefined,
+        y: undefined,
+      },
+      {
+        map: {
+          ...MAP_INITIAL_STATE,
+          data: {
+            ...MAP_INITIAL_STATE.data,
+            modelId: 5052,
+            size: {
+              width: 256,
+              height: 256,
+              tileSize: 256,
+              minZoom: 1,
+              maxZoom: 1,
+            },
+            position: {
+              initial: {
+                x: 0,
+                y: 0,
+                z: 2,
+              },
+              last: {
+                x: 1,
+                y: 1,
+                z: 3,
+              },
+            },
+          },
+        },
+        reactions: {
+          ...INITIAL_STORE_STATE_MOCK.reactions,
+          data: reactionsFixture,
+        },
+      },
+    );
+    const button = screen.getByText(BIO_ENTITY.name);
+    expect(button).toBeInTheDocument();
+
+    act(() => {
+      button.click();
+    });
+
+    const state = store.getState();
+
+    expect(state.reactions.data).toStrictEqual([]);
+  });
 });
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.tsx
index 77e7d4edb35b70342bfb522b7b4d2331dde6d9e6..fbc712831868f42dc694bfbddd8d4796ca9e477d 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem/BioEntitiesPinsListItem.component.tsx
@@ -13,6 +13,7 @@ import { numberByEntityNumberIdSelector } from '@/redux/entityNumber/entityNumbe
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { setMapPosition } from '@/redux/map/map.slice';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
 import { getSearchData } from '@/redux/search/search.thunks';
 import { twMerge } from 'tailwind-merge';
 import { PinListBioEntity } from './BioEntitiesPinsListItem.types';
@@ -53,6 +54,7 @@ export const BioEntitiesPinsListItem = ({
 
   const handleSearchMapForPin = (): void => {
     const searchValues = getSearchValuesArrayAndTrimToSeven(name);
+    dispatch(resetReactionsData());
     dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch: true }));
     dispatch(openSearchDrawerWithSelectedTab(getDefaultSearchTab(searchValues)));
   };
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.test.tsx
index 2631f91b3e40514399ee92ab44cc6c2ccf1f5cc7..c08187dc9d36cbc6197a92f9dc4ea29711aadbca 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.test.tsx
@@ -1,11 +1,14 @@
-import { act } from 'react-dom/test-utils';
+import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, THIRD_ARRAY_ELEMENT } from '@/constants/common';
+import { SEARCH_STATE_INITIAL_MOCK } from '@/redux/search/search.mock';
+import { AppDispatch, RootState, StoreType } from '@/redux/store';
+import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import {
   InitialStoreState,
   getReduxWrapperWithStore,
 } from '@/utils/testing/getReduxWrapperWithStore';
-import { StoreType } from '@/redux/store';
 import { render, screen } from '@testing-library/react';
-import { SEARCH_STATE_INITIAL_MOCK } from '@/redux/search/search.mock';
+import { act } from 'react-dom/test-utils';
+import { MockStoreEnhanced } from 'redux-mock-store';
 import { PerfectMatchSwitch } from './PerfectMatchSwitch.component';
 
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
@@ -23,6 +26,23 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St
   );
 };
 
+const renderComponentWithActionListener = (
+  initialStoreState: InitialStoreState = {},
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStoreState);
+
+  return (
+    render(
+      <Wrapper>
+        <PerfectMatchSwitch />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
 describe('PerfectMatchSwitch - component', () => {
   it('should initialy be set to false when perfectMatch is not in query or set to false', () => {
     renderComponent({ search: SEARCH_STATE_INITIAL_MOCK });
@@ -62,4 +82,32 @@ describe('PerfectMatchSwitch - component', () => {
 
     expect(store.getState().search.perfectMatch).toBe(false);
   });
+  it('should trigger get search data and reset reactions on checked value change', async () => {
+    const { store } = renderComponentWithActionListener({
+      search: { ...SEARCH_STATE_INITIAL_MOCK, perfectMatch: false, searchValue: ['nadh', 'scna'] },
+    });
+
+    const checkbox = screen.getByRole<HTMLInputElement>('checkbox');
+    act(() => {
+      checkbox.click();
+    });
+
+    const actions = store.getActions();
+
+    expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({
+      payload: true,
+      type: 'search/setPerfectMatch',
+    });
+
+    expect(actions[SECOND_ARRAY_ELEMENT]).toStrictEqual({
+      payload: undefined,
+      type: 'reactions/resetReactionsData',
+    });
+
+    expect(actions[THIRD_ARRAY_ELEMENT].meta.arg).toStrictEqual({
+      isPerfectMatch: true,
+      searchQueries: ['nadh', 'scna'],
+    });
+    expect(actions[THIRD_ARRAY_ELEMENT].type).toEqual('project/getSearchData/pending');
+  });
 });
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.tsx
index 1e38a26326a0d6e59b01de2302bac725f3be7320..c58d239020e0a17ef7839d779bfca8dba8de7a58 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerHeader/PerfectMatchSwitch/PerfectMatchSwitch.component.tsx
@@ -1,16 +1,27 @@
 /* eslint-disable jsx-a11y/label-has-associated-control */
-import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { perfectMatchSelector } from '@/redux/search/search.selectors';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { perfectMatchSelector, searchValueSelector } from '@/redux/search/search.selectors';
 import { setPerfectMatch } from '@/redux/search/search.slice';
+import { getSearchData } from '@/redux/search/search.thunks';
 import { ChangeEvent } from 'react';
 
 export const PerfectMatchSwitch = (): JSX.Element => {
   const dispatch = useAppDispatch();
   const isChecked = useAppSelector(perfectMatchSelector);
+  const searchValue = useAppSelector(searchValueSelector);
+
+  const setChecked = (value: boolean): void => {
+    dispatch(setPerfectMatch(value));
+  };
+
+  const setCheckedAndRefreshSearch = (event: ChangeEvent<HTMLInputElement>): void => {
+    const isCheckedNewValue = event.target.checked;
 
-  const setChecked = (event: ChangeEvent<HTMLInputElement>): void => {
-    dispatch(setPerfectMatch(event.target.checked));
+    setChecked(isCheckedNewValue);
+    dispatch(resetReactionsData());
+    dispatch(getSearchData({ searchQueries: searchValue, isPerfectMatch: isCheckedNewValue }));
   };
 
   return (
@@ -22,7 +33,7 @@ export const PerfectMatchSwitch = (): JSX.Element => {
           value=""
           className="peer sr-only"
           checked={isChecked}
-          onChange={setChecked}
+          onChange={setCheckedAndRefreshSearch}
         />
         <div className="peer h-6 w-11 rounded-full bg-greyscale-500 after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-med-sea-green peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none rtl:peer-checked:after:-translate-x-full" />
       </label>
diff --git a/src/components/Map/Legend/Legend.component.test.tsx b/src/components/Map/Legend/Legend.component.test.tsx
index 4ad65a5a609f1c6843970b68345781f9bed62000..266c109cfee97985c6a1b9828ca7ee6b4e6a1257 100644
--- a/src/components/Map/Legend/Legend.component.test.tsx
+++ b/src/components/Map/Legend/Legend.component.test.tsx
@@ -1,20 +1,41 @@
-import { currentLegendImagesSelector, legendSelector } from '@/redux/legend/legend.selectors';
+import {
+  allLegendsNamesAndIdsSelector,
+  currentLegendImagesSelector,
+  legendSelector,
+  pluginLegendsSelector,
+} from '@/redux/legend/legend.selectors';
 import { StoreType } from '@/redux/store';
 import {
   InitialStoreState,
   getReduxWrapperWithStore,
 } from '@/utils/testing/getReduxWrapperWithStore';
 import { render, screen, within } from '@testing-library/react';
-import { Legend } from './Legend.component';
+import { PLUGINS_INITIAL_STATE_MOCK } from '@/redux/plugins/plugins.mock';
+import { pluginFixture } from '@/models/fixtures/pluginFixture';
+import { DEFAULT_LEGEND_TAB } from '@/redux/legend/legend.constants';
 import { LEGEND_ROLE } from './Legend.constants';
+import { Legend } from './Legend.component';
+
+const PLUGIN_ID = '1';
+const PLUGIN_NAME = 'Plugin Custom Name';
+const LEGEND_IMAGES = [
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_d.png',
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_a.png',
+  'https://lux1.atcomp.pl//minerva/resources/images/legend_b.png',
+];
 
 jest.mock('../../../redux/legend/legend.selectors', () => ({
   legendSelector: jest.fn(),
   currentLegendImagesSelector: jest.fn(),
+  allLegendsNamesAndIdsSelector: jest.fn(),
+  pluginLegendsSelector: jest.fn(),
+  isActiveLegendSelector: jest.fn(),
 }));
 
 const legendSelectorMock = legendSelector as unknown as jest.Mock;
 const currentLegendImagesSelectorMock = currentLegendImagesSelector as unknown as jest.Mock;
+const pluginLegendsSelectorMock = pluginLegendsSelector as unknown as jest.Mock;
+const allLegendsNamesAndIdsSelectorMock = allLegendsNamesAndIdsSelector as unknown as jest.Mock;
 
 const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
   const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
@@ -32,7 +53,19 @@ const renderComponent = (initialStore?: InitialStoreState): { store: StoreType }
 
 describe('Legend - component', () => {
   beforeAll(() => {
-    currentLegendImagesSelectorMock.mockImplementation(() => []);
+    allLegendsNamesAndIdsSelectorMock.mockImplementation(() => [
+      DEFAULT_LEGEND_TAB,
+      {
+        id: PLUGIN_ID,
+        name: PLUGIN_NAME,
+      },
+    ]);
+
+    pluginLegendsSelectorMock.mockImplementation(() => ({
+      PLUGIN_ID: LEGEND_IMAGES,
+    }));
+
+    currentLegendImagesSelectorMock.mockImplementation(() => LEGEND_IMAGES);
   });
 
   describe('when is closed', () => {
@@ -72,4 +105,65 @@ describe('Legend - component', () => {
       expect(legendImages).toBeInTheDocument();
     });
   });
+  describe('when loaded plugin has own legend', () => {
+    it('should display legend tabs', () => {
+      legendSelectorMock.mockImplementation(() => ({
+        isOpen: false,
+        pluginLegend: {
+          PLUGIN_ID: LEGEND_IMAGES,
+        },
+        activeLegendId: 'MAIN',
+      }));
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          activePlugins: {
+            data: {
+              PLUGIN_ID: {
+                ...pluginFixture,
+                hash: PLUGIN_ID,
+                name: PLUGIN_NAME,
+              },
+            },
+            pluginsId: [PLUGIN_ID],
+          },
+        },
+      });
+
+      expect(screen.getByText('Plugin Custom Name')).toBeVisible();
+      expect(screen.getByText('Main Legend')).toBeVisible();
+    });
+  });
+  describe('when loaded plugin does not have own legend', () => {
+    it('should not display legend tabs and display only main legend', () => {
+      allLegendsNamesAndIdsSelectorMock.mockImplementation(() => [DEFAULT_LEGEND_TAB]);
+
+      pluginLegendsSelectorMock.mockImplementation(() => ({}));
+
+      currentLegendImagesSelectorMock.mockImplementation(() => LEGEND_IMAGES);
+      legendSelectorMock.mockImplementation(() => ({
+        isOpen: false,
+        pluginLegend: {},
+        activeLegendId: 'MAIN',
+      }));
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          activePlugins: {
+            data: {
+              PLUGIN_ID: {
+                ...pluginFixture,
+                hash: PLUGIN_ID,
+                name: PLUGIN_NAME,
+              },
+            },
+            pluginsId: [PLUGIN_ID],
+          },
+        },
+      });
+
+      expect(screen.queryByText('Plugin Custom Name')).not.toBeInTheDocument();
+      expect(screen.queryByText('Main Legend')).not.toBeInTheDocument();
+    });
+  });
 });
diff --git a/src/components/Map/Legend/Legend.component.tsx b/src/components/Map/Legend/Legend.component.tsx
index 7414765ec96c9b42908538ff07ef67dd91fa2434..e515483a6cd8fc808a079434eae82c2e92ee3beb 100644
--- a/src/components/Map/Legend/Legend.component.tsx
+++ b/src/components/Map/Legend/Legend.component.tsx
@@ -1,13 +1,20 @@
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { legendSelector } from '@/redux/legend/legend.selectors';
+import { legendSelector, pluginLegendsSelector } from '@/redux/legend/legend.selectors';
 import * as React from 'react';
 import { twMerge } from 'tailwind-merge';
+import { ZERO } from '@/constants/common';
 import { LEGEND_ROLE } from './Legend.constants';
 import { LegendHeader } from './LegendHeader';
 import { LegendImages } from './LegendImages';
+import { LegendTabs } from './LegendTabs';
 
 export const Legend: React.FC = () => {
   const { isOpen } = useAppSelector(legendSelector);
+  const allPluginLegends = useAppSelector(pluginLegendsSelector);
+  const isAnyPluginLegendExists = React.useMemo(
+    () => Object.values(allPluginLegends).length > ZERO,
+    [allPluginLegends],
+  );
 
   return (
     <div
@@ -18,6 +25,7 @@ export const Legend: React.FC = () => {
       role={LEGEND_ROLE}
     >
       <LegendHeader />
+      {isAnyPluginLegendExists ? <LegendTabs /> : null}
       <LegendImages />
     </div>
   );
diff --git a/src/components/Map/Legend/LegendImages/LegendImages.component.test.tsx b/src/components/Map/Legend/LegendImages/LegendImages.component.test.tsx
index e6d2681f440694b773c135e4fd303df4788c7516..87fb07574489553dfccf2ac12380b990b644aecd 100644
--- a/src/components/Map/Legend/LegendImages/LegendImages.component.test.tsx
+++ b/src/components/Map/Legend/LegendImages/LegendImages.component.test.tsx
@@ -1,4 +1,3 @@
-import { BASE_MAP_IMAGES_URL } from '@/constants';
 import { currentLegendImagesSelector, legendSelector } from '@/redux/legend/legend.selectors';
 import { StoreType } from '@/redux/store';
 import {
@@ -60,7 +59,7 @@ describe('LegendImages - component', () => {
       const imgElement = screen.getByAltText(partialUrl);
 
       expect(imgElement).toBeInTheDocument();
-      expect(imgElement.getAttribute('src')).toBe(`${BASE_MAP_IMAGES_URL}/minerva/${partialUrl}`);
+      expect(imgElement.getAttribute('src')).toBe(partialUrl);
     });
   });
 });
diff --git a/src/components/Map/Legend/LegendImages/LegendImages.component.tsx b/src/components/Map/Legend/LegendImages/LegendImages.component.tsx
index dfc49d8fe3d51cee153d75a2adc952fe9f6541e6..8a56a05071dd8f2c55ba5a23384638663d2ca976 100644
--- a/src/components/Map/Legend/LegendImages/LegendImages.component.tsx
+++ b/src/components/Map/Legend/LegendImages/LegendImages.component.tsx
@@ -1,5 +1,4 @@
 /* eslint-disable @next/next/no-img-element */
-import { BASE_MAP_IMAGES_URL } from '@/constants';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { currentLegendImagesSelector } from '@/redux/legend/legend.selectors';
 
@@ -12,12 +11,7 @@ export const LegendImages: React.FC = () => {
       className="flex items-center justify-between overflow-x-auto border-b border-b-divide px-6 py-8"
     >
       {imageUrls.map(imageUrl => (
-        <img
-          key={imageUrl}
-          src={`${BASE_MAP_IMAGES_URL}/minerva/${imageUrl}`}
-          alt={imageUrl}
-          className="h-[400px] max-h-[50vh]"
-        />
+        <img key={imageUrl} src={imageUrl} alt={imageUrl} className="h-[400px] max-h-[50vh]" />
       ))}
     </div>
   );
diff --git a/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.test.tsx b/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2667820cc807a6e6712500c2f5109460c2611c60
--- /dev/null
+++ b/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.test.tsx
@@ -0,0 +1,45 @@
+import { StoreType } from '@/redux/store';
+import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { render, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { LegendTab } from './LegendTab.component';
+
+const LEGEND_ID = '2';
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <LegendTab id={LEGEND_ID} name="Legend Tab Name" />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('LegendTab - component', () => {
+  it('should display tab with provided name', () => {
+    renderComponent();
+
+    expect(screen.queryByText('Legend Tab Name')).toBeVisible();
+  });
+  it('should set tab as active on tab click', () => {
+    const { store } = renderComponent();
+    const tab = screen.queryByText('Legend Tab Name');
+    expect(tab).toHaveClass('font-normal');
+
+    act(() => {
+      tab?.click();
+    });
+
+    const { activeLegendId } = store.getState().legend;
+    expect(activeLegendId).toBe(LEGEND_ID);
+
+    expect(tab).not.toHaveClass('font-normal');
+    expect(tab).toHaveClass('bg-[#EBF4FF]');
+  });
+});
diff --git a/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.tsx b/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..094e8ab97b659581288d2c7427e8cdb6170a3b93
--- /dev/null
+++ b/src/components/Map/Legend/LegendTabs/LegendTab/LegendTab.component.tsx
@@ -0,0 +1,28 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { isActiveLegendSelector } from '@/redux/legend/legend.selectors';
+import { setActiveLegendId } from '@/redux/legend/legend.slice';
+import { Button } from '@/shared/Button';
+import React from 'react';
+import { twMerge } from 'tailwind-merge';
+
+type LegendTypeProps = { id: string; name: string };
+
+export const LegendTab = ({ id, name }: LegendTypeProps): React.ReactNode => {
+  const dispatch = useAppDispatch();
+  const isActiveLegend = useAppSelector(state => isActiveLegendSelector(state, id));
+
+  const handleLegendTabClick = (): void => {
+    dispatch(setActiveLegendId(id));
+  };
+
+  return (
+    <Button
+      className={twMerge('h-10 whitespace-nowrap', isActiveLegend ? 'bg-[#EBF4FF]' : 'font-normal')}
+      variantStyles={isActiveLegend ? 'secondary' : 'ghost'}
+      onClick={handleLegendTabClick}
+    >
+      {name}
+    </Button>
+  );
+};
diff --git a/src/components/Map/Legend/LegendTabs/LegendTab/index.ts b/src/components/Map/Legend/LegendTabs/LegendTab/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..96003da9a63f2475c78d8ced4fe43a95474ea402
--- /dev/null
+++ b/src/components/Map/Legend/LegendTabs/LegendTab/index.ts
@@ -0,0 +1 @@
+export { LegendTab } from './LegendTab.component';
diff --git a/src/components/Map/Legend/LegendTabs/LegendTabs.component.tsx b/src/components/Map/Legend/LegendTabs/LegendTabs.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..216e6ac11710658bb60c2bc008dc24ddcbd6397a
--- /dev/null
+++ b/src/components/Map/Legend/LegendTabs/LegendTabs.component.tsx
@@ -0,0 +1,16 @@
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { allLegendsNamesAndIdsSelector } from '@/redux/legend/legend.selectors';
+import React from 'react';
+import { LegendTab } from './LegendTab/LegendTab.component';
+
+export const LegendTabs = (): React.ReactNode => {
+  const allLegendsNamesAndIds = useAppSelector(allLegendsNamesAndIdsSelector);
+
+  return (
+    <div className="flex h-10 w-full flex-row flex-nowrap justify-start border-b border-b-divide bg-white-pearl text-xs">
+      {allLegendsNamesAndIds.map(({ id, name }) => (
+        <LegendTab name={name} id={id} key={id} />
+      ))}
+    </div>
+  );
+};
diff --git a/src/components/Map/Legend/LegendTabs/index.ts b/src/components/Map/Legend/LegendTabs/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bea41d1d71770d9172c792a43e970aa6825808b4
--- /dev/null
+++ b/src/components/Map/Legend/LegendTabs/index.ts
@@ -0,0 +1 @@
+export { LegendTabs } from './LegendTabs.component';
diff --git a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.ts b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.ts
index 209499399e2e28b88e311ca935895b0a1304e076..e80346b5d069b42e41ffaa1d690248fa91ad6497 100644
--- a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.ts
+++ b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.ts
@@ -1,14 +1,12 @@
+import { DEFAULT_ZOOM } from '@/constants/map';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { setMapPosition, varyPositionZoom } from '@/redux/map/map.slice';
+import { currentModelIdSelector, modelByIdSelector } from '@/redux/models/models.selectors';
 import { SetBoundsResult, useSetBounds } from '@/utils/map/useSetBounds';
 import { useCallback } from 'react';
 import { useDispatch } from 'react-redux';
-import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { currentModelIdSelector, modelByIdSelector } from '@/redux/models/models.selectors';
-import { DEFAULT_ZOOM } from '@/constants/map';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
-import { mapDataLastPositionSelector } from '@/redux/map/map.selectors';
-import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
 import { MAP_ZOOM_IN_DELTA, MAP_ZOOM_OUT_DELTA } from '../MappAdditionalActions.constants';
+import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
 
 interface UseAddtionalActionsResult {
   zoomIn(): void;
@@ -22,7 +20,6 @@ export const useAddtionalActions = (): UseAddtionalActionsResult => {
   const polygonCoordinates = useVisibleBioEntitiesPolygonCoordinates();
   const currentMapModelId = useAppSelector(currentModelIdSelector);
   const currentModel = useAppSelector(state => modelByIdSelector(state, currentMapModelId));
-  const currentModelLastPostiion = useAppSelector(mapDataLastPositionSelector);
 
   const zoomInToBioEntities = (): SetBoundsResult | undefined => {
     if (polygonCoordinates) {
@@ -38,24 +35,6 @@ export const useAddtionalActions = (): UseAddtionalActionsResult => {
       };
 
       dispatch(setMapPosition(defaultPosition));
-
-      if (currentModelLastPostiion.z !== defaultPosition.z) {
-        PluginsEventBus.dispatchEvent('onZoomChanged', {
-          modelId: currentMapModelId,
-          zoom: defaultPosition.z,
-        });
-      }
-
-      if (
-        currentModelLastPostiion.x !== defaultPosition.x ||
-        currentModelLastPostiion.y !== defaultPosition.y
-      ) {
-        PluginsEventBus.dispatchEvent('onCenterChanged', {
-          modelId: currentMapModelId,
-          x: defaultPosition.x,
-          y: defaultPosition.y,
-        });
-      }
     }
 
     return undefined;
diff --git a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx
index 7b31ff35d5928f6e36482d9e7dbc92a7badc1f89..ca0dd73392f972a5285211dba89fb71d1a87a102 100644
--- a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx
+++ b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx
@@ -10,7 +10,6 @@ import { Icon } from '@/shared/Icon';
 import { MapBackground } from '@/types/models';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { setMapBackground } from '@/redux/map/map.slice';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 
 const DEFAULT_TOGGLE_BUTTON_TEXT = 'Background';
 
@@ -23,7 +22,6 @@ export const BackgroundSelector = (): JSX.Element => {
   const onItemSelect = (background: MapBackground | undefined | null): void => {
     if (background) {
       dispatch(setMapBackground(background.id));
-      PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', background.id);
     }
   };
 
diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
index 19571c28cdedc3cc2b49845b5f60e0f9a53f0350..ff8459b49d6a8039abdb5126f4465d0874666f50 100644
--- a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
+++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
@@ -12,6 +12,8 @@ import {
 } from '@/utils/testing/getReduxWrapperWithStore';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { PROJECT_STATE_INITIAL_MOCK } from '@/redux/project/project.mock';
+import { projectFixture } from '@/models/fixtures/projectFixture';
 import { MapAdditionalOptions } from './MapAdditionalOptions.component';
 
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
@@ -59,6 +61,10 @@ describe('MapAdditionalOptions - component', () => {
 
   it('should open overview image modal on button click', () => {
     const { store } = renderComponentWithActionListener({
+      project: {
+        ...PROJECT_STATE_INITIAL_MOCK,
+        data: projectFixture,
+      },
       map: initialMapStateFixture,
       backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
     });
@@ -68,8 +74,28 @@ describe('MapAdditionalOptions - component', () => {
 
     const actions = store.getActions();
     expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({
-      payload: 0,
+      payload: projectFixture.topOverviewImage?.idObject,
       type: 'modal/openOverviewImagesModalById',
     });
   });
+  it('should disable button browse overview images if there are no overview images', () => {
+    const { store } = renderComponentWithActionListener({
+      project: {
+        ...PROJECT_STATE_INITIAL_MOCK,
+        data: {
+          ...projectFixture,
+          overviewImageViews: [],
+        },
+      },
+      map: initialMapStateFixture,
+      backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
+    });
+
+    const overviewImageButton = screen.getByText('Browse overview images');
+    expect(overviewImageButton).toBeDisabled();
+    overviewImageButton.click();
+
+    const actions = store.getActions();
+    expect(actions).toStrictEqual([]);
+  });
 });
diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
index 84dec36a881d484691c2845d80688ca9dba1056c..a06ecbdb7e999f2127996e28edafd20d4baeed66 100644
--- a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
+++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
@@ -1,9 +1,13 @@
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { openOverviewImagesModalById } from '@/redux/modal/modal.slice';
-import { projectDefaultOverviewImageIdSelector } from '@/redux/project/project.selectors';
+import {
+  projectDefaultOverviewImageIdSelector,
+  projectOverviewImagesSelector,
+} from '@/redux/project/project.selectors';
 import { Button } from '@/shared/Button';
 import { useSelector } from 'react-redux';
 import { twMerge } from 'tailwind-merge';
+import { ZERO } from '@/constants/common';
 import { BackgroundSelector } from './BackgroundsSelector';
 
 // top-[calc(64px+40px+24px)] -> TOP_BAR_HEIGHT+MAP_NAVIGATION_HEIGHT+DISTANCE_FROM_MAP_NAVIGATION
@@ -11,6 +15,8 @@ import { BackgroundSelector } from './BackgroundsSelector';
 export const MapAdditionalOptions = (): JSX.Element => {
   const dispatch = useAppDispatch();
   const defaultOverviewImageId = useSelector(projectDefaultOverviewImageIdSelector);
+  const overviewImages = useSelector(projectOverviewImagesSelector);
+  const overviewImagesEmpty = overviewImages.length === ZERO;
 
   const handleBrowseOverviewImagesClick = (): void => {
     dispatch(openOverviewImagesModalById(defaultOverviewImageId));
@@ -18,7 +24,11 @@ export const MapAdditionalOptions = (): JSX.Element => {
 
   return (
     <div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10 flex')}>
-      <Button className="mr-4" onClick={handleBrowseOverviewImagesClick}>
+      <Button
+        className="mr-4"
+        onClick={handleBrowseOverviewImagesClick}
+        disabled={overviewImagesEmpty}
+      >
         Browse overview images
       </Button>
       <BackgroundSelector />
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
index 174d0a138eab987b1fb7b3019f34b8659a6efea4..ffa0b76e1564bd0fbcd0c638744ef529a650a27b 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
@@ -56,9 +56,11 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
       center: [center.x, center.y],
       zoom: mapInitialPosition.z,
       showFullExtent: OPTIONS.showFullExtent,
+      maxZoom: mapSize.maxZoom,
+      minZoom: mapSize.minZoom,
       extent,
     }),
-    [mapInitialPosition.z, center, extent],
+    [mapInitialPosition.z, mapSize.maxZoom, mapSize.minZoom, center, extent],
   );
 
   const view = useMemo(() => new View(viewConfig), [viewConfig]);
diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
index a957771e4eb8f3cd53824aab5de92347eb96af7f..2288c0c6deb6bedef430c00b506fbd67ff5c7fe8 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
@@ -14,7 +14,7 @@ export const onMapRightClick =
     dispatch(handleDataReset);
     dispatch(openContextMenu(pixel));
 
-    const searchResults = await getSearchResults({ coordinate, mapSize, modelId });
+    const { searchResults } = await getSearchResults({ coordinate, mapSize, modelId });
     if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) {
       return;
     }
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7dcf389a3dd7f3e22337040b0b6dd783395dd20b
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts
@@ -0,0 +1,48 @@
+/* eslint-disable no-magic-numbers */
+import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { findClosestBioEntityPoint } from './findClosestBioEntityPoint';
+
+describe('findClosestBioEntityPoint', () => {
+  const bioEntityContents = [
+    {
+      ...bioEntityContentFixture,
+      bioEntity: { ...bioEntityContentFixture.bioEntity, x: 10, y: 10, width: 20, height: 20 },
+    },
+    {
+      ...bioEntityContentFixture,
+      bioEntity: { ...bioEntityContentFixture.bioEntity, x: 50, y: 50, width: 30, height: 30 },
+    },
+  ];
+
+  const validPoint = { x: 15, y: 15 };
+  const invalidPoint = {
+    x: 500,
+    y: 300,
+  };
+
+  const searchDistance = '10';
+  const maxZoom = 5;
+  const zoom = 2;
+
+  it('should return the closest bioEntity within the search distance', () => {
+    const result = findClosestBioEntityPoint(
+      bioEntityContents,
+      searchDistance,
+      maxZoom,
+      zoom,
+      validPoint,
+    );
+    expect(result).toEqual(bioEntityContents[0]);
+  });
+
+  it('should return undefined if no matching bioEntity is found within the search distance', () => {
+    const result = findClosestBioEntityPoint(
+      bioEntityContents,
+      searchDistance,
+      maxZoom,
+      zoom,
+      invalidPoint,
+    );
+    expect(result).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7150b506832aad15760e632091660191f009d09
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts
@@ -0,0 +1,29 @@
+import { Point as PointType } from '@/types/map';
+import { BioEntityContent } from '@/types/models';
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+export const findClosestBioEntityPoint = (
+  bioEntityContents: BioEntityContent[],
+  searchDistance: string,
+  maxZoom: number,
+  zoom: number,
+  point: PointType,
+): BioEntityContent | undefined => {
+  const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance);
+
+  const matchingBioEntityFound = bioEntityContents.find(bio => {
+    const { x, y, width, height } = bio.bioEntity;
+
+    const minX = x - maxDistance;
+    const maxX = x + width + maxDistance;
+    const minY = y - maxDistance;
+    const maxY = y + height + maxDistance;
+
+    const withinXRange = point.x >= minX && point.x <= maxX;
+    const withinYRange = point.y >= minY && point.y <= maxY;
+
+    return withinXRange && withinYRange;
+  });
+
+  return matchingBioEntityFound;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b318ffc16bb9d58724352e7f05ec6390fa5751bd
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts
@@ -0,0 +1,43 @@
+/* eslint-disable no-magic-numbers */
+import { reactionsFixture } from '@/models/fixtures/reactionFixture';
+import { findClosestReactionPoint } from './findClosestReactionPoint';
+
+describe('findClosestReactionPoint', () => {
+  const reaction = {
+    ...reactionsFixture[0],
+    lines: [{ start: { x: 0, y: 0 }, end: { x: 3, y: 4 }, type: 'START' }],
+  };
+
+  const validPoint = { x: 1, y: 1 };
+  const invalidPoint = {
+    x: 1115,
+    y: 2225,
+  };
+  const searchDistance = '10';
+  const maxZoom = 10;
+  const zoom = 5;
+
+  it('should return the matching line segment if a point within the search distance is found', () => {
+    const result = findClosestReactionPoint({
+      reaction,
+      searchDistance,
+      maxZoom,
+      zoom,
+      point: validPoint,
+    });
+
+    expect(result).toEqual(reaction.lines[0]);
+  });
+
+  it('should return undefined if no point within the search distance is found', () => {
+    const result = findClosestReactionPoint({
+      reaction,
+      searchDistance,
+      maxZoom,
+      zoom,
+      point: invalidPoint,
+    });
+
+    expect(result).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c29fc89a830fdd5e8a9472ca51c99cb1e58b44bc
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts
@@ -0,0 +1,42 @@
+/* eslint-disable no-magic-numbers */
+import { Point as PointType } from '@/types/map';
+import { Reaction } from '@/types/models';
+import { distance } from 'ol/coordinate';
+import { LineString, Point } from 'ol/geom';
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+type ReactionLine = Reaction['lines'][0];
+
+type FindClosestReactionArgs = {
+  reaction: Reaction;
+  searchDistance: string;
+  maxZoom: number;
+  zoom: number;
+  point: PointType;
+};
+
+export const findClosestReactionPoint = ({
+  reaction,
+  searchDistance,
+  maxZoom,
+  zoom,
+  point,
+}: FindClosestReactionArgs): ReactionLine | undefined => {
+  const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance);
+
+  const clickedPoint = new Point([point.x, point.y]);
+
+  const closestLine = reaction.lines.find(line => {
+    const lineString = new LineString([
+      [line.start.x, line.start.y],
+      [line.end.x, line.end.y],
+    ]);
+
+    const closestPointOnLine = lineString.getClosestPoint(clickedPoint.getCoordinates());
+    const distanceToLine = distance(closestPointOnLine, clickedPoint.getCoordinates());
+
+    return distanceToLine <= maxDistance;
+  });
+
+  return closestLine;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35711a82103c21f5a6b5d8ef23906de3846a9a54
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts
@@ -0,0 +1,25 @@
+/* eslint-disable no-magic-numbers */
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+describe('getMaxClickDistance', () => {
+  it.each([
+    [10, 5, '10', 320],
+    [10, 5, '20', 640],
+    [10, 2, '10', 2560],
+    [10, 3, '18', 2304],
+  ])(
+    'should calculate the maximum click distance correctly',
+    (maxZoom, zoom, searchDistance, expected) => {
+      expect(getMaxClickDistance(maxZoom, zoom, searchDistance)).toBe(expected);
+    },
+  );
+
+  it.each([['invalid'], [''], [' ']])(
+    'should throw an error if the search distance "%s" is not a valid number',
+    invalidDistance => {
+      expect(() => getMaxClickDistance(10, 5, invalidDistance)).toThrow(
+        'Invalid search distance. Please provide a valid number.',
+      );
+    },
+  );
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc02c450001a0c11ed798aafe18095ce3f350c1a
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-magic-numbers */
+
+import { ZOOM_FACTOR } from '@/constants/common';
+
+export const getMaxClickDistance = (
+  maxZoom: number,
+  zoom: number,
+  searchDistance: string,
+): number => {
+  const distance = parseFloat(searchDistance);
+
+  if (typeof distance !== 'number' || Number.isNaN(distance)) {
+    throw new Error('Invalid search distance. Please provide a valid number.');
+  }
+
+  const zoomDiff = maxZoom - zoom;
+  const maxDistance = distance * ZOOM_FACTOR ** zoomDiff;
+
+  return maxDistance;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
index 5533b62d166c032bd0341524805b0dad29836c8c..996d2d1c32589c53684be6a25e362289b0ca9177 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
@@ -36,7 +36,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual([ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
+      expect(result).toEqual({
+        point,
+        searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS],
+      });
     });
   });
 
@@ -65,7 +68,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual([ELEMENT_SEARCH_RESULT_MOCK_REACTION]);
+      expect(result).toEqual({
+        point,
+        searchResults: [ELEMENT_SEARCH_RESULT_MOCK_REACTION],
+      });
     });
   });
 
@@ -96,7 +102,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual(undefined);
+      expect(result).toEqual({
+        point,
+        searchResults: undefined,
+      });
     });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
index df376d4a846df12e4fa6820e893ca9e3b7773b3e..0fedae9f89f1f2c788b52e133b64b58b1e5f7623 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
@@ -1,4 +1,5 @@
 import { MapSize } from '@/redux/map/map.types';
+import { Point } from '@/types/map';
 import { ElementSearchResult } from '@/types/models';
 import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates';
@@ -15,8 +16,16 @@ export const getSearchResults = async ({
   coordinate,
   mapSize,
   modelId,
-}: GetSearchResultsInput): Promise<ElementSearchResult[] | undefined> => {
+}: GetSearchResultsInput): Promise<{
+  searchResults: ElementSearchResult[] | undefined;
+  point: Point;
+}> => {
   const [lng, lat] = toLonLat(coordinate);
   const point = latLngToPoint([lat, lng], mapSize);
-  return getElementsByPoint({ point, currentModelId: modelId });
+  const searchResults = await getElementsByPoint({ point, currentModelId: modelId });
+
+  return {
+    searchResults,
+    point,
+  };
 };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
index 088aa80fec038461d5e0664c44e3eead9fa29709..e36e92a7b655a1c1b20a77f41effe2707c51fab3 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
@@ -1,61 +1,253 @@
-import {
-  FIRST_ARRAY_ELEMENT,
-  SECOND_ARRAY_ELEMENT,
-  SIZE_OF_EMPTY_ARRAY,
-  THIRD_ARRAY_ELEMENT,
-} from '@/constants/common';
+/* eslint-disable no-magic-numbers */
+import { SIZE_OF_EMPTY_ARRAY, ZERO } from '@/constants/common';
 import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
 import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock';
 import { apiPath } from '@/redux/apiPath';
-import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
+import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
 import { handleAliasResults } from './handleAliasResults';
 
+jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
+
+const mockedAxiosClient = mockNetworkNewAPIResponse();
 const mockedAxiosOldClient = mockNetworkResponse();
 
+const SEARCH_CONFIG_MOCK = {
+  point: {
+    x: 500,
+    y: 700,
+  },
+  maxZoom: 9,
+  zoom: 5,
+  isResultDrawerOpen: false,
+};
+
 describe('handleAliasResults - util', () => {
-  const { store } = getReduxStoreWithActionsListener();
-  const { dispatch } = store;
-
-  mockedAxiosOldClient
-    .onGet(
-      apiPath.getBioEntityContentsStringWithQuery({
-        searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
-        isPerfectMatch: true,
-      }),
-    )
-    .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
-
-  beforeAll(async () => {
-    handleAliasResults(
-      dispatch,
-      ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
-    )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+  beforeEach(() => {
+    jest.clearAllMocks();
+
+    const bioEntityWithIdReaction = bioEntityResponseFixture.content.find(c =>
+      Boolean(c.bioEntity.idReaction),
+    )?.bioEntity || { id: ZERO };
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getReactionsWithIds([Number(`${bioEntityWithIdReaction.id}`)]))
+      .reply(HttpStatusCode.Ok, []);
   });
+  describe('when matching bioEntity not found', () => {
+    it('should clear bio entities and do not close drawer if result drawer is not open', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance: '10',
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
 
-  it('should run selectTab as first action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('drawer/selectTab');
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'reactions/getByIds/pending',
+          'reactions/getByIds/fulfilled',
+          'project/getMultiBioEntity/fulfilled',
+          'bioEntityContents/clearBioEntitiesData',
+        ]);
+      });
+    });
+    it('should clear bio entities and close drawer if result drawer is already open', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance: '10',
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'reactions/getByIds/pending',
+          'reactions/getByIds/fulfilled',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/closeDrawer',
+          'bioEntityContents/clearBioEntitiesData',
+        ]);
+      });
     });
   });
+  describe('when matching bioEntity found', () => {
+    it('should select tab and open bio entity drawer', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, {
+          ...bioEntityResponseFixture,
+          content: [
+            {
+              ...bioEntityResponseFixture.content[0],
+              bioEntity: {
+                ...bioEntityResponseFixture.content[0].bioEntity,
+                x: 500,
+                y: 700,
+                width: 50,
+                height: 50,
+              },
+            },
+          ],
+        });
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
+        SEARCH_CONFIG_MOCK,
+      )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
 
-  it('should run openBioEntityDrawerById as second action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('drawer/openBioEntityDrawerById');
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'reactions/getByIds/pending',
+          'reactions/getByIds/fulfilled',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/selectTab',
+          'drawer/openBioEntityDrawerById',
+        ]);
+      });
     });
   });
 
-  it('should run getMultiBioEntity as third action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[THIRD_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending');
+  describe('when searchDistance is not provided', () => {
+    it('should select tab and open drawer without clearing bio entities', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'reactions/getByIds/pending',
+          'reactions/getByIds/fulfilled',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/selectTab',
+          'drawer/openBioEntityDrawerById',
+        ]);
+      });
+    });
+
+    describe('fitBounds after search', () => {
+      it('should fit bounds after search when hasFitBounds is true', async () => {
+        mockedAxiosClient
+          .onGet(
+            apiPath.getBioEntityContentsStringWithQuery({
+              searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+              isPerfectMatch: true,
+            }),
+          )
+          .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+        const { store } = getReduxStoreWithActionsListener();
+        const { dispatch } = store;
+
+        await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+          ...SEARCH_CONFIG_MOCK,
+          hasFitBounds: true,
+        })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+        await waitFor(() => {
+          expect(searchFitBounds).toHaveBeenCalled();
+        });
+      });
+
+      it('should not fit bounds after search when hasFitBounds is false', async () => {
+        mockedAxiosClient
+          .onGet(
+            apiPath.getBioEntityContentsStringWithQuery({
+              searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+              isPerfectMatch: true,
+            }),
+          )
+          .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+        const { store } = getReduxStoreWithActionsListener();
+        const { dispatch } = store;
+
+        await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+          ...SEARCH_CONFIG_MOCK,
+          hasFitBounds: false,
+        })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+        await waitFor(() => {
+          expect(searchFitBounds).not.toHaveBeenCalled();
+        });
+      });
     });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
index bab0bd6227775d92db2c5edfc8f15c1d9377aa6b..cd8fc849d4b02a287a06c6909dc2af9db1e68c44 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
@@ -1,33 +1,57 @@
 import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
-import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
+import { closeDrawer, openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { AppDispatch } from '@/redux/store';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { ElementSearchResult } from '@/types/models';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+import { Point } from '@/types/map';
+import { findClosestBioEntityPoint } from './findClosestBioEntityPoint';
 
+type SearchConfig = {
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
+  hasFitBounds?: boolean;
+  isResultDrawerOpen?: boolean;
+};
 /* prettier-ignore */
 export const handleAliasResults =
-  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, { hasFitBounds, maxZoom, point, searchDistance, zoom, isResultDrawerOpen }: SearchConfig) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
-
-      dispatch(selectTab(`${id}`));
-      dispatch(openBioEntityDrawerById(id));
-      dispatch(
+      const bioEntityContents = await dispatch(
         getMultiBioEntity({
           searchQueries: [id.toString()],
           isPerfectMatch: true
         }),
-      )
-        .unwrap().then((bioEntityContents) => {
+      ).unwrap();
 
-          PluginsEventBus.dispatchEvent('onSearch', {
-            type: 'bioEntity',
-            searchValues: [closestSearchResult],
-            results: [bioEntityContents],
-          });
+      if (searchDistance) {
 
-          if (hasFitBounds) {
-            searchFitBounds(fitBoundsZoom);
+        const matchingBioEntityFound = findClosestBioEntityPoint(bioEntityContents, searchDistance, maxZoom, zoom, point);
+
+        if (!matchingBioEntityFound) {
+          if (isResultDrawerOpen) {
+            dispatch(closeDrawer());
           }
-        });
+
+          dispatch(clearBioEntitiesData());
+          return;
+        }
+      }
+
+      dispatch(selectTab(`${id}`));
+      dispatch(openBioEntityDrawerById(id));
+
+
+      PluginsEventBus.dispatchEvent('onSearch', {
+        type: 'bioEntity',
+        searchValues: [closestSearchResult],
+        results: [bioEntityContents],
+      });
+
+      if (hasFitBounds) {
+        searchFitBounds();
+      }
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts
index a2f23be1f53f13cfe3e5aea6ee8bd0674db12f26..b50e4b9e98070f52cf2406dcbd2e5dd9edc7ba21 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts
@@ -26,9 +26,9 @@ describe('handleFeaturesClick - util', () => {
       expect(dispatchEventSpy).toHaveBeenCalledWith('onPinIconClick', { id: featureId });
     });
 
-    it('should return shouldBlockCoordSearch=false', () => {
+    it('should return shouldBlockCoordSearch=true', () => {
       expect(handleFeaturesClick(features, dispatch)).toStrictEqual({
-        shouldBlockCoordSearch: false,
+        shouldBlockCoordSearch: true,
       });
     });
   });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts
index 3c1e5923d2290df828598a4b46d8c175979f020c..ff191109e84629da4c797cc8875789f8d37fef15 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts
@@ -1,3 +1,4 @@
+import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { FEATURE_TYPE, PIN_ICON_ANY, SURFACE_ANY } from '@/constants/features';
 import { openBioEntityDrawerById } from '@/redux/drawer/drawer.slice';
 import { clearSearchData } from '@/redux/search/search.slice';
@@ -13,9 +14,9 @@ export const handleFeaturesClick = (
   features: FeatureLike[],
   dispatch: AppDispatch,
 ): HandleFeaturesClickResult => {
-  let shouldBlockCoordSearch = false;
   const pinFeatures = features.filter(feature => PIN_ICON_ANY.includes(feature.get('type')));
   const surfaceFeatures = features.filter(feature => SURFACE_ANY.includes(feature.get('type')));
+  const shouldBlockCoordSearch = pinFeatures.length > SIZE_OF_EMPTY_ARRAY;
 
   pinFeatures.forEach(pin => {
     const pinId = pin.get('id') as string | number;
@@ -24,7 +25,6 @@ export const handleFeaturesClick = (
     if (pin.get('type') === FEATURE_TYPE.PIN_ICON_BIOENTITY) {
       dispatch(clearSearchData());
       dispatch(openBioEntityDrawerById(pinId));
-      shouldBlockCoordSearch = true;
     }
   });
 
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
index 46b2da8741898a76786f27da3b5128e314c45f89..1d754d392306c2af1ddddfe26d96867d4f0ceb1a 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
@@ -11,90 +11,259 @@ import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
 import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { HttpStatusCode } from 'axios';
+import * as findClosestReactionPoint from './findClosestReactionPoint';
 import { handleReactionResults } from './handleReactionResults';
 
 const mockedAxiosOldClient = mockNetworkResponse();
 const mockedAxiosNewClient = mockNetworkNewAPIResponse();
 
+jest.mock('./findClosestReactionPoint', () => ({
+  __esModule: true,
+  ...jest.requireActual('./findClosestReactionPoint'),
+}));
+
+const findClosestReactionPointSpy = jest.spyOn(
+  findClosestReactionPoint,
+  'findClosestReactionPoint',
+);
+
+const SEARCH_CONFIG_MOCK = {
+  point: {
+    x: 200,
+    y: 3012,
+  },
+  maxZoom: 9,
+  zoom: 3,
+  isResultDrawerOpen: false,
+};
+
 describe('handleReactionResults - util', () => {
-  const { store } = getReduxStoreWithActionsListener({
-    ...INITIAL_STORE_STATE_MOCK,
-  });
-  const { dispatch } = store;
-
-  mockedAxiosNewClient
-    .onGet(
-      apiPath.getBioEntityContentsStringWithQuery({
-        searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
-        isPerfectMatch: true,
-      }),
-    )
-    .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
-
-  mockedAxiosOldClient
-    .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
-    .reply(HttpStatusCode.Ok, [
-      {
-        ...reactionsFixture[0],
-        reactants: [],
-        products: [],
-        modifiers: [
-          {
-            ...reactionsFixture[0].modifiers[0],
-            aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
-          },
-        ],
-      },
-    ]);
-
-  beforeEach(async () => {
-    await handleReactionResults(
-      dispatch,
-      ELEMENT_SEARCH_RESULT_MOCK_REACTION,
-    )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
-  });
+  const searchDistance = '10';
 
-  it('should run getReactionsByIds as first action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[0].type).toEqual('reactions/getByIds/pending');
-    expect(actions[1].type).toEqual('reactions/getByIds/fulfilled');
+  beforeEach(() => {
+    jest.clearAllMocks();
   });
 
-  it('should run openReactionDrawerById to empty array as second action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[2].type).toEqual('drawer/openReactionDrawerById');
-    expect(actions[2].payload).toEqual(reactionsFixture[FIRST_ARRAY_ELEMENT].id);
-  });
+  describe('actions', () => {
+    const { store } = getReduxStoreWithActionsListener({
+      ...INITIAL_STORE_STATE_MOCK,
+    });
+    const { dispatch } = store;
 
-  it('should run setBioEntityContent to empty array as third action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[3].type).toEqual('project/getMultiBioEntity/pending');
-  });
+    mockedAxiosNewClient
+      .onGet(
+        apiPath.getBioEntityContentsStringWithQuery({
+          searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+          isPerfectMatch: true,
+        }),
+      )
+      .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+      .reply(HttpStatusCode.Ok, [
+        {
+          ...reactionsFixture[0],
+          reactants: [],
+          products: [],
+          modifiers: [
+            {
+              ...reactionsFixture[0].modifiers[0],
+              aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
+            },
+          ],
+        },
+      ]);
 
-  it('should run getBioEntityContents as fourth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[4].type).toEqual('project/getBioEntityContents/pending');
+    beforeEach(async () => {
+      await handleReactionResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_REACTION,
+      )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+    });
+
+    it('should run getReactionsByIds as first action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[0].type).toEqual('reactions/getByIds/pending');
+      expect(actions[1].type).toEqual('reactions/getByIds/fulfilled');
+    });
+
+    it('should run openReactionDrawerById to empty array as third action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[2].type).toEqual('drawer/openReactionDrawerById');
+      expect(actions[2].payload).toEqual(reactionsFixture[FIRST_ARRAY_ELEMENT].id);
+    });
+
+    it('should run select tab as fourth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[3].type).toEqual('drawer/selectTab');
+    });
+
+    it('should run getMultiBioEntity to empty array as fifth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[4].type).toEqual('project/getMultiBioEntity/pending');
+    });
+
+    it('should run getBioEntityContents as sixth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[5].type).toEqual('project/getBioEntityContents/pending');
+    });
+
+    it('should run getBioEntityContents fullfilled as seventh action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[6].type).toEqual('project/getBioEntityContents/fulfilled');
+    });
+
+    it('should run addNumbersToEntityNumberData as eighth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[7].type).toEqual('entityNumber/addNumbersToEntityNumberData');
+    });
   });
+  describe('when search config provided but search distance is not provided', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+
+    it('should not find closest reaction', async () => {
+      await handleReactionResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_REACTION,
+        SEARCH_CONFIG_MOCK,
+      )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
 
-  it('should run getBioEntityContents fullfilled as fifth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[5].type).toEqual('project/getBioEntityContents/fulfilled');
+      expect(findClosestReactionPointSpy).not.toHaveBeenCalled();
+    });
   });
 
-  it('should run addNumbersToEntityNumberData as sixth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[6].type).toEqual('entityNumber/addNumbersToEntityNumberData');
+  describe('when search config provided and matching reaction not found', () => {
+    mockedAxiosNewClient
+      .onGet(
+        apiPath.getBioEntityContentsStringWithQuery({
+          searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+          isPerfectMatch: true,
+        }),
+      )
+      .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+      .reply(HttpStatusCode.Ok, [
+        {
+          ...reactionsFixture[0],
+          reactants: [],
+          products: [],
+          modifiers: [
+            {
+              ...reactionsFixture[0].modifiers[0],
+              aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
+            },
+          ],
+        },
+      ]);
+
+    const invalidPoint = {
+      x: 991,
+      y: 612,
+    };
+    it('should close drawer and reset data if result drawer open', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance,
+        point: invalidPoint,
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      const actions = store.getActions();
+      const acionTypes = actions.map(action => action.type);
+
+      expect(acionTypes).toStrictEqual([
+        'reactions/getByIds/pending',
+        'reactions/getByIds/fulfilled',
+        'drawer/closeDrawer',
+        'reactions/resetReactionsData',
+        'bioEntityContents/clearBioEntitiesData',
+      ]);
+    });
+
+    it('should only reset data if result drawer is closed', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+
+      const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance,
+        point: invalidPoint,
+        isResultDrawerOpen: false,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      expect(dispatchSpy).toHaveBeenCalledWith({
+        payload: undefined,
+        type: 'reactions/resetReactionsData',
+      });
+
+      expect(dispatchSpy).toHaveBeenCalledWith({
+        payload: undefined,
+        type: 'bioEntityContents/clearBioEntitiesData',
+      });
+    });
   });
+  describe('when search config provided and matching reaction found', () => {
+    const reaction = {
+      ...reactionsFixture[0],
+      products: [],
+      reactants: [],
+      modifiers: [],
+      lines: [{ start: { x: 0, y: 0 }, end: { x: 3, y: 4 }, type: 'START' }],
+    };
+
+    const point = { x: 1, y: 1 };
+    const maxZoom = 10;
+    const zoom = 5;
+
+    it('should open reaction drawer and fetch bio entities', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+      mockedAxiosNewClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, []);
+
+      mockedAxiosOldClient
+        .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+        .reply(HttpStatusCode.Ok, [reaction]);
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        searchDistance,
+        maxZoom,
+        zoom,
+        point,
+        isResultDrawerOpen: false,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      const actions = store.getActions();
+      const acionTypes = actions.map(action => action.type);
 
-  it('should run getByIds as seventh action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[7].type).toEqual('reactions/getByIds/pending');
+      expect(acionTypes).toStrictEqual([
+        'reactions/getByIds/pending',
+        'reactions/getByIds/fulfilled',
+        'drawer/openReactionDrawerById',
+        'drawer/selectTab',
+        'project/getMultiBioEntity/pending',
+        'entityNumber/addNumbersToEntityNumberData',
+        'project/getMultiBioEntity/fulfilled',
+      ]);
+    });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
index bfbcaaa362c7350ed2d53fdb861f39fdd0b52824..4a12d522b3cc290d881fd5623d8bdd302c7d6c76 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
@@ -1,16 +1,28 @@
 import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
-import { openReactionDrawerById } from '@/redux/drawer/drawer.slice';
+import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
 import { AppDispatch } from '@/redux/store';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { Point } from '@/types/map';
 import { ElementSearchResult } from '@/types/models';
+import { findClosestReactionPoint } from './findClosestReactionPoint';
 import { getBioEntitiesIdsFromReaction } from './getBioEntitiesIdsFromReaction';
+import { handleReactionSearchClickFailure } from './handleReactionSearchClickFailure';
+
+type SearchConfig = {
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
+  hasFitBounds?: boolean;
+  isResultDrawerOpen: boolean;
+};
 
 /* prettier-ignore */
 export const handleReactionResults =
-  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, searchConfig?: SearchConfig) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
       const data = await dispatch(getReactionsByIds([id]));
       const payload = data?.payload;
@@ -21,7 +33,22 @@ export const handleReactionResults =
       const reaction = payload.data[FIRST_ARRAY_ELEMENT];
       const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction);
 
+      if (searchConfig && searchConfig.searchDistance) {
+        const { maxZoom, point, searchDistance, zoom, isResultDrawerOpen } = searchConfig;
+        const matchingReactionFound = findClosestReactionPoint({
+          reaction, searchDistance, maxZoom, zoom, point
+        });
+
+        if (!matchingReactionFound) {
+          handleReactionSearchClickFailure(dispatch, isResultDrawerOpen);
+
+          return;
+        }
+      }
+
       dispatch(openReactionDrawerById(reaction.id));
+
+      dispatch(selectTab(''));
       await dispatch(
         getMultiBioEntity({
           searchQueries: bioEntitiesIds,
@@ -34,8 +61,9 @@ export const handleReactionResults =
           results: [bioEntityContents],
         });
 
-        if (hasFitBounds) {
-          searchFitBounds(fitBoundsZoom);
+        if (searchConfig && searchConfig.hasFitBounds) {
+          searchFitBounds();
         }
       });
+
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7368bb185bb1bf9020c5000c3f3fa193c04110c9
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts
@@ -0,0 +1,15 @@
+import { AppDispatch } from '@/redux/store';
+import { closeDrawer } from '@/redux/drawer/drawer.slice';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+
+export const handleReactionSearchClickFailure = (
+  dispatch: AppDispatch,
+  isResultDrawerOpen: boolean,
+): void => {
+  if (isResultDrawerOpen) {
+    dispatch(closeDrawer());
+  }
+  dispatch(resetReactionsData());
+  dispatch(clearBioEntitiesData());
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
index 8492539613ec74eec4792b914632d354d64733cb..d07fe1bed16b9e748b1f1a2309386b7bbbaec5d2 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
@@ -19,6 +19,13 @@ jest.mock('./handleReactionResults', () => ({
 const handleAliasResultsSpy = jest.spyOn(handleAliasResults, 'handleAliasResults');
 const handleReactionResultsSpy = jest.spyOn(handleReactionResults, 'handleReactionResults');
 
+const POINT_MOCK = {
+  x: 1323,
+  y: 2000,
+};
+const ZOOM_MOCK = 3;
+const MAX_ZOOM_MOCK = 9;
+
 describe('handleSearchResultAction - util', () => {
   const dispatch = jest.fn();
 
@@ -30,7 +37,14 @@ describe('handleSearchResultAction - util', () => {
     const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_ALIAS];
 
     it('should fire handleAliasResults', async () => {
-      await handleSearchResultAction({ searchResults, dispatch });
+      await handleSearchResultAction({
+        searchResults,
+        dispatch,
+        maxZoom: MAX_ZOOM_MOCK,
+        isResultDrawerOpen: false,
+        point: POINT_MOCK,
+        zoom: ZOOM_MOCK,
+      });
       expect(handleAliasResultsSpy).toBeCalled();
     });
   });
@@ -39,7 +53,14 @@ describe('handleSearchResultAction - util', () => {
     const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_REACTION];
 
     it('should fire handleReactionResults', async () => {
-      await handleSearchResultAction({ searchResults, dispatch });
+      await handleSearchResultAction({
+        searchResults,
+        dispatch,
+        maxZoom: MAX_ZOOM_MOCK,
+        isResultDrawerOpen: false,
+        point: POINT_MOCK,
+        zoom: ZOOM_MOCK,
+      });
       expect(handleReactionResultsSpy).toBeCalled();
     });
   });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
index 39dea1009c6cbacb44eb5819f49911b801aa7a04..f85813b1dad50806ee66f25b56751da0ec9e8134 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
@@ -2,21 +2,30 @@ import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
 import { AppDispatch } from '@/redux/store';
 import { ElementSearchResult } from '@/types/models';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { Point } from '@/types/map';
 import { handleAliasResults } from './handleAliasResults';
 import { handleReactionResults } from './handleReactionResults';
 
 interface HandleSearchResultActionInput {
   searchResults: ElementSearchResult[];
   dispatch: AppDispatch;
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
   hasFitBounds?: boolean;
-  fitBoundsZoom?: number;
+  isResultDrawerOpen: boolean;
 }
 
 export const handleSearchResultAction = async ({
   searchResults,
   dispatch,
+  point,
+  searchDistance,
+  maxZoom,
+  zoom,
   hasFitBounds,
-  fitBoundsZoom,
+  isResultDrawerOpen,
 }: HandleSearchResultActionInput): Promise<void> => {
   const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT];
   const { type } = closestSearchResult;
@@ -25,7 +34,14 @@ export const handleSearchResultAction = async ({
     REACTION: handleReactionResults,
   }[type];
 
-  await action(dispatch, closestSearchResult, hasFitBounds, fitBoundsZoom)(closestSearchResult);
+  await action(dispatch, closestSearchResult, {
+    point,
+    searchDistance,
+    maxZoom,
+    zoom,
+    hasFitBounds,
+    isResultDrawerOpen,
+  })(closestSearchResult);
 
   if (type === 'ALIAS') {
     PluginsEventBus.dispatchEvent('onBioEntityClick', closestSearchResult);
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
index 4270a32407558717ed920b0a1740c2286a75f7a2..ae02e8c16624fc9ef7f18b96f8f1e1a6646ad724 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
@@ -37,6 +37,11 @@ const getEvent = (coordinate: MapBrowserEvent<UIEvent>['coordinate']): MapBrowse
     coordinate,
   }) as unknown as MapBrowserEvent<UIEvent>;
 
+const MAX_ZOOM_MOCK_MOCK = 9;
+const ZOOM_MOCK = 3;
+const SEARCH_DISTANCE_MOCK = '10';
+const IS_RESULT_DRAWER_OPEN_MOCK = true;
+
 describe('onMapSingleClick - util', () => {
   beforeEach(() => {
     jest.clearAllMocks();
@@ -54,7 +59,15 @@ describe('onMapSingleClick - util', () => {
       minZoom: 2,
       maxZoom: 9,
     };
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [90, 90];
     const event = getEvent(coordinate);
 
@@ -79,7 +92,15 @@ describe('onMapSingleClick - util', () => {
       minZoom: 2,
       maxZoom: 9,
     };
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [90, 90];
     const point = { x: 180.0008084837557, y: 179.99919151624428 };
     const event = getEvent(coordinate);
@@ -111,7 +132,15 @@ describe('onMapSingleClick - util', () => {
       maxZoom: 9,
     };
 
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [180, 180];
     const point = { x: 360.0032339350228, y: 359.9967660649771 };
     const event = getEvent(coordinate);
@@ -161,7 +190,15 @@ describe('onMapSingleClick - util', () => {
     } as unknown as Map;
 
     it('does NOT fire search result action handler', async () => {
-      const handler = onMapSingleClick(mapSize, modelId, dispatch);
+      const handler = onMapSingleClick(
+        mapSize,
+        modelId,
+        dispatch,
+        SEARCH_DISTANCE_MOCK,
+        MAX_ZOOM_MOCK_MOCK,
+        ZOOM_MOCK,
+        IS_RESULT_DRAWER_OPEN_MOCK,
+      );
       await handler(event, mapInstanceMock);
       await waitFor(() => expect(handleSearchResultActionSpy).not.toBeCalled());
     });
@@ -192,7 +229,15 @@ describe('onMapSingleClick - util', () => {
       } as unknown as Map;
 
       it('does fire search result action handler', async () => {
-        const handler = onMapSingleClick(mapSize, modelId, dispatch);
+        const handler = onMapSingleClick(
+          mapSize,
+          modelId,
+          dispatch,
+          SEARCH_DISTANCE_MOCK,
+          MAX_ZOOM_MOCK_MOCK,
+          ZOOM_MOCK,
+          IS_RESULT_DRAWER_OPEN_MOCK,
+        );
         await handler(event, mapInstanceMock);
         await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled());
       });
@@ -225,7 +270,15 @@ describe('onMapSingleClick - util', () => {
       } as unknown as Map;
 
       it('does fire search result action - handle reaction', async () => {
-        const handler = onMapSingleClick(mapSize, modelId, dispatch);
+        const handler = onMapSingleClick(
+          mapSize,
+          modelId,
+          dispatch,
+          SEARCH_DISTANCE_MOCK,
+          MAX_ZOOM_MOCK_MOCK,
+          ZOOM_MOCK,
+          IS_RESULT_DRAWER_OPEN_MOCK,
+        );
         await handler(event, mapInstanceMock);
         await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled());
       });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
index 3d1e425c2e5a8d894df37ecc3f04eceee865ead5..31dabd9af62d249b1344f4f06e6fdfbc4b7b3e23 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
@@ -10,7 +10,7 @@ import { handleSearchResultAction } from './handleSearchResultAction';
 
 /* prettier-ignore */
 export const onMapSingleClick =
-  (mapSize: MapSize, modelId: number, dispatch: AppDispatch) =>
+  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, searchDistance: string | undefined, maxZoom: number, zoom: number, isResultDrawerOpen: boolean) =>
     async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
       const featuresAtPixel: FeatureLike[] = [];
       mapInstance.forEachFeatureAtPixel(pixel, (feature) => featuresAtPixel.push(feature));
@@ -24,10 +24,10 @@ export const onMapSingleClick =
       // so we need to reset all the data before updating
       dispatch(handleDataReset);
 
-      const searchResults = await getSearchResults({ coordinate, mapSize, modelId });
+      const {searchResults, point} = await getSearchResults({ coordinate, mapSize, modelId });
       if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) {
         return;
       }
 
-      handleSearchResultAction({ searchResults, dispatch });
+      handleSearchResultAction({ searchResults, dispatch, point, searchDistance, maxZoom, zoom, isResultDrawerOpen });
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
index bc9228f024b01e1369e832ae32a93dcbf16c1b3d..295b91d1aad607f60565dbb88c33f6bc043659a0 100644
--- a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
@@ -16,8 +16,6 @@ const getEvent = (targetValues: ObjectEvent['target']['values_']): ObjectEvent =
 
 /* eslint-disable no-magic-numbers */
 describe('onMapPositionChange - util', () => {
-  const MAP_ID = 52;
-  const LAST_ZOOM = 4;
   const cases: [MapSize, ObjectEvent['target']['values_'], Point][] = [
     [
       {
@@ -65,7 +63,7 @@ describe('onMapPositionChange - util', () => {
       const dispatch = result.current;
       const event = getEvent(targetValues);
 
-      onMapPositionChange(mapSize, dispatch, MAP_ID, LAST_ZOOM)(event);
+      onMapPositionChange(mapSize, dispatch)(event);
 
       const { position } = mapDataSelector(store.getState());
       expect(position.last).toMatchObject(lastPosition);
diff --git a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
index d49f6f3edfd3d4a108619cf890fbc45a90a68d56..7102fec7fdecfd1f771295030dd82e30c42bc767 100644
--- a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
+++ b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
@@ -1,33 +1,19 @@
 import { setMapPosition } from '@/redux/map/map.slice';
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { toLonLat } from 'ol/proj';
 import { ObjectEvent } from 'openlayers';
 
 /* prettier-ignore */
 export const onMapPositionChange =
-  (mapSize: MapSize, dispatch: AppDispatch, modelId: number, mapLastZoomValue: number | undefined) =>
+  (mapSize: MapSize, dispatch: AppDispatch) =>
     (e: ObjectEvent): void => {
       // eslint-disable-next-line no-underscore-dangle
       const { center, zoom } = e.target.values_;
       const [lng, lat] = toLonLat(center);
       const { x, y } = latLngToPoint([lat, lng], mapSize, { rounded: true });
 
-      if (mapLastZoomValue !== zoom) {
-        PluginsEventBus.dispatchEvent('onZoomChanged', {
-          modelId,
-          zoom,
-        });
-      }
-
-      PluginsEventBus.dispatchEvent('onCenterChanged', {
-        modelId,
-        x,
-        y
-      });
-
       dispatch(
         setMapPosition({
           x,
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
index 68b1c28b94dcdaef277951c2f726be845a6c61df..340b61de1553e9f6523c5b71c744e0de8838e2eb 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
@@ -1,8 +1,8 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import mapSlice from '@/redux/map/map.slice';
-import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer';
 import { renderHook } from '@testing-library/react';
 import { View } from 'ol';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import * as singleClickListener from './mapSingleClick/onMapSingleClick';
 import * as positionListener from './onMapPositionChange';
 import { useOlMapListeners } from './useOlMapListeners';
@@ -25,7 +25,14 @@ jest.mock('use-debounce', () => {
 });
 
 describe('useOlMapListeners - util', () => {
-  const { Wrapper } = getReduxWrapperUsingSliceReducer('map', mapSlice);
+  const { Wrapper } = getReduxWrapperWithStore({
+    map: {
+      data: { ...initialMapDataFixture },
+      loading: 'succeeded',
+      error: { message: '', name: '' },
+      openedMaps: openedMapsThreeSubmapsFixture,
+    },
+  });
 
   beforeEach(() => {
     jest.clearAllMocks();
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index 7ca778b5965bafa6ac38cae39e887afbb5cc6c21..86e88297c2a6d487f1666027eb5ababe23e9e568 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -1,6 +1,10 @@
-import { OPTIONS } from '@/constants/map';
+import { DEFAULT_ZOOM, OPTIONS } from '@/constants/map';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { mapDataLastZoomValue, mapDataSizeSelector } from '@/redux/map/map.selectors';
+import {
+  mapDataLastZoomValue,
+  mapDataMaxZoomValue,
+  mapDataSizeSelector,
+} from '@/redux/map/map.selectors';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { MapInstance } from '@/types/map';
 import { View } from 'ol';
@@ -10,6 +14,8 @@ import { Pixel } from 'ol/pixel';
 import { useEffect, useRef } from 'react';
 import { useSelector } from 'react-redux';
 import { useDebouncedCallback } from 'use-debounce';
+import { searchDistanceValSelector } from '@/redux/configuration/configuration.selectors';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
 import { onMapRightClick } from './mapRightClick/onMapRightClick';
 import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
 import { onMapPositionChange } from './onMapPositionChange';
@@ -23,7 +29,10 @@ interface UseOlMapListenersInput {
 export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput): void => {
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
-  const mapLastZoomValue = useSelector(mapDataLastZoomValue);
+  const searchDistance = useSelector(searchDistanceValSelector);
+  const maxZoom = useSelector(mapDataMaxZoomValue);
+  const lastZoom = useSelector(mapDataLastZoomValue);
+  const isResultDrawerOpen = useSelector(resultDrawerOpen);
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
   const dispatch = useAppDispatch();
@@ -37,13 +46,21 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
   );
 
   const handleChangeCenter = useDebouncedCallback(
-    onMapPositionChange(mapSize, dispatch, modelId, mapLastZoomValue),
+    onMapPositionChange(mapSize, dispatch),
     OPTIONS.queryPersistTime,
     { leading: false },
   );
 
   const handleMapSingleClick = useDebouncedCallback(
-    onMapSingleClick(mapSize, modelId, dispatch),
+    onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      searchDistance,
+      maxZoom,
+      lastZoom || DEFAULT_ZOOM,
+      isResultDrawerOpen,
+    ),
     OPTIONS.clickPersistTime,
     { leading: false },
   );
diff --git a/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
index 143111c73513f1839bc13ef8782cc5905ebb291b..732577d8c824b0c935fd8de9ae29d88a6727c1ea 100644
--- a/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
+++ b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
@@ -13,7 +13,11 @@ export const PluginContent = (): JSX.Element => {
           ${getPluginContentStyle(selectedPlugin)}
         `}
       </style>
-      <div id={PLUGINS_CONTENT_ELEMENT_ID} data-testid="drawer-plugins-content" />
+      <div
+        id={PLUGINS_CONTENT_ELEMENT_ID}
+        data-testid="drawer-plugins-content"
+        className="h-full"
+      />
     </>
   );
 };
diff --git a/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx b/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
index c06d9774d177c066f0061b32c875f0d9d5abdbb9..f844fc1d356a78702481ae3e7e512fb5be54aac1 100644
--- a/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
@@ -12,7 +12,7 @@ export const PluginsDrawer = (): JSX.Element => {
   return (
     <div
       className={twMerge(
-        'absolute bottom-0 right-0 top-[104px] z-20 h-calc-drawer w-[432px] translate-x-full transform border border-divide bg-white-pearl text-font-500 transition-all duration-500',
+        'absolute bottom-0 right-0 top-[104px] z-20 flex h-calc-drawer w-[432px] translate-x-full transform flex-col border border-divide bg-white-pearl text-font-500 transition-all duration-500',
         isOpen && 'translate-x-0',
       )}
       role={PLUGINS_DRAWER_ROLE}
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
index fa013b073f5244e1197cf346cccb29aa26ffa770..7729070d1d97ece8514b28bf4d6b3b5dcac8af70 100644
--- a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
@@ -15,6 +15,7 @@ import { render, screen, waitFor } from '@testing-library/react';
 import axios, { HttpStatusCode } from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { PluginHeaderInfo } from './PluginHeaderInfo.component';
 import { RELOAD_PLUGIN_DRAWER_BUTTON_ROLE } from './PluginHeaderInfo.constants';
 
@@ -57,6 +58,7 @@ const STATE = {
     },
     list: PLUGINS_INITIAL_STATE_LIST_MOCK,
   },
+  legend: LEGEND_INITIAL_STATE_MOCK,
 };
 
 describe('PluginHeaderInfo - component', () => {
@@ -92,6 +94,10 @@ describe('PluginHeaderInfo - component', () => {
 
     expect(actions).toEqual([
       { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
+      {
+        payload: '5e3fcb59588cc311ef9839feea6382eb',
+        type: 'legend/removePluginLegend',
+      },
     ]);
 
     await waitFor(() => {
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginElement/LoadPluginElement.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginElement/LoadPluginElement.component.test.tsx
index 9f6d9e24759913f389b5e83d78f77f2c1e241327..f6a4184cfa9355827834a8acea57b96d58ff187f 100644
--- a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginElement/LoadPluginElement.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginElement/LoadPluginElement.component.test.tsx
@@ -15,6 +15,7 @@ import { render, screen, waitFor } from '@testing-library/react';
 import axios, { HttpStatusCode } from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { LoadPluginElement } from './LoadPluginElement.component';
 
 const mockedAxiosClient = new MockAdapter(axios);
@@ -55,6 +56,7 @@ const STATE = {
     },
     list: PLUGINS_INITIAL_STATE_LIST_MOCK,
   },
+  legend: LEGEND_INITIAL_STATE_MOCK,
 };
 
 describe('LoadPluginButton - component', () => {
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
index 01f6e7fc0af2f4da942ba98bedbed1a05a9b3241..43345cb8ff8be69f023fba6b089f55ed3c5701b0 100644
--- a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
@@ -8,6 +8,7 @@ import {
 } from '@/utils/testing/getReduxStoreActionsListener';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { PluginOpenButton } from './PluginOpenButton.component';
 
 const renderComponent = (
@@ -60,6 +61,7 @@ describe('PluginOpenButton - component', () => {
             data: PLUGINS_MOCK,
           },
         },
+        legend: LEGEND_INITIAL_STATE_MOCK,
       });
     });
 
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
index 864f1d963690ad2cd888ecd4e0b760d272464fc4..a4fab9d6f6b01779b814d3026d92df1a2dafaa0d 100644
--- a/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
@@ -11,6 +11,7 @@ import {
 } from '@/utils/testing/getReduxStoreActionsListener';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { CLOSE_PLUGINS_DRAWER_BUTTON_ROLE } from '../PluginsDrawer.constants';
 import { PluginsHeader } from './PluginsHeader.component';
 
@@ -41,6 +42,7 @@ describe('PluginsHeader - component', () => {
             currentPluginHash: undefined,
           },
         },
+        legend: LEGEND_INITIAL_STATE_MOCK,
       });
     });
 
@@ -69,6 +71,7 @@ describe('PluginsHeader - component', () => {
           },
           list: PLUGINS_INITIAL_STATE_LIST_MOCK,
         },
+        legend: LEGEND_INITIAL_STATE_MOCK,
       });
     });
 
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
index 157d4220526bcd10a9078f44b97e45672f9d50fa..f9ff782a84cd638f1798d510b448fed1af6fdc85 100644
--- a/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
@@ -1,4 +1,9 @@
-import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, THIRD_ARRAY_ELEMENT } from '@/constants/common';
+import {
+  FIRST_ARRAY_ELEMENT,
+  SECOND_ARRAY_ELEMENT,
+  THIRD_ARRAY_ELEMENT,
+  ZERO,
+} from '@/constants/common';
 import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
 import {
   PLUGINS_INITIAL_STATE_LIST_MOCK,
@@ -13,6 +18,7 @@ import {
 } from '@/utils/testing/getReduxStoreActionsListener';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { PluginSingleTab } from './PluginSingleTab.component';
 
 const renderComponent = (
@@ -53,6 +59,7 @@ const STATE = {
     },
     list: PLUGINS_INITIAL_STATE_LIST_MOCK,
   },
+  legend: LEGEND_INITIAL_STATE_MOCK,
 };
 
 describe('PluginSingleTab - component', () => {
@@ -82,9 +89,10 @@ describe('PluginSingleTab - component', () => {
         hash: PLUGIN.hash,
       });
 
-      expect(actions).toEqual([
-        { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
-      ]);
+      expect(actions[ZERO]).toEqual({
+        payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' },
+        type: 'plugins/removePlugin',
+      });
     });
 
     it('should dispatch close action on close btn click and new current drawer on last active plugin', () => {
@@ -116,6 +124,7 @@ describe('PluginSingleTab - component', () => {
 
       expect(actions).toEqual([
         { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
+        { payload: '5e3fcb59588cc311ef9839feea6382eb', type: 'legend/removePluginLegend' },
         {
           payload: '5314b9f996e56e67f0dad65e7df8b73b',
           type: 'plugins/setCurrentDrawerPluginHash',
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
index 64bc467055f57bfa940ce5750522da38a439e144..ba00c5772b7bc5c410d3bd919896c7ce386360fe 100644
--- a/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
@@ -11,6 +11,7 @@ import {
 } from '@/utils/testing/getReduxStoreActionsListener';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { LEGEND_INITIAL_STATE_MOCK } from '@/redux/legend/legend.mock';
 import { PluginsTabs } from './PluginsTabs.component';
 
 const renderComponent = (
@@ -75,6 +76,7 @@ describe('PluginsTabs - component', () => {
           },
           list: PLUGINS_INITIAL_STATE_LIST_MOCK,
         },
+        legend: LEGEND_INITIAL_STATE_MOCK,
       });
     });
 
diff --git a/src/constants/common.ts b/src/constants/common.ts
index 08cf2e9011a22d56572e8ea7c49d35a85ecc708d..cc5db42a582c323623f57455b262fba83529b98f 100644
--- a/src/constants/common.ts
+++ b/src/constants/common.ts
@@ -18,3 +18,4 @@ export const ONE_DECIMAL = 0.1;
 export const ONE_HUNDRED = 100;
 
 export const EMPTY_ARRAY_STRING = '[]';
+export const ZOOM_FACTOR = 2.0; // Zoom factor indicating doubling the distance for each zoom level
diff --git a/src/constants/errors.ts b/src/constants/errors.ts
index 887f64f35d00d60ce428da9f5b4239c0d8c6cbaa..d21cfea4d64f473e8a10fba56593bb7d0e1dafff 100644
--- a/src/constants/errors.ts
+++ b/src/constants/errors.ts
@@ -1 +1,16 @@
 export const DEFAULT_ERROR: Error = { message: '', name: '' };
+
+export const OVERVIEW_IMAGE_ERRORS = {
+  IMAGE_ID_IS_INVALID: "Image id is invalid. There's no such image in overview images list",
+};
+
+export const ZOOM_ERRORS = {
+  ZOOM_VALUE_TOO_HIGH: (maxZoom: number): string =>
+    `Provided zoom value exeeds max zoom of ${maxZoom}`,
+  ZOOM_VALUE_TOO_LOW: (minZoom: number): string =>
+    `Provided zoom value exceeds min zoom of ${minZoom}`,
+};
+
+export const MOLART_ERRORS = {
+  UNEXPECTED_MOLART_ERROR: 'UNEXPECTED_MOLART_ERROR',
+};
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 70fa367ac265c29efdfa7069d8f93d6ff46b3171..844268225f45daa6a286de76a1c04085439b2969 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,9 +1,12 @@
 /* eslint-disable no-magic-numbers */
 
-export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || '';
-export const BASE_MAP_IMAGES_URL = process.env.BASE_MAP_IMAGES_URL || '';
-export const BASE_NEW_API_URL = process.env.NEXT_PUBLIC_BASE_NEW_API_URL || '';
-export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || '';
+import { getConfigValue, getProjectIdFromUrl } from './index.utils';
+
+export const BASE_API_URL = getConfigValue('BASE_API_URL');
+export const BASE_MAP_IMAGES_URL = getConfigValue('BASE_MAP_IMAGES_URL');
+export const BASE_NEW_API_URL = getConfigValue('BASE_NEW_API_URL');
+export const DEFAULT_PROJECT_ID = getConfigValue('DEFAULT_PROJECT_ID');
+export const PROJECT_ID = getProjectIdFromUrl() || DEFAULT_PROJECT_ID;
 export const ZOD_SEED = parseInt(process.env.ZOD_SEED || '123', 10);
 export const BIO_ENTITY = 'bioEntity';
 export const DRUGS_CHEMICALS = ['drugs', 'chemicals'];
diff --git a/src/constants/index.utils.test.ts b/src/constants/index.utils.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d9ddfd0f18c475dbb0d6389a9742e86a5daccf1
--- /dev/null
+++ b/src/constants/index.utils.test.ts
@@ -0,0 +1,33 @@
+import { getConfigValue, getProjectIdFromUrl } from './index.utils';
+
+describe('getConfigValue - util', () => {
+  it('should return value for existing key', () => {
+    expect(getConfigValue('BASE_API_URL')).toBe('https://lux1.atcomp.pl/minerva/api');
+  });
+
+  it('should return default value for non-existing key', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    expect(getConfigValue('nonExistingKey' as any)).toBe('');
+  });
+});
+
+describe('getProjectIdFromUrl - util', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should return project ID from URL if present', () => {
+    jest.spyOn(URLSearchParams.prototype, 'get').mockReturnValue('my_project_id');
+    expect(getProjectIdFromUrl()).toBe('my_project_id');
+  });
+
+  it('should return default project ID if not present in URL', () => {
+    jest.spyOn(URLSearchParams.prototype, 'get').mockReturnValue(null);
+    expect(getProjectIdFromUrl()).toBeNull();
+  });
+  it('should return undefined if window is undefined', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    jest.spyOn(global as any, 'window', 'get').mockImplementation(() => undefined);
+
+    expect(getProjectIdFromUrl()).toBeUndefined();
+  });
+});
diff --git a/src/constants/index.utils.ts b/src/constants/index.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ebd2553b9de6e1d68d648c829f93b0242f4530d6
--- /dev/null
+++ b/src/constants/index.utils.ts
@@ -0,0 +1,19 @@
+type ConfigKeys = keyof typeof window.config;
+
+export const getConfigValue = (key: ConfigKeys): string => {
+  const defaultValue = '';
+  if (typeof window !== 'undefined' && window.config && window.config[key]) {
+    return window.config[key];
+  }
+  return defaultValue;
+};
+
+export const getProjectIdFromUrl = (): string | null | undefined => {
+  if (typeof window !== 'undefined') {
+    const params = new URLSearchParams(window.location.search);
+
+    return params.get('id');
+  }
+
+  return undefined;
+};
diff --git a/src/models/mocks/configurationOptionMock.ts b/src/models/mocks/configurationOptionMock.ts
index c1e6a1e53d452fd0b9b66bf25538afdbe832623b..f7a293f7592e80217573eef6dab39dc5a1038e87 100644
--- a/src/models/mocks/configurationOptionMock.ts
+++ b/src/models/mocks/configurationOptionMock.ts
@@ -6,6 +6,7 @@ export const CONFIGURATION_OPTIONS_TYPES_MOCK: string[] = [
   'SIMPLE_COLOR_VAL',
   'NEUTRAL_COLOR_VAL',
   'OVERLAY_OPACITY',
+  'SEARCH_DISTANCE',
 ];
 
 export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [
@@ -54,4 +55,13 @@ export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [
     value: '0.8',
     group: 'Overlays',
   },
+  {
+    idObject: 19,
+    type: 'SEARCH_DISTANCE',
+    valueType: 'DOUBLE',
+    commonName: 'Max distance for clicking on element (px)',
+    isServerSide: false,
+    value: '10',
+    group: 'Point and click',
+  },
 ];
diff --git a/src/models/pointSchema.ts b/src/models/pointSchema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b097ef8235bf4e2a0a27698337828153a62c553
--- /dev/null
+++ b/src/models/pointSchema.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod';
+
+export const zPointSchema = z.number().nonnegative('z should be non negative').optional();
+
+export const pointSchema = z.object({
+  x: z.number().nonnegative('x should be non negative'),
+  y: z.number().nonnegative('y should be non negative'),
+  z: zPointSchema,
+});
diff --git a/src/models/projectSchema.ts b/src/models/projectSchema.ts
index 517d7b0c52d0684e7f3ef1c29f05955add86ab61..611b93a7ef6b0fa9cb76d5ef4d8cdfb92f8e9432 100644
--- a/src/models/projectSchema.ts
+++ b/src/models/projectSchema.ts
@@ -23,5 +23,5 @@ export const projectSchema = z.object({
   creationDate: z.string(),
   mapCanvasType: z.string(),
   overviewImageViews: z.array(overviewImageView),
-  topOverviewImage: overviewImageView,
+  topOverviewImage: overviewImageView.nullable(),
 });
diff --git a/src/redux/configuration/configuration.constants.ts b/src/redux/configuration/configuration.constants.ts
index 9a2762d91efeac7a80edb56d071e2fee999a172d..a03edc92e3b60554196a00115bc246453a7eef83 100644
--- a/src/redux/configuration/configuration.constants.ts
+++ b/src/redux/configuration/configuration.constants.ts
@@ -3,6 +3,7 @@ export const MAX_COLOR_VAL_NAME_ID = 'MAX_COLOR_VAL';
 export const SIMPLE_COLOR_VAL_NAME_ID = 'SIMPLE_COLOR_VAL';
 export const NEUTRAL_COLOR_VAL_NAME_ID = 'NEUTRAL_COLOR_VAL';
 export const OVERLAY_OPACITY_NAME_ID = 'OVERLAY_OPACITY';
+export const SEARCH_DISTANCE_NAME_ID = 'SEARCH_DISTANCE';
 
 export const LEGEND_FILE_NAMES_IDS = [
   'LEGEND_FILE_1',
diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts
index 25cbfbfa134c8e574480d310a96781e7bd0cc776..00683ccb082b98e28df71b0fc95a3e4f88ef5518 100644
--- a/src/redux/configuration/configuration.selectors.ts
+++ b/src/redux/configuration/configuration.selectors.ts
@@ -16,6 +16,7 @@ import {
   SBML_HANDLER_NAME_ID,
   SIMPLE_COLOR_VAL_NAME_ID,
   SVG_IMAGE_HANDLER_NAME_ID,
+  SEARCH_DISTANCE_NAME_ID,
 } from './configuration.constants';
 import { ConfigurationHandlersIds, ConfigurationImageHandlersIds } from './configuration.types';
 
@@ -50,6 +51,11 @@ export const simpleColorValSelector = createSelector(
   state => configurationAdapterSelectors.selectById(state, SIMPLE_COLOR_VAL_NAME_ID)?.value,
 );
 
+export const searchDistanceValSelector = createSelector(
+  configurationOptionsSelector,
+  state => configurationAdapterSelectors.selectById(state, SEARCH_DISTANCE_NAME_ID)?.value,
+);
+
 export const defaultLegendImagesSelector = createSelector(configurationOptionsSelector, state =>
   LEGEND_FILE_NAMES_IDS.map(
     legendNameId => configurationAdapterSelectors.selectById(state, legendNameId)?.value,
diff --git a/src/redux/drawer/drawer.constants.ts b/src/redux/drawer/drawer.constants.ts
index bef3fd3d8a0b585baf19e421da3536e218e1403a..84246ac3f1aab141811c7659991410663c3afd9a 100644
--- a/src/redux/drawer/drawer.constants.ts
+++ b/src/redux/drawer/drawer.constants.ts
@@ -20,6 +20,8 @@ export const DRAWER_INITIAL_STATE: DrawerState = {
   },
 };
 
+export const RESULT_DRAWERS = ['search', 'reaction', 'bio-entity'];
+
 export const DRUGS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX = 'Failed to fetch drugs for bio entity';
 export const CHEMICALS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX =
   'Failed to fetch chemicals for bio entity';
diff --git a/src/redux/drawer/drawer.selectors.ts b/src/redux/drawer/drawer.selectors.ts
index 30d9831c715223c6e286b67c630e1b823e299541..af943c462e893c1b5a4a0a9a6b8718747f563e87 100644
--- a/src/redux/drawer/drawer.selectors.ts
+++ b/src/redux/drawer/drawer.selectors.ts
@@ -2,6 +2,7 @@ import { DEFAULT_FETCH_DATA } from '@/constants/fetchData';
 import { rootSelector } from '@/redux/root/root.selectors';
 import { assertNever } from '@/utils/assertNever';
 import { createSelector } from '@reduxjs/toolkit';
+import { RESULT_DRAWERS } from './drawer.constants';
 
 export const drawerSelector = createSelector(rootSelector, state => state.drawer);
 
@@ -127,3 +128,7 @@ export const currentStepOverlayDrawerStateSelector = createSelector(
   overlayDrawerStateSelector,
   state => state.currentStep,
 );
+
+export const resultDrawerOpen = createSelector(drawerSelector, drawer => {
+  return drawer.isOpen && RESULT_DRAWERS.includes(drawer.drawerName);
+});
diff --git a/src/redux/legend/legend.constants.ts b/src/redux/legend/legend.constants.ts
index 5719c74e0eb641b05f3fdaefc19e1f7ae16bd072..ffb97578ee12d56156169c5c9d86c91993d20be4 100644
--- a/src/redux/legend/legend.constants.ts
+++ b/src/redux/legend/legend.constants.ts
@@ -1,7 +1,14 @@
 import { LegendState } from './legend.types';
 
+export const DEFAULT_LEGEND_ID = 'MAIN';
+
 export const LEGEND_INITIAL_STATE: LegendState = {
   isOpen: false,
   pluginLegend: {},
-  selectedPluginId: undefined,
+  activeLegendId: DEFAULT_LEGEND_ID,
+};
+
+export const DEFAULT_LEGEND_TAB = {
+  name: 'Main Legend',
+  id: DEFAULT_LEGEND_ID,
 };
diff --git a/src/redux/legend/legend.mock.ts b/src/redux/legend/legend.mock.ts
index 6874d3a607591109d29e32f33b7cbe7908c36a90..fe0791d6cd8055656a28f65519625342c64aa380 100644
--- a/src/redux/legend/legend.mock.ts
+++ b/src/redux/legend/legend.mock.ts
@@ -1,7 +1,8 @@
+import { DEFAULT_LEGEND_ID } from './legend.constants';
 import { LegendState } from './legend.types';
 
 export const LEGEND_INITIAL_STATE_MOCK: LegendState = {
   isOpen: false,
   pluginLegend: {},
-  selectedPluginId: undefined,
+  activeLegendId: DEFAULT_LEGEND_ID,
 };
diff --git a/src/redux/legend/legend.reducers.ts b/src/redux/legend/legend.reducers.ts
index be1f0572cb8a95f18862049d88cc6b2a91132ed0..8fe3cba28ac590be6bb348c1c6558d3f2a3846d1 100644
--- a/src/redux/legend/legend.reducers.ts
+++ b/src/redux/legend/legend.reducers.ts
@@ -1,5 +1,6 @@
 import { PayloadAction } from '@reduxjs/toolkit';
 import { LegendState, PluginId } from './legend.types';
+import { DEFAULT_LEGEND_ID } from './legend.constants';
 
 export const openLegendReducer = (state: LegendState): void => {
   state.isOpen = true;
@@ -9,13 +10,37 @@ export const closeLegendReducer = (state: LegendState): void => {
   state.isOpen = false;
 };
 
-export const selectLegendPluginIdReducer = (
+export const setActiveLegendIdReducer = (
   state: LegendState,
-  action: PayloadAction<PluginId>,
+  action: PayloadAction<string>,
 ): void => {
-  state.selectedPluginId = action.payload;
+  state.activeLegendId = action.payload;
 };
 
-export const selectDefaultLegendReducer = (state: LegendState): void => {
-  state.selectedPluginId = undefined;
+export const setDefaultLegendIdReducer = (state: LegendState): void => {
+  state.activeLegendId = DEFAULT_LEGEND_ID;
+};
+
+export const setPluginLegendReducer = (
+  state: LegendState,
+  {
+    payload,
+  }: PayloadAction<{
+    pluginId: PluginId;
+    legend: string[];
+  }>,
+): void => {
+  state.pluginLegend = {
+    ...state.pluginLegend,
+    [payload.pluginId]: payload.legend,
+  };
+};
+
+export const removePluginLegendReducer = (
+  state: LegendState,
+  { payload }: PayloadAction<string>,
+): void => {
+  if (state.pluginLegend[payload]) {
+    delete state.pluginLegend[payload];
+  }
 };
diff --git a/src/redux/legend/legend.selectors.ts b/src/redux/legend/legend.selectors.ts
index fdf927f1de82b4af6c4b40cd5ea0432d3ec30ec9..91d7277b94a5984bedc13f485594f706509b5c53 100644
--- a/src/redux/legend/legend.selectors.ts
+++ b/src/redux/legend/legend.selectors.ts
@@ -1,20 +1,51 @@
 import { createSelector } from '@reduxjs/toolkit';
+import { BASE_MAP_IMAGES_URL } from '@/constants';
 import { defaultLegendImagesSelector } from '../configuration/configuration.selectors';
 import { rootSelector } from '../root/root.selectors';
+import { activePluginsDataSelector } from '../plugins/plugins.selectors';
+import { DEFAULT_LEGEND_ID, DEFAULT_LEGEND_TAB } from './legend.constants';
 
 export const legendSelector = createSelector(rootSelector, state => state.legend);
 
 export const isLegendOpenSelector = createSelector(legendSelector, state => state.isOpen);
 
-// TODO: add filter for active plugins
+export const pluginLegendsSelector = createSelector(legendSelector, state => state.pluginLegend);
+
 export const currentLegendImagesSelector = createSelector(
   legendSelector,
   defaultLegendImagesSelector,
-  ({ selectedPluginId, pluginLegend }, defaultImages) => {
-    if (selectedPluginId) {
-      return pluginLegend?.[selectedPluginId] || [];
+  ({ activeLegendId, pluginLegend }, defaultImages) => {
+    if (activeLegendId === DEFAULT_LEGEND_ID)
+      return defaultImages.map(image => `${BASE_MAP_IMAGES_URL}/minerva/${image}`);
+
+    if (activeLegendId) {
+      return pluginLegend?.[activeLegendId] || [];
     }
 
-    return defaultImages;
+    return [];
+  },
+);
+
+export const allLegendsNamesAndIdsSelector = createSelector(
+  activePluginsDataSelector,
+  pluginLegendsSelector,
+  (activePlugins, pluginLegends) => {
+    const allPluginLegendsNamesAndIds = Object.keys(pluginLegends).map(hash => {
+      const plugin = Object.values(activePlugins).find(activePlugin => activePlugin.hash === hash);
+
+      return {
+        name: plugin?.name || '',
+        id: plugin?.hash || '',
+      };
+    });
+
+    return [DEFAULT_LEGEND_TAB, ...allPluginLegendsNamesAndIds];
   },
 );
+
+export const activeLegendIdSelector = createSelector(legendSelector, state => state.activeLegendId);
+
+export const isActiveLegendSelector = createSelector(
+  [activeLegendIdSelector, (_, legendId: string): string => legendId],
+  (activeLegendId, legendId) => activeLegendId === legendId,
+);
diff --git a/src/redux/legend/legend.slice.ts b/src/redux/legend/legend.slice.ts
index fbb62f007c61d9a67ebda75f3cd563b68045147f..2dccdb5ab98c876dc9affe9a76d03c041df0d5f6 100644
--- a/src/redux/legend/legend.slice.ts
+++ b/src/redux/legend/legend.slice.ts
@@ -1,6 +1,13 @@
 import { createSlice } from '@reduxjs/toolkit';
 import { LEGEND_INITIAL_STATE } from './legend.constants';
-import { closeLegendReducer, openLegendReducer } from './legend.reducers';
+import {
+  closeLegendReducer,
+  openLegendReducer,
+  removePluginLegendReducer,
+  setActiveLegendIdReducer,
+  setDefaultLegendIdReducer,
+  setPluginLegendReducer,
+} from './legend.reducers';
 
 const legendSlice = createSlice({
   name: 'legend',
@@ -8,9 +15,20 @@ const legendSlice = createSlice({
   reducers: {
     openLegend: openLegendReducer,
     closeLegend: closeLegendReducer,
+    setPluginLegend: setPluginLegendReducer,
+    setActiveLegendId: setActiveLegendIdReducer,
+    setDefaultLegendId: setDefaultLegendIdReducer,
+    removePluginLegend: removePluginLegendReducer,
   },
 });
 
-export const { openLegend, closeLegend } = legendSlice.actions;
+export const {
+  openLegend,
+  closeLegend,
+  setPluginLegend,
+  setActiveLegendId,
+  setDefaultLegendId,
+  removePluginLegend,
+} = legendSlice.actions;
 
 export default legendSlice.reducer;
diff --git a/src/redux/legend/legend.types.ts b/src/redux/legend/legend.types.ts
index 80314aecede0f4c20d452eccdb9f8b4dbdad56a6..ad797feb899fa8dd661e4f85f4390823bc2625d6 100644
--- a/src/redux/legend/legend.types.ts
+++ b/src/redux/legend/legend.types.ts
@@ -1,8 +1,8 @@
-export type PluginId = number;
+export type PluginId = string;
 export type ImageUrl = string;
 
 export type LegendState = {
   isOpen: boolean;
   pluginLegend: Record<PluginId, ImageUrl[]>;
-  selectedPluginId: PluginId | undefined;
+  activeLegendId: string;
 };
diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts
index cc9d7651dcd56a7a7cc546d52a08f99a7b1940ed..c65bfe9746acc4cd2197cab5200fc891066ba834 100644
--- a/src/redux/map/map.reducers.ts
+++ b/src/redux/map/map.reducers.ts
@@ -16,6 +16,7 @@ import {
   SetActiveMapAction,
   SetBackgroundAction,
   SetLastPositionZoomAction,
+  SetLastPositionZoomWithDeltaAction,
   SetMapDataAction,
   SetMapPositionDataAction,
 } from './map.types';
@@ -32,19 +33,38 @@ export const setMapDataReducer = (state: MapState, action: SetMapDataAction): vo
 export const setMapPositionReducer = (state: MapState, action: SetMapPositionDataAction): void => {
   const position = action.payload || {};
   const statePosition = state.data.position;
+  const lastPosition = statePosition.last;
+  const lastZoom = lastPosition.z;
+  const finalPosition = getPointMerged(position || {}, lastPosition);
+  const { modelId } = state.data;
+
+  if (lastPosition?.x !== finalPosition.x || lastPosition?.y !== finalPosition.y) {
+    PluginsEventBus.dispatchEvent('onCenterChanged', {
+      modelId,
+      x: finalPosition.x,
+      y: finalPosition.y,
+    });
+  }
+
+  if (position?.z && lastZoom && lastZoom !== position?.z) {
+    PluginsEventBus.dispatchEvent('onZoomChanged', {
+      modelId,
+      zoom: position?.z,
+    });
+  }
 
   state.data = {
     ...state.data,
     position: {
-      initial: getPointMerged(position || {}, statePosition.initial),
-      last: getPointMerged(position || {}, statePosition.last),
+      initial: finalPosition,
+      last: finalPosition,
     },
   };
 };
 
 export const varyPositionZoomReducer = (
   state: MapState,
-  action: SetLastPositionZoomAction,
+  action: SetLastPositionZoomWithDeltaAction,
 ): void => {
   const { minZoom, maxZoom } = state.data.size;
   const { delta } = action.payload;
@@ -63,6 +83,23 @@ export const varyPositionZoomReducer = (
   state.data.position.initial.z = newZLimited;
 };
 
+export const setLastPositionZoomReducer = (
+  state: MapState,
+  action: SetLastPositionZoomAction,
+): void => {
+  const { zoom } = action.payload;
+
+  if (state.data.position.last.z !== zoom) {
+    PluginsEventBus.dispatchEvent('onZoomChanged', {
+      modelId: state.data.modelId,
+      zoom,
+    });
+  }
+
+  state.data.position.last.z = zoom;
+  state.data.position.initial.z = zoom;
+};
+
 const updateLastPositionOfCurrentlyActiveMap = (state: MapState): void => {
   const currentMapId = state.data.modelId;
   const currentOpenedMap = state.openedMaps.find(openedMap => openedMap.modelId === currentMapId);
@@ -135,6 +172,10 @@ export const closeMapAndSetMainMapActiveReducer = (
 };
 
 export const setMapBackgroundReducer = (state: MapState, action: SetBackgroundAction): void => {
+  if (action.payload !== state.data.backgroundId) {
+    PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', action.payload);
+  }
+
   state.data.backgroundId = action.payload;
 };
 
diff --git a/src/redux/map/map.selectors.ts b/src/redux/map/map.selectors.ts
index ac398f90f79c9e7f7bb63ffeb626b4d7eaf56c48..257f99fd81e90a0c23d606bbd5369379760a2ab6 100644
--- a/src/redux/map/map.selectors.ts
+++ b/src/redux/map/map.selectors.ts
@@ -32,3 +32,5 @@ export const mapDataLastZoomValue = createSelector(
   mapDataLastPositionSelector,
   position => position.z,
 );
+
+export const mapDataMaxZoomValue = createSelector(mapDataSizeSelector, model => model.maxZoom);
diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts
index 97554c329195c998ea2f873beacf29eb3cd5044d..46f68b78379b43eea92e3897455d4ddd82f5668b 100644
--- a/src/redux/map/map.slice.ts
+++ b/src/redux/map/map.slice.ts
@@ -10,6 +10,7 @@ import {
   openMapAndOrSetActiveIfSelectedReducer,
   openMapAndSetActiveReducer,
   setActiveMapReducer,
+  setLastPositionZoomReducer,
   setMapBackgroundReducer,
   setMapDataReducer,
   setMapPositionReducer,
@@ -29,6 +30,7 @@ const mapSlice = createSlice({
     varyPositionZoom: varyPositionZoomReducer,
     setMapBackground: setMapBackgroundReducer,
     openMapAndOrSetActiveIfSelected: openMapAndOrSetActiveIfSelectedReducer,
+    setLastPositionZoom: setLastPositionZoomReducer,
   },
   extraReducers: builder => {
     initMapPositionReducers(builder);
@@ -48,6 +50,7 @@ export const {
   setMapBackground,
   varyPositionZoom,
   openMapAndOrSetActiveIfSelected,
+  setLastPositionZoom,
 } = mapSlice.actions;
 
 export default mapSlice.reducer;
diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts
index b11c5cfefe794fb850de5629934e181c4f94877d..727df76f71b29da80f9690edcc3e9e86c9888d12 100644
--- a/src/redux/map/map.types.ts
+++ b/src/redux/map/map.types.ts
@@ -88,10 +88,17 @@ export type GetUpdatedMapDataResult = Pick<
 
 export type SetMapPositionDataAction = PayloadAction<Point>;
 
-export type SetLastPositionZoomActionPayload = {
+export type SetLastPositionZoomWithDeltaActionPayload = {
   delta: number;
 };
 
+export type SetLastPositionZoomWithDeltaAction =
+  PayloadAction<SetLastPositionZoomWithDeltaActionPayload>;
+
+export type SetLastPositionZoomActionPayload = {
+  zoom: number;
+};
+
 export type SetLastPositionZoomAction = PayloadAction<SetLastPositionZoomActionPayload>;
 
 export type InitMapDataActionPayload = {
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
index 30222f300c8a31f6d6104e23634c9d9b0d6d33ac..956b6751b59b785cf082e89b5630f65d8acaf08f 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
@@ -100,7 +100,6 @@ export const getInitOverlays = createAsyncThunk<
     const emptyBackgroundId = emptyBackgroundIdSelector(state);
     if (emptyBackgroundId) {
       dispatch(setMapBackground(emptyBackgroundId));
-      PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', emptyBackgroundId);
     }
 
     overlaysId.forEach(id => {
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
index 4b1fa460e4d60e8604d9bf36a351597d9db3929f..67fd7b7d20e59979c665bccb5e5977fbd58f2ade 100644
--- a/src/redux/overlays/overlays.thunks.ts
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -14,6 +14,7 @@ import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { showToast } from '@/utils/showToast';
 import { getErrorMessage } from '@/utils/getErrorMessage';
 import { ThunkConfig } from '@/types/store';
+import { BASE_API_URL } from '@/constants';
 import { apiPath } from '../apiPath';
 import {
   CHUNK_SIZE,
@@ -124,7 +125,7 @@ const uploadContent = async ({ createdFile, overlayContent }: UploadContentArgs)
     const chunk = data.slice(uploadedLength, uploadedLength + CHUNK_SIZE);
 
     const responeJSON = await fetch(
-      `${process.env.NEXT_PUBLIC_BASE_API_URL}/${apiPath.uploadOverlayFileContent(createdFile.id)}`,
+      `${BASE_API_URL}/${apiPath.uploadOverlayFileContent(createdFile.id)}`,
       {
         method: 'POST',
         credentials: 'include',
diff --git a/src/redux/plugins/plugins.thunks.ts b/src/redux/plugins/plugins.thunks.ts
index 1d69a6cbb318e9a02a78ce882e9c638199ebd324..76dc4491488086e5a89990ecd8d4fda2593cedff 100644
--- a/src/redux/plugins/plugins.thunks.ts
+++ b/src/redux/plugins/plugins.thunks.ts
@@ -85,11 +85,14 @@ export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps, ThunkC
         if (isDataValid) {
           const { urls } = res.data;
           const scriptRes = await axios(urls[0]);
-          const pluginScript = scriptRes.data;
+          let pluginScript = scriptRes.data;
           setHashedPlugin({ pluginUrl: urls[0], pluginScript });
 
+          pluginScript += `//# sourceURL=${urls[0]}`;
+
           /* eslint-disable no-new-func */
           const loadPlugin = new Function(pluginScript);
+
           loadPlugin();
         }
       }
diff --git a/src/redux/project/project.mock.ts b/src/redux/project/project.mock.ts
index dada67e42131deef75d6f209e0f660a609931340..0b8da5ef09076244c11c3621e9d82abd8428cc54 100644
--- a/src/redux/project/project.mock.ts
+++ b/src/redux/project/project.mock.ts
@@ -6,9 +6,10 @@ export const PROJECT_STATE_INITIAL_MOCK: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: DEFAULT_ERROR,
+  projectId: '',
 };
 
-export const PROJECT_OVERVIEW_IMAGE_MOCK: OverviewImageView = {
+export const PROJECT_OVERVIEW_IMAGE_MOCK: NonNullable<OverviewImageView> = {
   idObject: 440,
   filename: '9d4911bdeeea752f076e57a91d9b1f45/biolayout_main_root.png',
   width: 5776,
diff --git a/src/redux/project/project.reducers.test.ts b/src/redux/project/project.reducers.test.ts
index f3c007766b44ea26789d010ffccb631808e843c0..0521d9c262092e38956e8d239c5a8b82fb337610 100644
--- a/src/redux/project/project.reducers.test.ts
+++ b/src/redux/project/project.reducers.test.ts
@@ -17,6 +17,7 @@ const INITIAL_STATE: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: { name: '', message: '' },
+  projectId: '',
 };
 
 describe('project reducer', () => {
diff --git a/src/redux/project/project.reducers.ts b/src/redux/project/project.reducers.ts
index 435d55e8abbb5a895a21a0a4bba01e793333e36c..e022b8f8d3968a7a5ac8b662a455606aae124f4e 100644
--- a/src/redux/project/project.reducers.ts
+++ b/src/redux/project/project.reducers.ts
@@ -1,4 +1,4 @@
-import { getProjectById } from '@/redux/project/project.thunks';
+import { getProjectById, setProjectId } from '@/redux/project/project.thunks';
 import { ProjectState } from '@/redux/project/project.types';
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
 
@@ -15,3 +15,9 @@ export const getProjectByIdReducer = (builder: ActionReducerMapBuilder<ProjectSt
     // TODO to discuss manage state of failure
   });
 };
+
+export const setProjectIdReducer = (builder: ActionReducerMapBuilder<ProjectState>): void => {
+  builder.addCase(setProjectId.fulfilled, (state, action) => {
+    state.projectId = action.payload;
+  });
+};
diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts
index 6ae2769e7c06400bd86c6df411b73f800bba3d36..aa0c62aee628115e23e91adb0099a84490989218 100644
--- a/src/redux/project/project.selectors.ts
+++ b/src/redux/project/project.selectors.ts
@@ -10,7 +10,7 @@ export const projectDataSelector = createSelector(projectSelector, project => pr
 
 export const projectDefaultOverviewImageIdSelector = createSelector(
   projectDataSelector,
-  projectData => projectData?.topOverviewImage.idObject || OVERVIEW_IMAGE_ID_DEFAULT,
+  projectData => projectData?.topOverviewImage?.idObject || OVERVIEW_IMAGE_ID_DEFAULT,
 );
 
 export const currentOverviewImageSelector = createSelector(
@@ -63,3 +63,5 @@ export const organismNameSelector = createSelector(
 );
 
 export const versionSelector = createSelector(projectDataSelector, state => state?.version);
+
+export const projectMainIdSelector = createSelector(projectSelector, project => project.projectId);
diff --git a/src/redux/project/project.slice.ts b/src/redux/project/project.slice.ts
index a6e0d9d0e9990dd201476682f6854a3893c1c0c8..6fb716ec3fef9412d092b03d011d65d0d6c1c66b 100644
--- a/src/redux/project/project.slice.ts
+++ b/src/redux/project/project.slice.ts
@@ -1,11 +1,12 @@
 import { ProjectState } from '@/redux/project/project.types';
 import { createSlice } from '@reduxjs/toolkit';
-import { getProjectByIdReducer } from './project.reducers';
+import { getProjectByIdReducer, setProjectIdReducer } from './project.reducers';
 
 const initialState: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: { name: '', message: '' },
+  projectId: '',
 };
 
 const projectSlice = createSlice({
@@ -14,6 +15,7 @@ const projectSlice = createSlice({
   reducers: {},
   extraReducers: builder => {
     getProjectByIdReducer(builder);
+    setProjectIdReducer(builder);
   },
 });
 
diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts
index 2ef8de8212f136df6f1a6810bea8e0ebf31806bc..23a73a0934aae9f5351d7810beb40666d93f9076 100644
--- a/src/redux/project/project.thunks.ts
+++ b/src/redux/project/project.thunks.ts
@@ -5,8 +5,10 @@ import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { getErrorMessage } from '@/utils/getErrorMessage';
 import { ThunkConfig } from '@/types/store';
+import { DEFAULT_PROJECT_ID } from '@/constants';
 import { apiPath } from '../apiPath';
 import { PROJECT_FETCHING_ERROR_PREFIX } from './project.constants';
+import { SetProjectIdParams } from './project.types';
 
 export const getProjectById = createAsyncThunk<Project | undefined, string, ThunkConfig>(
   'project/getProjectById',
@@ -23,3 +25,12 @@ export const getProjectById = createAsyncThunk<Project | undefined, string, Thun
     }
   },
 );
+
+export const setProjectId = createAsyncThunk<string, SetProjectIdParams, ThunkConfig>(
+  'project/setProjectId',
+  async ({ queryData }) => {
+    const projectId = queryData?.id || DEFAULT_PROJECT_ID;
+
+    return projectId;
+  },
+);
diff --git a/src/redux/project/project.types.ts b/src/redux/project/project.types.ts
index c92be2c478cdd4f90986974b8a8de59e6c5d1901..df7dad10c1d61b97c17347ccda4f8f44f8c28653 100644
--- a/src/redux/project/project.types.ts
+++ b/src/redux/project/project.types.ts
@@ -1,8 +1,12 @@
 import { Loading } from '@/types/loadingState';
 import { Project } from '@/types/models';
+import { QueryData } from '@/types/query';
 
 export type ProjectState = {
+  projectId: string;
   data: Project | undefined;
   loading: Loading;
   error: Error;
 };
+
+export type SetProjectIdParams = { queryData: QueryData };
diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts
index fc1c0f25d4eefa4a7d1fc0e170a7b82770aed158..23aa9368fc288cbf418e7c5b10ab3221d09df23b 100644
--- a/src/redux/root/init.thunks.ts
+++ b/src/redux/root/init.thunks.ts
@@ -20,7 +20,7 @@ import {
   getAllUserOverlaysByCreator,
 } from '../overlays/overlays.thunks';
 import { getAllPlugins, getInitPlugins } from '../plugins/plugins.thunks';
-import { getProjectById } from '../project/project.thunks';
+import { getProjectById, setProjectId } from '../project/project.thunks';
 import { setPerfectMatch } from '../search/search.slice';
 import { getSearchData } from '../search/search.thunks';
 import { getStatisticsById } from '../statistics/statistics.thunks';
@@ -35,6 +35,8 @@ export const fetchInitialAppData = createAsyncThunk<
   InitializeAppParams,
   { dispatch: AppDispatch }
 >('appInit/fetchInitialAppData', async ({ queryData }, { dispatch }): Promise<void> => {
+  dispatch(setProjectId({ queryData }));
+
   if (queryData.pluginsId) {
     await dispatch(
       getInitPlugins({
diff --git a/src/redux/root/query.selectors.ts b/src/redux/root/query.selectors.ts
index b862ed429fdb885367f3f87755c47204903b08b7..8c4f10386b433be4e6240d3385a5216772f47548 100644
--- a/src/redux/root/query.selectors.ts
+++ b/src/redux/root/query.selectors.ts
@@ -5,6 +5,7 @@ import { mapDataSelector } from '../map/map.selectors';
 import { perfectMatchSelector, searchValueSelector } from '../search/search.selectors';
 import { activeOverlaysIdSelector } from '../overlayBioEntity/overlayBioEntity.selector';
 import { activePluginsIdSelector } from '../plugins/plugins.selectors';
+import { projectMainIdSelector } from '../project/project.selectors';
 
 export const queryDataParamsSelector = createSelector(
   searchValueSelector,
@@ -12,12 +13,14 @@ export const queryDataParamsSelector = createSelector(
   mapDataSelector,
   activeOverlaysIdSelector,
   activePluginsIdSelector,
+  projectMainIdSelector,
   (
     searchValue,
     perfectMatch,
     { modelId, backgroundId, position },
     activeOverlaysId,
     activePluginsId,
+    id,
   ): QueryDataParams => {
     const joinedSearchValue = searchValue.join(';');
     const shouldIncludeSearchValue = searchValue.length > ZERO && joinedSearchValue;
@@ -26,6 +29,7 @@ export const queryDataParamsSelector = createSelector(
     const shouldIncludePluginsId = activePluginsId.length > ZERO;
 
     const queryDataParams: QueryDataParams = {
+      id,
       perfectMatch,
       modelId,
       backgroundId,
diff --git a/src/services/pluginsManager/errorMessages.ts b/src/services/pluginsManager/errorMessages.ts
index 753b06f04cadabf975857a4a773c8574f3b1befb..029b14e0e9f0f6256aca438d65b7e1914b219ff0 100644
--- a/src/services/pluginsManager/errorMessages.ts
+++ b/src/services/pluginsManager/errorMessages.ts
@@ -3,5 +3,15 @@ export const ERROR_INVALID_QUERY_TYPE = 'Invalid query type. The query should be
 export const ERROR_INVALID_COORDINATES = 'Invalid coordinates type or values';
 export const ERROR_INVALID_MODEL_ID_TYPE = 'Invalid model id type. The model id should be a number';
 export const ERROR_PROJECT_NOT_FOUND = 'Project does not exist';
+export const ERROR_PROJECT_ID_NOT_FOUND = 'Project id does not exist';
+export const ERROR_OVERLAY_NAME_NOT_PROVIDED = 'Overlay name is not provided';
+export const ERROR_FAILED_TO_READ_FILE = 'Failed to read file';
+export const ERROR_INVALID_TYPE_FILE_CONTENT = 'Invalid type of fileContent';
+export const ERROR_OVERLAY_ID_NOT_FOUND = 'Overlay with provided id does not exist';
+export const ERROR_OVERLAY_ID_NOT_ACTIVE = 'Overlay with provided id is not active';
+export const ERROR_OVERLAY_ID_ALREADY_ACTIVE = 'Overlay with provided id is already active';
 export const ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL =
   'Unable to retrieve the id of the active map: the modelId is not a number';
+export const ERROR_INVALID_EVENT_TYPE = (type: string): string => `Invalid event type: ${type}`;
+export const ERROR_PLUGIN_CRASH = (pluginName: string): string =>
+  `Plugin "${pluginName}" has crashed. Please contact the plugin developer for assistance`;
diff --git a/src/services/pluginsManager/legend/removeLegend.ts b/src/services/pluginsManager/legend/removeLegend.ts
new file mode 100644
index 0000000000000000000000000000000000000000..70fb75ed9bbe3b89bd1f1ea17125127e0ec4eb42
--- /dev/null
+++ b/src/services/pluginsManager/legend/removeLegend.ts
@@ -0,0 +1,14 @@
+import { isActiveLegendSelector } from '@/redux/legend/legend.selectors';
+import { removePluginLegend, setDefaultLegendId } from '@/redux/legend/legend.slice';
+import { store } from '@/redux/store';
+
+export const removeLegend = (pluginId: string): void => {
+  const isActivePluginLegend = isActiveLegendSelector(store.getState(), pluginId);
+  const { dispatch } = store;
+
+  if (isActivePluginLegend) {
+    dispatch(setDefaultLegendId());
+  }
+
+  dispatch(removePluginLegend(pluginId));
+};
diff --git a/src/services/pluginsManager/legend/setLegend.ts b/src/services/pluginsManager/legend/setLegend.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4d50c9f6587e1b8c10bdd186ff4c62d1f0ee3598
--- /dev/null
+++ b/src/services/pluginsManager/legend/setLegend.ts
@@ -0,0 +1,13 @@
+import { setPluginLegend } from '@/redux/legend/legend.slice';
+import { store } from '@/redux/store';
+
+export const setLegend = (pluginId: string, legend: string[]): void => {
+  const { dispatch } = store;
+
+  dispatch(
+    setPluginLegend({
+      pluginId,
+      legend,
+    }),
+  );
+};
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2973a0e93664ed8e5966ef5b81dfff4c45d91fe2
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts
@@ -0,0 +1,2 @@
+export const DEFAULT_TYPE = 'GENERIC';
+export const DEFAULT_FILE_NAME = 'unknown.txt';
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..729d53cb97d470de7a70414541689cd034f20165
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts
@@ -0,0 +1,124 @@
+import {
+  createdOverlayFileFixture,
+  createdOverlayFixture,
+  uploadedOverlayFileContentFixture,
+} from '@/models/fixtures/overlaysFixture';
+import { projectFixture } from '@/models/fixtures/projectFixture';
+import { apiPath } from '@/redux/apiPath';
+import { RootState, store } from '@/redux/store';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { addOverlay } from '@/redux/overlays/overlays.thunks';
+import {
+  ERROR_OVERLAY_NAME_NOT_PROVIDED,
+  ERROR_PROJECT_ID_NOT_FOUND,
+} from '@/services/pluginsManager/errorMessages';
+import { addDataOverlay } from './addDataOverlay';
+import { DEFAULT_FILE_NAME, DEFAULT_TYPE } from './addDataOverlay.constants';
+
+jest.mock('../../../../../redux/store');
+jest.mock('../../../../../redux/overlays/overlays.thunks');
+
+const MOCK_STATE = {
+  project: {
+    data: {
+      ...projectFixture,
+    },
+    loading: 'succeeded',
+    error: { message: '', name: '' },
+  },
+};
+
+const mockedAxiosClient = mockNetworkResponse();
+
+describe('addDataOverlay', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  const overlay = {
+    name: 'Mock Overlay',
+    description: 'Mock Description',
+    filename: 'mockFile.txt',
+    fileContent: 'Mock File Content',
+    type: 'mockType',
+  };
+  it('should add overlay with provided data', async () => {
+    mockedAxiosClient
+      .onPost(apiPath.createOverlayFile())
+      .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(createdOverlayFileFixture.id))
+      .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.createOverlay(projectFixture.projectId))
+      .reply(HttpStatusCode.Ok, createdOverlayFixture);
+
+    getStateSpy.mockImplementation(() => MOCK_STATE as RootState);
+
+    await addDataOverlay(overlay);
+
+    expect(addOverlay).toHaveBeenCalledWith({
+      content: overlay.fileContent,
+      description: overlay.description,
+      filename: overlay.filename,
+      name: overlay.name,
+      projectId: projectFixture.projectId,
+      type: overlay.type,
+    });
+  });
+
+  it('should throw error when project id is not found', async () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          project: {
+            ...MOCK_STATE.project,
+            data: {
+              ...MOCK_STATE.project.data,
+              projectId: '',
+            },
+          },
+        }) as RootState,
+    );
+
+    await expect(() => addDataOverlay(overlay)).rejects.toThrow(ERROR_PROJECT_ID_NOT_FOUND);
+  });
+
+  it('should throw error when overlay name is not provided', async () => {
+    getStateSpy.mockImplementation(() => MOCK_STATE as RootState);
+
+    const overlayWithoutName = {
+      ...overlay,
+      name: '',
+    };
+
+    await expect(addDataOverlay(overlayWithoutName)).rejects.toThrow(
+      ERROR_OVERLAY_NAME_NOT_PROVIDED,
+    );
+  });
+
+  it('should add overlay with default values when optional parameters are not provided', async () => {
+    getStateSpy.mockImplementation(() => MOCK_STATE as RootState);
+
+    const overlayWithoutDefaultValues = {
+      name: 'Mock Overlay',
+      fileContent: 'Mock File Content',
+    };
+
+    await addDataOverlay(overlayWithoutDefaultValues);
+
+    expect(addOverlay).toHaveBeenCalledWith({
+      content: overlay.fileContent,
+      description: '',
+      filename: DEFAULT_FILE_NAME,
+      name: overlay.name,
+      projectId: projectFixture.projectId,
+      type: DEFAULT_TYPE,
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d52b351825e267b798ec76fd8851bba800fd9823
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts
@@ -0,0 +1,45 @@
+import { addOverlay } from '@/redux/overlays/overlays.thunks';
+import { projectIdSelector } from '@/redux/project/project.selectors';
+import { store } from '@/redux/store';
+import {
+  ERROR_OVERLAY_NAME_NOT_PROVIDED,
+  ERROR_PROJECT_ID_NOT_FOUND,
+} from '@/services/pluginsManager/errorMessages';
+import { DEFAULT_FILE_NAME, DEFAULT_TYPE } from './addDataOverlay.constants';
+import { getOverlayContent } from './addDataOverlay.utils';
+
+type AddDataOverlayArgs = {
+  name: string;
+  description?: string;
+  filename?: string;
+  fileContent: string;
+  type?: string;
+};
+
+export const addDataOverlay = async ({
+  name,
+  description,
+  filename,
+  fileContent,
+  type,
+}: AddDataOverlayArgs): Promise<void> => {
+  const { dispatch, getState } = store;
+  const projectId = projectIdSelector(getState());
+
+  if (!projectId) throw new Error(ERROR_PROJECT_ID_NOT_FOUND);
+
+  if (!name) throw new Error(ERROR_OVERLAY_NAME_NOT_PROVIDED);
+
+  const content = await getOverlayContent(fileContent);
+
+  dispatch(
+    addOverlay({
+      content,
+      description: description || '',
+      filename: filename || DEFAULT_FILE_NAME,
+      name,
+      projectId,
+      type: type || DEFAULT_TYPE,
+    }),
+  );
+};
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5190b8c85148e37fdacc0a3f79cb7d1d7d0e800c
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts
@@ -0,0 +1,39 @@
+/* eslint-disable no-magic-numbers */
+import {
+  ERROR_FAILED_TO_READ_FILE,
+  ERROR_INVALID_TYPE_FILE_CONTENT,
+} from '@/services/pluginsManager/errorMessages';
+import { getOverlayContent } from './addDataOverlay.utils';
+
+describe('getOverlayContent', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should return string if fileContent is a string', async () => {
+    const content = 'This is a string content';
+    const result = await getOverlayContent(content);
+    expect(result).toBe(content);
+  });
+
+  it('should return file content if fileContent is a File object', async () => {
+    const fileContent = 'File content';
+    const file = new File([fileContent], 'test.txt', { type: 'text/plain' });
+    const result = await getOverlayContent(file);
+    expect(result).toBe(fileContent);
+  });
+
+  it('should throw error if fileContent is neither a string nor a File object', async () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    await expect(getOverlayContent(123 as any)).rejects.toThrow(ERROR_INVALID_TYPE_FILE_CONTENT);
+  });
+
+  it('should throw error if there is an error reading the file', async () => {
+    const file = new File(['File content'], 'test.txt', { type: 'text/plain' });
+    const error = new Error('Failed to read file');
+    jest.spyOn(FileReader.prototype, 'readAsText').mockImplementation(() => {
+      throw error;
+    });
+
+    await expect(getOverlayContent(file)).rejects.toThrow(ERROR_FAILED_TO_READ_FILE);
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5eb69360c0a601bb08ac129becfed234852b940
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts
@@ -0,0 +1,28 @@
+import {
+  ERROR_FAILED_TO_READ_FILE,
+  ERROR_INVALID_TYPE_FILE_CONTENT,
+} from '@/services/pluginsManager/errorMessages';
+
+const getFileContentFromFile = (file: File): Promise<string> => {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsText(file);
+    reader.onload = (e): void => {
+      if (e.target) {
+        resolve(e.target.result as string);
+      } else {
+        reject(new Error(ERROR_FAILED_TO_READ_FILE));
+      }
+    };
+  });
+};
+
+export const getOverlayContent = async (fileContent: string | File): Promise<string> => {
+  if (typeof fileContent === 'string') {
+    return fileContent;
+  }
+  if (fileContent instanceof File) {
+    return getFileContentFromFile(fileContent);
+  }
+  throw new Error(ERROR_INVALID_TYPE_FILE_CONTENT);
+};
diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..48e56a9fc6ab7d95da86ddb9e00872fc89866a1c
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts
@@ -0,0 +1 @@
+export { addDataOverlay } from './addDataOverlay';
diff --git a/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts b/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3464c36210ef6031ce4927380e12f34738bded9d
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts
@@ -0,0 +1,70 @@
+import {
+  OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+  PUBLIC_OVERLAYS_MOCK,
+  USER_OVERLAYS_MOCK,
+} from '@/redux/overlays/overlays.mock';
+import { RootState, store } from '@/redux/store';
+import { getDataOverlays } from './getDataOverlays';
+
+describe('getDataOverlays', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  it('should return combined overlays and user overlays if exist', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+            userOverlays: {
+              data: USER_OVERLAYS_MOCK,
+              error: { message: '', name: '' },
+              loading: 'succeeded',
+            },
+          },
+        }) as RootState,
+    );
+
+    expect(getDataOverlays()).toEqual([...PUBLIC_OVERLAYS_MOCK, ...USER_OVERLAYS_MOCK]);
+  });
+
+  it('should return overlays if user overlays are not present', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+            userOverlays: {
+              data: [],
+              error: { message: '', name: '' },
+              loading: 'succeeded',
+            },
+          },
+        }) as RootState,
+    );
+
+    expect(getDataOverlays()).toEqual(PUBLIC_OVERLAYS_MOCK);
+  });
+
+  it('should return empty array if no overlays or user overlays exist', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+            data: [],
+            userOverlays: {
+              data: [],
+              error: { message: '', name: '' },
+              loading: 'succeeded',
+            },
+          },
+        }) as RootState,
+    );
+
+    expect(getDataOverlays()).toEqual([]);
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/getDataOverlays.ts b/src/services/pluginsManager/map/overlays/getDataOverlays.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7eb9c20671242c644edcd7c10264076b3755467d
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/getDataOverlays.ts
@@ -0,0 +1,13 @@
+import {
+  overlaysDataSelector,
+  userOverlaysDataSelector,
+} from '@/redux/overlays/overlays.selectors';
+import { store } from '@/redux/store';
+import { MapOverlay } from '@/types/models';
+
+export const getDataOverlays = (): MapOverlay[] => {
+  const overlays = overlaysDataSelector(store.getState());
+  const userOverlays = userOverlaysDataSelector(store.getState()) || [];
+
+  return [...overlays, ...userOverlays];
+};
diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..036a668b22bf45829ccc22e4767a40ff0c184697
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts
@@ -0,0 +1,59 @@
+import { DEFAULT_ERROR } from '@/constants/errors';
+import { overlaysFixture } from '@/models/fixtures/overlaysFixture';
+import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { RootState, store } from '@/redux/store';
+import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock';
+import { getVisibleDataOverlays } from './getVisibleDataOverlays';
+
+const ACTIVE_OVERLAYS_IDS = overlaysFixture.map(overlay => overlay.idObject);
+
+describe('getVisibleDataOverlays', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  it('should return active overlays', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+
+          overlayBioEntity: {
+            ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
+            overlaysId: ACTIVE_OVERLAYS_IDS,
+          },
+        }) as RootState,
+    );
+
+    expect(getVisibleDataOverlays()).toEqual(overlaysFixture);
+  });
+
+  it('should return empty array if no active overlays', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: [],
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+
+          overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
+        }) as RootState,
+    );
+
+    expect(getVisibleDataOverlays()).toEqual([]);
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6224d3ffe50af7b5e79591e464a76571ed3b0422
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts
@@ -0,0 +1,9 @@
+import { activeOverlaysSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { store } from '@/redux/store';
+import { MapOverlay } from '@/types/models';
+
+export const getVisibleDataOverlays = (): MapOverlay[] => {
+  const activeOverlays = activeOverlaysSelector(store.getState());
+
+  return activeOverlays;
+};
diff --git a/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3f9a88e455f7bebdfba51701e205166396102be8
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts
@@ -0,0 +1,102 @@
+/* eslint-disable no-magic-numbers */
+import { RootState, store } from '@/redux/store';
+import { overlaysFixture } from '@/models/fixtures/overlaysFixture';
+import { DEFAULT_ERROR } from '@/constants/errors';
+import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock';
+import { hideDataOverlay } from './hideDataOverlay';
+import { PluginsEventBus } from '../../pluginsEventBus';
+import { ERROR_OVERLAY_ID_NOT_ACTIVE, ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages';
+
+const OVERLAY_ID = overlaysFixture[0].idObject;
+
+describe('hideDataOverlay', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+  const getStateSpy = jest.spyOn(store, 'getState');
+  it('should throw error if overlay is not active', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [123] },
+        }) as RootState,
+    );
+    expect(() => hideDataOverlay(OVERLAY_ID)).toThrow(ERROR_OVERLAY_ID_NOT_ACTIVE);
+  });
+
+  it('should throw error if matching overlay is not found', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] },
+        }) as RootState,
+    );
+
+    expect(() => hideDataOverlay(431)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND);
+  });
+
+  it('should hide overlay with provided id', () => {
+    const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] },
+        }) as RootState,
+    );
+
+    hideDataOverlay(OVERLAY_ID);
+
+    expect(dispatchSpy).toHaveBeenCalledWith({
+      payload: { overlayId: OVERLAY_ID },
+      type: 'overlayBioEntity/removeOverlayBioEntityForGivenOverlay',
+    });
+  });
+  it('should dispatch plugin event with hidden overlay', () => {
+    const pluginDispatchEvent = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] },
+        }) as RootState,
+    );
+
+    hideDataOverlay(OVERLAY_ID);
+
+    expect(pluginDispatchEvent).toHaveBeenCalledWith('onHideOverlay', overlaysFixture[0]);
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/hideDataOverlay.ts b/src/services/pluginsManager/map/overlays/hideDataOverlay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17ca62b3dd3fa595cdcb7502be2a2855f1119620
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/hideDataOverlay.ts
@@ -0,0 +1,26 @@
+import { isOverlayActiveSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { removeOverlayBioEntityForGivenOverlay } from '@/redux/overlayBioEntity/overlayBioEntity.slice';
+import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors';
+import { store } from '@/redux/store';
+import { PluginsEventBus } from '../../pluginsEventBus';
+import { ERROR_OVERLAY_ID_NOT_ACTIVE, ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages';
+
+export const hideDataOverlay = (overlayId: number): void => {
+  const { dispatch, getState } = store;
+  const state = getState();
+  const isOverlayActive = isOverlayActiveSelector(state, overlayId);
+  const overlay = overlaySelector(state, overlayId);
+  const userOverlay = userOverlaySelector(state, overlayId);
+
+  const matchingOverlay = overlay || userOverlay;
+
+  if (!matchingOverlay) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND);
+
+  if (!isOverlayActive) {
+    throw new Error(ERROR_OVERLAY_ID_NOT_ACTIVE);
+  }
+
+  dispatch(removeOverlayBioEntityForGivenOverlay({ overlayId }));
+
+  PluginsEventBus.dispatchEvent('onHideOverlay', matchingOverlay);
+};
diff --git a/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a178691f8ddaf9db1a9337a6d04abc8fe7baaf33
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts
@@ -0,0 +1,44 @@
+/* eslint-disable no-magic-numbers */
+import { RootState, store } from '@/redux/store';
+import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { overlaysFixture } from '@/models/fixtures/overlaysFixture';
+import { DEFAULT_ERROR } from '@/constants/errors';
+import { removeOverlay } from '@/redux/overlays/overlays.thunks';
+import { removeDataOverlay } from './removeDataOverlay';
+import { ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages';
+
+jest.mock('../../../../redux/store');
+jest.mock('../../../../redux/overlays/overlays.thunks');
+
+const MOCK_STATE = {
+  overlays: {
+    ...OVERLAYS_INITIAL_STATE_MOCK,
+    userOverlays: {
+      data: overlaysFixture,
+      loading: 'idle',
+      error: DEFAULT_ERROR,
+    },
+  },
+};
+
+describe('removeDataOverlay', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+  const getStateSpy = jest.spyOn(store, 'getState');
+  it('should throw error if matching overlay not found', () => {
+    const overlayId = 991;
+    getStateSpy.mockImplementation(() => MOCK_STATE as RootState);
+
+    expect(() => removeDataOverlay(overlayId)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND);
+  });
+
+  it('should dispatch removeOverlay with correct overlayId if matching overlay is found', () => {
+    getStateSpy.mockImplementation(() => MOCK_STATE as RootState);
+    const overlayId = overlaysFixture[0].idObject;
+
+    removeDataOverlay(overlayId);
+
+    expect(removeOverlay).toHaveBeenCalledWith({ overlayId });
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/removeDataOverlay.ts b/src/services/pluginsManager/map/overlays/removeDataOverlay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f730e17b5424c1acce4bb470cae6b660d1b43f19
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/removeDataOverlay.ts
@@ -0,0 +1,18 @@
+import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors';
+import { removeOverlay } from '@/redux/overlays/overlays.thunks';
+import { store } from '@/redux/store';
+import { ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages';
+
+export const removeDataOverlay = (overlayId: number): void => {
+  const { dispatch, getState } = store;
+  const state = getState();
+
+  const overlay = overlaySelector(state, overlayId);
+  const userOverlay = userOverlaySelector(state, overlayId);
+
+  const matchingOverlayId = overlay || userOverlay;
+
+  if (!matchingOverlayId) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND);
+
+  dispatch(removeOverlay({ overlayId }));
+};
diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05d6fd3ef32fcbd1a65ed006597ca4b5471341b2
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts
@@ -0,0 +1 @@
+export { showDataOverlay } from './showDataOverlay';
diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15b32c94cf7c96ce2acb9972717a7e3d412ee20b
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts
@@ -0,0 +1,52 @@
+import {
+  BACKGROUNDS_MOCK,
+  BACKGROUND_INITIAL_STATE_MOCK,
+} from '@/redux/backgrounds/background.mock';
+import { initialMapStateFixture } from '@/redux/map/map.fixtures';
+import { RootState, store } from '@/redux/store';
+import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable';
+
+const DEFAULT_BACKGROUND_ID = 0;
+const EMPTY_BACKGROUND_ID = 15;
+
+describe('setEmptyBackground', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  const getStateSpy = jest.spyOn(store, 'getState');
+  const dispatchSpy = jest.spyOn(store, 'dispatch');
+  it('should not set background to empty if its not available', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          map: initialMapStateFixture,
+          backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
+        }) as RootState,
+    );
+
+    expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
+
+    setBackgroundtoEmptyIfAvailable();
+
+    expect(dispatchSpy).not.toHaveBeenCalled();
+  });
+
+  it('should set background to empty if its available', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          map: initialMapStateFixture,
+          backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
+        }) as RootState,
+    );
+
+    expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
+
+    setBackgroundtoEmptyIfAvailable();
+
+    expect(dispatchSpy).toHaveBeenCalledWith({
+      payload: EMPTY_BACKGROUND_ID,
+      type: 'map/setMapBackground',
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts
new file mode 100644
index 0000000000000000000000000000000000000000..59c6c06103cdaff3be28cfaaa149e1337f8e39f1
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts
@@ -0,0 +1,12 @@
+import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.selectors';
+import { setMapBackground } from '@/redux/map/map.slice';
+import { store } from '@/redux/store';
+
+export const setBackgroundtoEmptyIfAvailable = (): void => {
+  const { dispatch, getState } = store;
+  const emptyBackgroundId = emptyBackgroundIdSelector(getState());
+
+  if (emptyBackgroundId) {
+    dispatch(setMapBackground(emptyBackgroundId));
+  }
+};
diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3ccebefa0b896ad8aeaac85e2c6ebd3e7b32ec19
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts
@@ -0,0 +1,151 @@
+/* eslint-disable no-magic-numbers */
+import { RootState, store } from '@/redux/store';
+import { overlaysFixture } from '@/models/fixtures/overlaysFixture';
+import { DEFAULT_ERROR } from '@/constants/errors';
+import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock';
+import {
+  ERROR_OVERLAY_ID_ALREADY_ACTIVE,
+  ERROR_OVERLAY_ID_NOT_FOUND,
+} from '@/services/pluginsManager/errorMessages';
+import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { showDataOverlay } from './showDataOverlay';
+import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable';
+
+jest.mock('../../../../../redux/overlayBioEntity/overlayBioEntity.thunk');
+jest.mock('../../../../../redux/store');
+jest.mock('./setBackgroundtoEmptyIfAvailable');
+
+const OVERLAY_ID = overlaysFixture[0].idObject;
+
+describe('showDataOverlay function', () => {
+  afterEach(() => {
+    jest.clearAllMocks(); // Clear mocks after each test
+  });
+
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  it('should throw error if overlay is already active', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] },
+        }) as RootState,
+    );
+
+    expect(() => showDataOverlay(OVERLAY_ID)).toThrow(ERROR_OVERLAY_ID_ALREADY_ACTIVE);
+  });
+
+  it('should throw error if matching overlay is not found', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] },
+        }) as RootState,
+    );
+
+    expect(() => showDataOverlay(991)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND);
+  });
+
+  it('should dispatch overlay id to show', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK },
+        }) as RootState,
+    );
+
+    showDataOverlay(OVERLAY_ID);
+
+    expect(getOverlayBioEntityForAllModels).toHaveBeenCalledWith({ overlayId: OVERLAY_ID });
+  });
+
+  it('should dispatch plugin event with overlay to show', () => {
+    const pluginDispatchEvent = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK },
+        }) as RootState,
+    );
+
+    showDataOverlay(OVERLAY_ID);
+
+    expect(pluginDispatchEvent).toHaveBeenCalledWith('onShowOverlay', overlaysFixture[0]);
+  });
+
+  it('should not set empty background when it show overlay', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK },
+        }) as RootState,
+    );
+
+    showDataOverlay(OVERLAY_ID);
+
+    expect(setBackgroundtoEmptyIfAvailable).not.toHaveBeenCalled();
+  });
+  it('should set empty background when it show overlay if setBackgroundEmpty is true', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          overlays: {
+            ...OVERLAYS_INITIAL_STATE_MOCK,
+            userOverlays: {
+              data: overlaysFixture,
+              loading: 'idle',
+              error: DEFAULT_ERROR,
+            },
+          },
+          overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK },
+        }) as RootState,
+    );
+
+    showDataOverlay(OVERLAY_ID, true);
+
+    expect(setBackgroundtoEmptyIfAvailable).toHaveBeenCalled();
+  });
+});
diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8304beed8c8ddf35a0ec97f6f9d4ce90cb76efce
--- /dev/null
+++ b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts
@@ -0,0 +1,33 @@
+import { store } from '@/redux/store';
+import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk';
+import { isOverlayActiveSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import {
+  ERROR_OVERLAY_ID_ALREADY_ACTIVE,
+  ERROR_OVERLAY_ID_NOT_FOUND,
+} from '@/services/pluginsManager/errorMessages';
+import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable';
+
+export const showDataOverlay = (overlayId: number, setBackgroundEmpty?: boolean): void => {
+  const { dispatch, getState } = store;
+  const state = getState();
+  const isOverlayActive = isOverlayActiveSelector(state, overlayId);
+  const overlay = overlaySelector(state, overlayId);
+  const userOverlay = userOverlaySelector(state, overlayId);
+
+  const matchingOverlay = overlay || userOverlay;
+
+  if (!matchingOverlay) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND);
+
+  if (isOverlayActive) {
+    throw new Error(ERROR_OVERLAY_ID_ALREADY_ACTIVE);
+  }
+
+  if (setBackgroundEmpty) {
+    setBackgroundtoEmptyIfAvailable();
+  }
+
+  dispatch(getOverlayBioEntityForAllModels({ overlayId }));
+  PluginsEventBus.dispatchEvent('onShowOverlay', matchingOverlay);
+};
diff --git a/src/services/pluginsManager/map/position/getCenter.test.ts b/src/services/pluginsManager/map/position/getCenter.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f9d9dd545bda215f262f039e2d9c5f2ae7349ab5
--- /dev/null
+++ b/src/services/pluginsManager/map/position/getCenter.test.ts
@@ -0,0 +1,39 @@
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { RootState, store } from '@/redux/store';
+import { getCenter } from './getCenter';
+
+jest.mock('../../../../redux/store');
+
+describe('getCenter - plugin method', () => {
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  getStateSpy.mockImplementation(
+    () =>
+      ({
+        map: {
+          data: {
+            ...initialMapDataFixture,
+            position: {
+              ...initialMapDataFixture.position,
+              last: {
+                x: 2137,
+                y: 420,
+                z: 1.488,
+              },
+            },
+          },
+          loading: 'succeeded',
+          error: { message: '', name: '' },
+          openedMaps: openedMapsThreeSubmapsFixture,
+        },
+      }) as RootState,
+  );
+
+  it('should return last position from Redux', () => {
+    expect(getCenter()).toStrictEqual({
+      x: 2137,
+      y: 420,
+      z: 1.488,
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/position/getCenter.ts b/src/services/pluginsManager/map/position/getCenter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccf28579481881132d6677d2a982a465321ddac0
--- /dev/null
+++ b/src/services/pluginsManager/map/position/getCenter.ts
@@ -0,0 +1,10 @@
+import { mapDataLastPositionSelector } from '@/redux/map/map.selectors';
+import { store } from '@/redux/store';
+import { Point } from '@/types/map';
+
+export const getCenter = (): Point => {
+  const { getState } = store;
+  const lastPosition = mapDataLastPositionSelector(getState());
+
+  return lastPosition;
+};
diff --git a/src/services/pluginsManager/map/position/setCenter.test.ts b/src/services/pluginsManager/map/position/setCenter.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef26567b20576c97982dd6385a71829b534d4249
--- /dev/null
+++ b/src/services/pluginsManager/map/position/setCenter.test.ts
@@ -0,0 +1,55 @@
+import { setMapPosition } from '@/redux/map/map.slice';
+import { store } from '@/redux/store';
+import { Point } from '@/types/map';
+import { ZodError } from 'zod';
+import { setCenter } from './setCenter';
+
+jest.mock('../../../../redux/store');
+
+describe('setCenter - plugin method', () => {
+  const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+  describe('when position is invalid', () => {
+    const invalidPositions = [
+      {
+        x: -1,
+        y: 1,
+        z: 1,
+      },
+      {
+        x: 1,
+        y: -1,
+        z: 1,
+      },
+      {
+        x: 1,
+        y: 1,
+        z: -1,
+      },
+      {
+        y: 1,
+      },
+      {
+        x: 1,
+      },
+    ] as Point[];
+
+    it.each(invalidPositions)('should throw error', position => {
+      expect(() => setCenter(position)).toThrow(ZodError);
+    });
+  });
+
+  describe('when position is valid', () => {
+    const position: Point = {
+      x: 500,
+      y: 200,
+      z: 2,
+    };
+
+    it('should set map position', () => {
+      setCenter(position);
+
+      expect(dispatchSpy).toHaveBeenCalledWith(setMapPosition(position));
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/position/setCenter.ts b/src/services/pluginsManager/map/position/setCenter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a32717df9b4897e0136f3d461d26779a9e5480ab
--- /dev/null
+++ b/src/services/pluginsManager/map/position/setCenter.ts
@@ -0,0 +1,11 @@
+import { pointSchema } from '@/models/pointSchema';
+import { setMapPosition } from '@/redux/map/map.slice';
+import { store } from '@/redux/store';
+import { Point } from '@/types/map';
+
+export const setCenter = (position: Point): void => {
+  const { dispatch } = store;
+  pointSchema.parse(position);
+
+  dispatch(setMapPosition(position));
+};
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
index c04dfaace49cb9e95f3d76640e407b80a11c3ff1..207c91c02692e871a917dd463f7e5e111e5eb21f 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
@@ -3,6 +3,10 @@ import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/liste
 import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { store } from '@/redux/store';
 import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates';
+import { mapDataLastZoomValue, mapDataMaxZoomValue } from '@/redux/map/map.selectors';
+import { searchDistanceValSelector } from '@/redux/configuration/configuration.selectors';
+import { DEFAULT_ZOOM } from '@/constants/map';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
 import { Coordinates } from './triggerSearch.types';
 
 export const searchByCoordinates = async (
@@ -11,11 +15,16 @@ export const searchByCoordinates = async (
   hasFitBounds?: boolean,
   fitBoundsZoom?: number,
 ): Promise<void> => {
-  const { dispatch } = store;
+  const { dispatch, getState } = store;
   // side-effect below is to prevent complications with data update - old data may conflict with new data
   // so we need to reset all the data before updating
   dispatch(handleDataReset);
 
+  const maxZoom = mapDataMaxZoomValue(getState());
+  const lastZoom = mapDataLastZoomValue(getState());
+  const searchDistance = searchDistanceValSelector(getState());
+  const isResultDrawerOpen = resultDrawerOpen(getState());
+
   const searchResults = await getElementsByPoint({
     point: coordinates,
     currentModelId: modelId,
@@ -25,5 +34,14 @@ export const searchByCoordinates = async (
     return;
   }
 
-  handleSearchResultAction({ searchResults, dispatch, hasFitBounds, fitBoundsZoom });
+  handleSearchResultAction({
+    searchResults,
+    dispatch,
+    hasFitBounds,
+    zoom: fitBoundsZoom || lastZoom || DEFAULT_ZOOM,
+    maxZoom,
+    point: coordinates,
+    searchDistance,
+    isResultDrawerOpen,
+  });
 };
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts b/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts
index e804a294f9ad4f681661e008f15897543d1db1d9..b4beac3eee835d5984b8849c6c9c5be171ed41c5 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts
@@ -1,14 +1,15 @@
-import { RootState, store } from '@/redux/store';
-import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
-import { apiPath } from '@/redux/apiPath';
-import { HttpStatusCode } from 'axios';
 import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
-import { drugsFixture } from '@/models/fixtures/drugFixtures';
 import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
+import { drugsFixture } from '@/models/fixtures/drugFixtures';
+import { apiPath } from '@/redux/apiPath';
 import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
-import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
 import { MAP_INITIAL_STATE } from '@/redux/map/map.constants';
+import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { RootState, store } from '@/redux/store';
+import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { waitFor } from '@testing-library/react';
+import { HttpStatusCode } from 'axios';
 import { searchByQuery } from './searchByQuery';
 import { searchFitBounds } from './searchFitBounds';
 
@@ -116,4 +117,32 @@ describe('searchByQuery', () => {
       expect(searchFitBounds).not.toHaveBeenCalled();
     });
   });
+
+  it('should reset reactions data on every trigger', async () => {
+    dispatchSpy.mockImplementation(() => ({
+      unwrap: (): Promise<void> => Promise.resolve(),
+    }));
+
+    getStateSpy.mockImplementation(() => MOCK_SEARCH_BY_QUERY_STORE as RootState);
+    mockedAxiosClient
+      .onGet(
+        apiPath.getBioEntityContentsStringWithQuery({
+          searchQuery: SEARCH_QUERY,
+          isPerfectMatch: false,
+        }),
+      )
+      .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+    mockedAxiosClient
+      .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
+      .reply(HttpStatusCode.Ok, drugsFixture);
+
+    mockedAxiosClient
+      .onGet(apiPath.getChemicalsStringWithQuery(SEARCH_QUERY))
+      .reply(HttpStatusCode.Ok, chemicalsFixture);
+
+    searchByQuery(SEARCH_QUERY, false, true);
+
+    expect(dispatchSpy).toHaveBeenCalledWith(resetReactionsData());
+  });
 });
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts b/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
index e29c50f97532b73a77fe158f1d4e2571ff58bb4b..c195ee6cbce0094646e719ef91b840105fb40d8d 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
@@ -1,4 +1,5 @@
 import { getSearchValuesArrayAndTrimToSeven } from '@/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
 import { getSearchData } from '@/redux/search/search.thunks';
 import { store } from '@/redux/store';
 import { displaySearchDrawerWithSelectedDefaultTab } from './displaySearchDrawerWithSelectedDefaultTab';
@@ -13,6 +14,8 @@ export const searchByQuery = (
   const searchValues = getSearchValuesArrayAndTrimToSeven(query);
   const isPerfectMatch = !!perfectSearch;
 
+  dispatch(resetReactionsData());
+
   dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch }))
     ?.unwrap()
     .then(() => {
diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
index 1b522bb065723a9ef78cf2d24e01869960c3c00a..bdec855900cbde830daca6eb1343a424e40087fc 100644
--- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
+++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
@@ -1,14 +1,16 @@
 /* eslint-disable no-magic-numbers */
-import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
-import { apiPath } from '@/redux/apiPath';
-import { HttpStatusCode } from 'axios';
-import { drugsFixture } from '@/models/fixtures/drugFixtures';
+import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock';
+import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
 import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
-import { RootState, store } from '@/redux/store';
+import { drugsFixture } from '@/models/fixtures/drugFixtures';
 import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock';
-import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { apiPath } from '@/redux/apiPath';
+import { RootState, store } from '@/redux/store';
+import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { waitFor } from '@testing-library/react';
-import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction';
+import { HttpStatusCode } from 'axios';
 import { triggerSearch } from './triggerSearch';
 import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages';
 
@@ -18,6 +20,19 @@ const SEARCH_QUERY = 'park7';
 const point = { x: 545.8013, y: 500.9926 };
 const modelId = 1000;
 
+const MOCK_STATE = {
+  drawer: {
+    isOpen: false,
+  },
+  map: {
+    data: { ...initialMapDataFixture },
+    loading: 'succeeded',
+    error: { message: '', name: '' },
+    openedMaps: openedMapsThreeSubmapsFixture,
+  },
+  configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+};
+
 jest.mock('../../../../redux/store');
 jest.mock(
   '../../../../components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction',
@@ -29,6 +44,7 @@ describe('triggerSearch', () => {
   });
   describe('search by query', () => {
     it('should throw error if query param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         query: 123 as any,
@@ -39,14 +55,9 @@ describe('triggerSearch', () => {
       );
     });
     it('should search for provided query and open drawer when it is not open', async () => {
-      const getState = jest.spyOn(store, 'getState').mockImplementation(
-        () =>
-          ({
-            drawer: {
-              isOpen: false,
-            },
-          }) as RootState,
-      );
+      const getState = jest
+        .spyOn(store, 'getState')
+        .mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosClient
         .onGet(
           apiPath.getBioEntityContentsStringWithQuery({
@@ -70,7 +81,7 @@ describe('triggerSearch', () => {
         }),
       ).resolves.toBe(undefined);
 
-      expect(store.dispatch).toHaveBeenCalledTimes(2);
+      expect(store.dispatch).toHaveBeenCalledTimes(3);
 
       expect(store.dispatch).not.toHaveBeenCalledWith({
         payload: SEARCH_QUERY,
@@ -87,6 +98,7 @@ describe('triggerSearch', () => {
       const getState = jest.spyOn(store, 'getState').mockImplementation(
         () =>
           ({
+            ...MOCK_STATE,
             drawer: {
               isOpen: true,
             },
@@ -117,7 +129,7 @@ describe('triggerSearch', () => {
       ).resolves.toBe(undefined);
 
       expect(getState).toHaveBeenCalled();
-      expect(store.dispatch).toHaveBeenCalledTimes(2);
+      expect(store.dispatch).toHaveBeenCalledTimes(3);
       expect(store.dispatch).not.toHaveBeenLastCalledWith({
         payload: SEARCH_QUERY,
         type: 'drawer/openSearchDrawerWithSelectedTab',
@@ -132,6 +144,7 @@ describe('triggerSearch', () => {
   });
   describe('search by coordinations', () => {
     it('should throw error if coordinates param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         coordinates: {} as any,
@@ -143,6 +156,7 @@ describe('triggerSearch', () => {
       );
     });
     it('should throw error if model id param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         coordinates: { x: 992, y: 993 },
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -152,6 +166,7 @@ describe('triggerSearch', () => {
       await expect(triggerSearch(invalidParams)).rejects.toThrowError(ERROR_INVALID_MODEL_ID_TYPE);
     });
     it('should search result with proper data', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosOldClient
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
@@ -175,10 +190,20 @@ describe('triggerSearch', () => {
         expect(handleSearchResultAction).toHaveBeenCalledWith({
           searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS],
           dispatch: store.dispatch,
+          hasFitBounds: undefined,
+          isResultDrawerOpen: false,
+          maxZoom: 9,
+          point: {
+            x: 545.8013,
+            y: 500.9926,
+          },
+          searchDistance: undefined,
+          zoom: 5,
         });
       });
     });
     it('should not search result if there is no bio entity with specific coordinates', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosOldClient
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, []);
diff --git a/src/services/pluginsManager/map/zoom/getZoom.test.ts b/src/services/pluginsManager/map/zoom/getZoom.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7808d53163d34fa802f14c8e090119590c3c11f
--- /dev/null
+++ b/src/services/pluginsManager/map/zoom/getZoom.test.ts
@@ -0,0 +1,69 @@
+/* eslint-disable no-magic-numbers */
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { RootState, store } from '@/redux/store';
+import { getZoom } from './getZoom';
+
+jest.mock('../../../../redux/store');
+
+describe('getZoom - plugin method', () => {
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  describe('when last position zoom is present', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            map: {
+              data: {
+                ...initialMapDataFixture,
+                position: {
+                  ...initialMapDataFixture.position,
+                  last: {
+                    x: 2137,
+                    y: 420,
+                    z: 1.488,
+                  },
+                },
+              },
+              loading: 'succeeded',
+              error: { message: '', name: '' },
+              openedMaps: openedMapsThreeSubmapsFixture,
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should return last position from Redux', () => {
+      expect(getZoom()).toEqual(1.488);
+    });
+  });
+
+  describe('when last position zoom is NOT present', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            map: {
+              data: {
+                ...initialMapDataFixture,
+                position: {
+                  ...initialMapDataFixture.position,
+                  last: {
+                    x: 2137,
+                    y: 420,
+                  },
+                },
+              },
+              loading: 'succeeded',
+              error: { message: '', name: '' },
+              openedMaps: openedMapsThreeSubmapsFixture,
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should return undefined', () => {
+      expect(getZoom()).toBeUndefined();
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/zoom/getZoom.ts b/src/services/pluginsManager/map/zoom/getZoom.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fee865408304b40d1f3ea5d614d39c2315b32123
--- /dev/null
+++ b/src/services/pluginsManager/map/zoom/getZoom.ts
@@ -0,0 +1,9 @@
+import { mapDataLastPositionSelector } from '@/redux/map/map.selectors';
+import { store } from '@/redux/store';
+
+export const getZoom = (): number | undefined => {
+  const { getState } = store;
+  const lastPosition = mapDataLastPositionSelector(getState());
+
+  return lastPosition?.z;
+};
diff --git a/src/services/pluginsManager/map/zoom/setZoom.test.ts b/src/services/pluginsManager/map/zoom/setZoom.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..55502f302a70e74e6e7b7983e1bc12601e1071d9
--- /dev/null
+++ b/src/services/pluginsManager/map/zoom/setZoom.test.ts
@@ -0,0 +1,77 @@
+/* eslint-disable no-magic-numbers */
+import { ZOOM_ERRORS } from '@/constants/errors';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { setLastPositionZoom } from '@/redux/map/map.slice';
+import { RootState, store } from '@/redux/store';
+import { ZodError } from 'zod';
+import { setZoom } from './setZoom';
+
+jest.mock('../../../../redux/store');
+
+describe('setZoom - plugin method', () => {
+  const dispatchSpy = jest.spyOn(store, 'dispatch');
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  beforeEach(() => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          map: {
+            data: {
+              ...initialMapDataFixture,
+              position: {
+                ...initialMapDataFixture.position,
+                last: {
+                  x: 2137,
+                  y: 420,
+                  z: 1.488,
+                },
+              },
+              size: {
+                ...initialMapDataFixture.size,
+                minZoom: 2,
+                maxZoom: 8,
+              },
+            },
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+            openedMaps: openedMapsThreeSubmapsFixture,
+          },
+        }) as RootState,
+    );
+  });
+
+  describe('when zoom value type is invalid', () => {
+    const invalidZoom = [-1, -123, '-123'] as number[];
+
+    it.each(invalidZoom)('should throw error', zoom => {
+      expect(() => setZoom(zoom)).toThrow(ZodError);
+    });
+  });
+
+  describe('when zoom value value exeeds max zoom', () => {
+    const invalidZoom = [444, 21, 9] as number[];
+
+    it.each(invalidZoom)('should throw error', zoom => {
+      expect(() => setZoom(zoom)).toThrow(ZOOM_ERRORS.ZOOM_VALUE_TOO_HIGH(8));
+    });
+  });
+
+  describe('when zoom value value exeeds min zoom', () => {
+    const invalidZoom = [1, 0] as number[];
+
+    it.each(invalidZoom)('should throw error', zoom => {
+      expect(() => setZoom(zoom)).toThrow(ZOOM_ERRORS.ZOOM_VALUE_TOO_LOW(2));
+    });
+  });
+
+  describe('when zoom is valid', () => {
+    const zoom = 2;
+
+    it('should set map zoom', () => {
+      setZoom(zoom);
+
+      expect(dispatchSpy).toHaveBeenCalledWith(setLastPositionZoom({ zoom }));
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/zoom/setZoom.ts b/src/services/pluginsManager/map/zoom/setZoom.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f161d1c0588e5010c701d4e64ed2e6a7e83f7f5a
--- /dev/null
+++ b/src/services/pluginsManager/map/zoom/setZoom.ts
@@ -0,0 +1,21 @@
+import { ZOOM_ERRORS } from '@/constants/errors';
+import { zPointSchema } from '@/models/pointSchema';
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { setLastPositionZoom } from '@/redux/map/map.slice';
+import { store } from '@/redux/store';
+
+export const setZoom = (zoom: number): void => {
+  const { dispatch, getState } = store;
+  const { minZoom, maxZoom } = mapDataSizeSelector(getState());
+  zPointSchema.parse(zoom);
+
+  if (zoom < minZoom) {
+    throw Error(ZOOM_ERRORS.ZOOM_VALUE_TOO_LOW(minZoom));
+  }
+
+  if (zoom > maxZoom) {
+    throw Error(ZOOM_ERRORS.ZOOM_VALUE_TOO_HIGH(maxZoom));
+  }
+
+  dispatch(setLastPositionZoom({ zoom }));
+};
diff --git a/src/services/pluginsManager/overviewImage/getCurrentOverviewImage.ts b/src/services/pluginsManager/overviewImage/getCurrentOverviewImage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b9d54c9aad16354024f9efbf5e9f487e182926eb
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/getCurrentOverviewImage.ts
@@ -0,0 +1,10 @@
+import { currentOverviewImageSelector } from '@/redux/project/project.selectors';
+import { store } from '@/redux/store';
+import { OverviewImageView } from '@/types/models';
+
+export const getCurrentOverviewImage = (): OverviewImageView | undefined => {
+  const { getState } = store;
+  const overviewImage = currentOverviewImageSelector(getState());
+
+  return overviewImage;
+};
diff --git a/src/services/pluginsManager/overviewImage/getOverviewImages.ts b/src/services/pluginsManager/overviewImage/getOverviewImages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e2e19c189033e08f86b97d4a0b983d6cca30cb3
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/getOverviewImages.ts
@@ -0,0 +1,10 @@
+import { projectOverviewImagesSelector } from '@/redux/project/project.selectors';
+import { store } from '@/redux/store';
+import { OverviewImageView } from '@/types/models';
+
+export const getOverviewImages = (): OverviewImageView[] => {
+  const { getState } = store;
+  const overviewImages = projectOverviewImagesSelector(getState());
+
+  return overviewImages;
+};
diff --git a/src/services/pluginsManager/overviewImage/hideOverviewImageModal.test.ts b/src/services/pluginsManager/overviewImage/hideOverviewImageModal.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25eb34afffd19c5809906d2adc013892927dc3e7
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/hideOverviewImageModal.test.ts
@@ -0,0 +1,60 @@
+import { closeModal } from '@/redux/modal/modal.slice';
+import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
+import { RootState, store } from '@/redux/store';
+import { hideOverviewImageModal } from './hideOverviewImageModal';
+
+jest.mock('../../../redux/store');
+
+describe('hideOverviewImageModal - util', () => {
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
+
+  describe('when opened modal is overview image', () => {
+    const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            modal: {
+              ...INITIAL_STORE_STATE_MOCK.modal,
+              modalName: 'overview-images',
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should close modal', () => {
+      hideOverviewImageModal();
+
+      expect(dispatchSpy).toHaveBeenCalledWith(closeModal());
+    });
+  });
+
+  describe('when opened modal is NOT overview image', () => {
+    const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            modal: {
+              ...INITIAL_STORE_STATE_MOCK.modal,
+              modalName: 'login',
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should not close modal', () => {
+      hideOverviewImageModal();
+
+      expect(dispatchSpy).not.toHaveBeenCalledWith(closeModal());
+    });
+  });
+});
diff --git a/src/services/pluginsManager/overviewImage/hideOverviewImageModal.ts b/src/services/pluginsManager/overviewImage/hideOverviewImageModal.ts
new file mode 100644
index 0000000000000000000000000000000000000000..47652cd524a0fb8a0851ad730d280085d3105559
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/hideOverviewImageModal.ts
@@ -0,0 +1,14 @@
+import { modalSelector } from '@/redux/modal/modal.selector';
+import { closeModal } from '@/redux/modal/modal.slice';
+import { store } from '@/redux/store';
+
+export const hideOverviewImageModal = (): void => {
+  const { getState, dispatch } = store;
+  const { modalName } = modalSelector(getState());
+
+  if (modalName !== 'overview-images') {
+    return;
+  }
+
+  dispatch(closeModal());
+};
diff --git a/src/services/pluginsManager/overviewImage/selectOverviewImage.test.ts b/src/services/pluginsManager/overviewImage/selectOverviewImage.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..500bfae94ebce30954ce2b772a67a4c12b224917
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/selectOverviewImage.test.ts
@@ -0,0 +1,63 @@
+import { OVERVIEW_IMAGE_ERRORS } from '@/constants/errors';
+import { setOverviewImageId } from '@/redux/modal/modal.slice';
+import { PROJECT_OVERVIEW_IMAGE_MOCK } from '@/redux/project/project.mock';
+import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
+import { RootState, store } from '@/redux/store';
+import { selectOverviewImage } from './selectOverviewImage';
+
+jest.mock('../../../redux/store');
+
+describe('selectOverviewImage - plugin method', () => {
+  const dispatchSpy = jest.spyOn(store, 'dispatch');
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  describe('when image id is valid', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            project: {
+              ...INITIAL_STORE_STATE_MOCK.project,
+              data: {
+                ...INITIAL_STORE_STATE_MOCK.project.data,
+                overviewImageViews: [PROJECT_OVERVIEW_IMAGE_MOCK],
+              },
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should dispatch action set overview image', () => {
+      selectOverviewImage(PROJECT_OVERVIEW_IMAGE_MOCK.idObject);
+
+      expect(dispatchSpy).toHaveBeenCalledWith(
+        setOverviewImageId(PROJECT_OVERVIEW_IMAGE_MOCK.idObject),
+      );
+    });
+  });
+
+  describe('when image id is NOT valid', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            project: {
+              ...INITIAL_STORE_STATE_MOCK.project,
+              data: {
+                ...INITIAL_STORE_STATE_MOCK.project.data,
+                overviewImageViews: [],
+              },
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should throw error', () => {
+      expect(() => selectOverviewImage(PROJECT_OVERVIEW_IMAGE_MOCK.idObject)).toThrow(
+        OVERVIEW_IMAGE_ERRORS.IMAGE_ID_IS_INVALID,
+      );
+    });
+  });
+});
diff --git a/src/services/pluginsManager/overviewImage/selectOverviewImage.ts b/src/services/pluginsManager/overviewImage/selectOverviewImage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..71ef40f1ff80adc6892ac6621a62562750cd3be4
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/selectOverviewImage.ts
@@ -0,0 +1,17 @@
+import { OVERVIEW_IMAGE_ERRORS } from '@/constants/errors';
+import { setOverviewImageId } from '@/redux/modal/modal.slice';
+import { projectOverviewImagesSelector } from '@/redux/project/project.selectors';
+import { store } from '@/redux/store';
+
+export const selectOverviewImage = (imageId: number): void => {
+  const { dispatch, getState } = store;
+  const overviewImages = projectOverviewImagesSelector(getState());
+  const foundOverviewImage = overviewImages.find(o => o.idObject === imageId);
+  const isImageIdValid = Boolean(foundOverviewImage);
+
+  if (!isImageIdValid) {
+    throw new Error(OVERVIEW_IMAGE_ERRORS.IMAGE_ID_IS_INVALID);
+  }
+
+  dispatch(setOverviewImageId(imageId));
+};
diff --git a/src/services/pluginsManager/overviewImage/showOverviewImageModal.test.ts b/src/services/pluginsManager/overviewImage/showOverviewImageModal.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f829470b22eb062f67a5e17a6641ffdbde72cbe7
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/showOverviewImageModal.test.ts
@@ -0,0 +1,105 @@
+import { OVERVIEW_IMAGE_ERRORS } from '@/constants/errors';
+import { openOverviewImagesModalById } from '@/redux/modal/modal.slice';
+import { PROJECT_OVERVIEW_IMAGE_MOCK } from '@/redux/project/project.mock';
+import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
+import { RootState, store } from '@/redux/store';
+import { showOverviewImageModal } from './showOverviewImageModal';
+
+jest.mock('../../../redux/store');
+
+describe('showOverviewImageModal - plugin method', () => {
+  const dispatchSpy = jest.spyOn(store, 'dispatch');
+  const getStateSpy = jest.spyOn(store, 'getState');
+
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
+
+  describe('when image id is not provided', () => {
+    const defaultImageId = 23332;
+
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            project: {
+              ...INITIAL_STORE_STATE_MOCK.project,
+              data: {
+                ...INITIAL_STORE_STATE_MOCK.project.data,
+                overviewImageViews: [
+                  PROJECT_OVERVIEW_IMAGE_MOCK,
+                  {
+                    ...PROJECT_OVERVIEW_IMAGE_MOCK,
+                    idObject: defaultImageId,
+                  },
+                ],
+                topOverviewImage: {
+                  ...PROJECT_OVERVIEW_IMAGE_MOCK,
+                  idObject: defaultImageId,
+                },
+              },
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should dispatch action set overview image with defaultImageId', () => {
+      showOverviewImageModal();
+
+      expect(dispatchSpy).toHaveBeenCalledWith(openOverviewImagesModalById(defaultImageId));
+    });
+  });
+
+  describe('when image id is valid', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            project: {
+              ...INITIAL_STORE_STATE_MOCK.project,
+              data: {
+                ...INITIAL_STORE_STATE_MOCK.project.data,
+                overviewImageViews: [PROJECT_OVERVIEW_IMAGE_MOCK],
+                topOverviewImage: PROJECT_OVERVIEW_IMAGE_MOCK,
+              },
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should dispatch action set overview image', () => {
+      showOverviewImageModal(PROJECT_OVERVIEW_IMAGE_MOCK.idObject);
+
+      expect(dispatchSpy).toHaveBeenCalledWith(
+        openOverviewImagesModalById(PROJECT_OVERVIEW_IMAGE_MOCK.idObject),
+      );
+    });
+  });
+
+  describe('when image id is NOT valid', () => {
+    beforeEach(() => {
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            ...INITIAL_STORE_STATE_MOCK,
+            project: {
+              ...INITIAL_STORE_STATE_MOCK.project,
+              data: {
+                ...INITIAL_STORE_STATE_MOCK.project.data,
+                overviewImageViews: [],
+                topOverviewImage: PROJECT_OVERVIEW_IMAGE_MOCK,
+              },
+            },
+          }) as RootState,
+      );
+    });
+
+    it('should throw error', () => {
+      expect(() => showOverviewImageModal(PROJECT_OVERVIEW_IMAGE_MOCK.idObject)).toThrow(
+        OVERVIEW_IMAGE_ERRORS.IMAGE_ID_IS_INVALID,
+      );
+    });
+  });
+});
diff --git a/src/services/pluginsManager/overviewImage/showOverviewImageModal.ts b/src/services/pluginsManager/overviewImage/showOverviewImageModal.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac4351069bceb73d3a136dfe8a11d357ee7ba165
--- /dev/null
+++ b/src/services/pluginsManager/overviewImage/showOverviewImageModal.ts
@@ -0,0 +1,23 @@
+import { OVERVIEW_IMAGE_ERRORS } from '@/constants/errors';
+import { openOverviewImagesModalById } from '@/redux/modal/modal.slice';
+import {
+  projectDefaultOverviewImageIdSelector,
+  projectOverviewImagesSelector,
+} from '@/redux/project/project.selectors';
+import { store } from '@/redux/store';
+
+export const showOverviewImageModal = (imageId?: number): void => {
+  const { dispatch, getState } = store;
+  const overviewImages = projectOverviewImagesSelector(getState());
+  const defaultImageId = projectDefaultOverviewImageIdSelector(getState());
+  const selectedImageId = imageId || defaultImageId;
+
+  const foundOverviewImage = overviewImages.find(o => o.idObject === selectedImageId);
+  const isImageIdValid = Boolean(foundOverviewImage);
+
+  if (!isImageIdValid) {
+    throw new Error(OVERVIEW_IMAGE_ERRORS.IMAGE_ID_IS_INVALID);
+  }
+
+  dispatch(openOverviewImagesModalById(selectedImageId));
+};
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
index 2969f8ab7be3e86ef9f44f871c4eace00d0e97a4..e34911324f81a8824d842d7f4f4955d9ca4765ad 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
@@ -20,6 +20,9 @@ const PLUGINS_EVENTS = {
   search: {
     onSearch: 'onSearch',
   },
+  plugins: {
+    onPluginUnload: 'onPluginUnload',
+  },
 };
 
 export const ALLOWED_PLUGINS_EVENTS = Object.values(PLUGINS_EVENTS).flatMap(obj =>
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.test.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.test.ts
index 474b9d214ce9d920b94c2a473cf052201294b496..c90919b45b79d6052fe4e215286838f25f4f0c85 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.test.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.test.ts
@@ -1,6 +1,22 @@
 /* eslint-disable no-magic-numbers */
 import { createdOverlayFixture } from '@/models/fixtures/overlaysFixture';
+import { RootState, store } from '@/redux/store';
+
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, THIRD_ARRAY_ELEMENT } from '@/constants/common';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { showToast } from '@/utils/showToast';
 import { PluginsEventBus } from './pluginsEventBus';
+import { ERROR_INVALID_EVENT_TYPE, ERROR_PLUGIN_CRASH } from '../errorMessages';
+
+const plugin = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+const secondPlugin = PLUGINS_MOCK[SECOND_ARRAY_ELEMENT];
+const thirdPlugin = PLUGINS_MOCK[THIRD_ARRAY_ELEMENT];
+
+jest.mock('../../../utils/showToast');
 
 describe('PluginsEventBus', () => {
   beforeEach(() => {
@@ -8,12 +24,13 @@ describe('PluginsEventBus', () => {
   });
   it('should store event listener', () => {
     const callback = jest.fn();
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
 
     expect(PluginsEventBus.events).toEqual([
       {
-        hash: '123',
+        hash: plugin.hash,
         type: 'onAddDataOverlay',
+        pluginName: plugin.name,
         callback,
       },
     ]);
@@ -21,7 +38,7 @@ describe('PluginsEventBus', () => {
 
   it('should dispatch event correctly', () => {
     const callback = jest.fn();
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
     PluginsEventBus.dispatchEvent('onAddDataOverlay', createdOverlayFixture);
 
     expect(callback).toHaveBeenCalled();
@@ -30,29 +47,31 @@ describe('PluginsEventBus', () => {
   it('should throw error if event type is incorrect', () => {
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     expect(() => PluginsEventBus.dispatchEvent('onData' as any, createdOverlayFixture)).toThrow(
-      'Invalid event type: onData',
+      ERROR_INVALID_EVENT_TYPE('onData'),
     );
   });
   it('should remove listener only for specific plugin, event type, and callback', () => {
     const callback = (): void => {};
 
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
-    PluginsEventBus.addListener('123', 'onBioEntityClick', callback);
-    PluginsEventBus.addListener('234', 'onBioEntityClick', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onBioEntityClick', callback);
+    PluginsEventBus.addListener(secondPlugin.hash, secondPlugin.name, 'onBioEntityClick', callback);
 
     expect(PluginsEventBus.events).toHaveLength(3);
 
-    PluginsEventBus.removeListener('123', 'onAddDataOverlay', callback);
+    PluginsEventBus.removeListener(plugin.hash, 'onAddDataOverlay', callback);
     expect(PluginsEventBus.events).toHaveLength(2);
     expect(PluginsEventBus.events).toEqual([
       {
         callback,
-        hash: '123',
+        hash: plugin.hash,
+        pluginName: plugin.name,
         type: 'onBioEntityClick',
       },
       {
         callback,
-        hash: '234',
+        hash: secondPlugin.hash,
+        pluginName: secondPlugin.name,
         type: 'onBioEntityClick',
       },
     ]);
@@ -60,13 +79,13 @@ describe('PluginsEventBus', () => {
   it('should throw if listener is not defined by plugin', () => {
     const callback = (): void => {};
 
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
-    PluginsEventBus.addListener('123', 'onBioEntityClick', callback);
-    PluginsEventBus.addListener('234', 'onBioEntityClick', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onBioEntityClick', callback);
+    PluginsEventBus.addListener(secondPlugin.hash, secondPlugin.name, 'onBioEntityClick', callback);
 
     expect(PluginsEventBus.events).toHaveLength(3);
 
-    expect(() => PluginsEventBus.removeListener('123', 'onHideOverlay', callback)).toThrow(
+    expect(() => PluginsEventBus.removeListener(plugin.hash, 'onHideOverlay', callback)).toThrow(
       "Listener doesn't exist",
     );
     expect(PluginsEventBus.events).toHaveLength(3);
@@ -75,43 +94,117 @@ describe('PluginsEventBus', () => {
     const callback = (): void => {};
     const secondCallback = (): void => {};
 
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', secondCallback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', secondCallback);
 
-    PluginsEventBus.removeListener('123', 'onAddDataOverlay', callback);
+    PluginsEventBus.removeListener(plugin.hash, 'onAddDataOverlay', callback);
 
     expect(PluginsEventBus.events).toHaveLength(1);
     expect(PluginsEventBus.events).toEqual([
       {
         callback: secondCallback,
-        hash: '123',
+        hash: plugin.hash,
+        pluginName: plugin.name,
         type: 'onAddDataOverlay',
       },
     ]);
   });
   it('should remove all listeners defined by specific plugin', () => {
     const callback = (): void => {};
-    PluginsEventBus.addListener('123', 'onAddDataOverlay', callback);
-    PluginsEventBus.addListener('123', 'onBackgroundOverlayChange', callback);
-    PluginsEventBus.addListener('251', 'onSubmapOpen', callback);
-    PluginsEventBus.addListener('123', 'onHideOverlay', callback);
-    PluginsEventBus.addListener('123', 'onSubmapOpen', callback);
-    PluginsEventBus.addListener('992', 'onSearch', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onAddDataOverlay', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onBackgroundOverlayChange', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onSubmapOpen', callback);
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onHideOverlay', callback);
+    PluginsEventBus.addListener(secondPlugin.hash, secondPlugin.name, 'onSubmapOpen', callback);
+    PluginsEventBus.addListener(thirdPlugin.hash, thirdPlugin.name, 'onSearch', callback);
 
-    PluginsEventBus.removeAllListeners('123');
+    PluginsEventBus.removeAllListeners(plugin.hash);
 
     expect(PluginsEventBus.events).toHaveLength(2);
     expect(PluginsEventBus.events).toEqual([
       {
         callback,
-        hash: '251',
+        hash: secondPlugin.hash,
         type: 'onSubmapOpen',
+        pluginName: secondPlugin.name,
       },
       {
         callback,
-        hash: '992',
+        hash: thirdPlugin.hash,
         type: 'onSearch',
+        pluginName: thirdPlugin.name,
       },
     ]);
   });
+  it('should show toast when event callback provided by plugin throws error', () => {
+    const getStateSpy = jest.spyOn(store, 'getState');
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          plugins: {
+            ...PLUGINS_INITIAL_STATE_MOCK,
+            activePlugins: {
+              data: {
+                [plugin.hash]: plugin,
+              },
+              pluginsId: [plugin.hash],
+            },
+            list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+          },
+        }) as RootState,
+    );
+
+    const callbackMock = jest.fn(() => {
+      throw new Error('Invalid callback');
+    });
+
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onPluginUnload', callbackMock);
+
+    PluginsEventBus.dispatchEvent('onPluginUnload', {
+      hash: plugin.hash,
+    });
+
+    expect(callbackMock).toHaveBeenCalled();
+    expect(callbackMock).toThrow('Invalid callback');
+    expect(showToast).toHaveBeenCalledWith({
+      message: ERROR_PLUGIN_CRASH(plugin.name),
+      type: 'error',
+    });
+  });
+  it('should call all event callbacks for specific event type even if one event callback provided by plugin throws error', () => {
+    const getStateSpy = jest.spyOn(store, 'getState');
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          plugins: {
+            ...PLUGINS_INITIAL_STATE_MOCK,
+            activePlugins: {
+              data: {
+                [plugin.hash]: plugin,
+              },
+              pluginsId: [plugin.hash],
+            },
+            list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+          },
+        }) as RootState,
+    );
+
+    const errorCallbackMock = jest.fn(() => {
+      throw new Error('Invalid callback');
+    });
+
+    const callbackMock = jest.fn(() => {
+      return 'plguin';
+    });
+
+    PluginsEventBus.addListener(plugin.hash, plugin.name, 'onSubmapOpen', errorCallbackMock);
+    PluginsEventBus.addListener(secondPlugin.hash, secondPlugin.name, 'onSubmapOpen', callbackMock);
+
+    PluginsEventBus.dispatchEvent('onSubmapOpen', 109);
+
+    expect(errorCallbackMock).toHaveBeenCalled();
+    expect(errorCallbackMock).toThrow('Invalid callback');
+
+    expect(callbackMock).toHaveBeenCalled();
+  });
 });
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
index 66d4ab43fd2d6dd73422f714ada6c8a897592d0e..9ff0bbce2e297e2012be88766bb3f07099dcd6c3 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
 import { CreatedOverlay, MapOverlay } from '@/types/models';
-import { ALLOWED_PLUGINS_EVENTS, LISTENER_NOT_FOUND } from './pluginsEventBus.constants';
+import { showToast } from '@/utils/showToast';
 import type {
   CenteredCoordinates,
   ClickedBioEntity,
@@ -8,11 +8,15 @@ import type {
   ClickedSurfaceOverlay,
   Events,
   EventsData,
+  PluginUnloaded,
   PluginsEventBusType,
   SearchData,
   ZoomChanged,
 } from './pluginsEventBus.types';
+import { ALLOWED_PLUGINS_EVENTS, LISTENER_NOT_FOUND } from './pluginsEventBus.constants';
+import { ERROR_INVALID_EVENT_TYPE, ERROR_PLUGIN_CRASH } from '../errorMessages';
 
+export function dispatchEvent(type: 'onPluginUnload', data: PluginUnloaded): void;
 export function dispatchEvent(type: 'onAddDataOverlay', createdOverlay: CreatedOverlay): void;
 export function dispatchEvent(type: 'onRemoveDataOverlay', overlayId: number): void;
 export function dispatchEvent(type: 'onShowOverlay', overlay: MapOverlay): void;
@@ -27,12 +31,21 @@ export function dispatchEvent(type: 'onPinIconClick', data: ClickedPinIcon): voi
 export function dispatchEvent(type: 'onSurfaceClick', data: ClickedSurfaceOverlay): void;
 export function dispatchEvent(type: 'onSearch', data: SearchData): void;
 export function dispatchEvent(type: Events, data: EventsData): void {
-  if (!ALLOWED_PLUGINS_EVENTS.includes(type)) throw new Error(`Invalid event type: ${type}`);
+  if (!ALLOWED_PLUGINS_EVENTS.includes(type)) throw new Error(ERROR_INVALID_EVENT_TYPE(type));
 
   // eslint-disable-next-line no-restricted-syntax, no-use-before-define
   for (const event of PluginsEventBus.events) {
     if (event.type === type) {
-      event.callback(data);
+      try {
+        event.callback(data);
+      } catch (error) {
+        showToast({
+          message: ERROR_PLUGIN_CRASH(event.pluginName),
+          type: 'error',
+        });
+        // eslint-disable-next-line no-console
+        console.error(error);
+      }
     }
   }
 }
@@ -40,9 +53,15 @@ export function dispatchEvent(type: Events, data: EventsData): void {
 export const PluginsEventBus: PluginsEventBusType = {
   events: [],
 
-  addListener: (hash: string, type: Events, callback: (data: unknown) => void) => {
+  addListener: (
+    hash: string,
+    pluginName: string,
+    type: Events,
+    callback: (data: unknown) => void,
+  ) => {
     PluginsEventBus.events.push({
       hash,
+      pluginName,
       type,
       callback,
     });
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
index 0fdec91300559d50089301f264031c2d069f5fc4..fa6d70e4801de397b941fdf088b811ae62c02fb6 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
@@ -24,7 +24,9 @@ export type SubmapEvents =
   | 'onPinIconClick';
 export type SearchEvents = 'onSearch';
 
-export type Events = OverlayEvents | BackgroundEvents | SubmapEvents | SearchEvents;
+export type PluginEvents = 'onPluginUnload';
+
+export type Events = OverlayEvents | BackgroundEvents | SubmapEvents | SearchEvents | PluginEvents;
 
 export type ZoomChanged = {
   zoom: number;
@@ -69,6 +71,10 @@ export type SearchDataChemicals = {
   results: Chemical[][];
 };
 
+export type PluginUnloaded = {
+  hash: string;
+};
+
 export type SearchData = SearchDataBioEntity | SearchDataDrugs | SearchDataChemicals;
 
 export type EventsData =
@@ -80,15 +86,22 @@ export type EventsData =
   | ClickedBioEntity
   | ClickedPinIcon
   | ClickedSurfaceOverlay
-  | SearchData;
+  | SearchData
+  | PluginUnloaded;
 
 export type PluginsEventBusType = {
   events: {
     hash: string;
+    pluginName: string;
     type: Events;
     callback: (data: unknown) => void;
   }[];
-  addListener: (hash: string, type: Events, callback: (data: unknown) => void) => void;
+  addListener: (
+    hash: string,
+    pluginName: string,
+    type: Events,
+    callback: (data: unknown) => void,
+  ) => void;
   removeListener: (hash: string, type: Events, callback: unknown) => void;
   removeAllListeners: (hash: string) => void;
   dispatchEvent: typeof dispatchEvent;
diff --git a/src/services/pluginsManager/pluginsManager.ts b/src/services/pluginsManager/pluginsManager.ts
index c0b7bd2b474b3c61cce3aa8fdfcd2de6d3cf2e54..bb42708664d32c5d9119619c242337d20839c7d6 100644
--- a/src/services/pluginsManager/pluginsManager.ts
+++ b/src/services/pluginsManager/pluginsManager.ts
@@ -2,22 +2,39 @@ import { PLUGINS_CONTENT_ELEMENT_ATTR_NAME, PLUGINS_CONTENT_ELEMENT_ID } from '@
 import { registerPlugin } from '@/redux/plugins/plugins.thunks';
 import { store } from '@/redux/store';
 import md5 from 'crypto-js/md5';
+import { bioEntitiesMethods } from './bioEntities';
 import { getModels } from './map/models/getModels';
 import { openMap } from './map/openMap';
-import { bioEntitiesMethods } from './bioEntities';
+import { getCenter } from './map/position/getCenter';
+import { setCenter } from './map/position/setCenter';
 import { triggerSearch } from './map/triggerSearch';
+import { getZoom } from './map/zoom/getZoom';
+import { setZoom } from './map/zoom/setZoom';
+import { getCurrentOverviewImage } from './overviewImage/getCurrentOverviewImage';
+import { getOverviewImages } from './overviewImage/getOverviewImages';
+import { hideOverviewImageModal } from './overviewImage/hideOverviewImageModal';
+import { selectOverviewImage } from './overviewImage/selectOverviewImage';
+import { showOverviewImageModal } from './overviewImage/showOverviewImageModal';
 import { PluginsEventBus } from './pluginsEventBus';
-import { getProjectId } from './project/data/getProjectId';
-import { getName } from './project/data/getName';
-import { getVersion } from './project/data/getVersion';
-import { getDisease } from './project/data/getDisease';
-import { getOrganism } from './project/data/getOrganism';
 import type { PluginsManagerType } from './pluginsManager.types';
 import { configurationMapper } from './pluginsManager.utils';
-
+import { getDataOverlays } from './map/overlays/getDataOverlays';
+import { getVisibleDataOverlays } from './map/overlays/getVisibleDataOverlays';
+import { showDataOverlay } from './map/overlays/showDataOverlay';
+import { hideDataOverlay } from './map/overlays/hideDataOverlay';
+import { removeDataOverlay } from './map/overlays/removeDataOverlay';
+import { addDataOverlay } from './map/overlays/addDataOverlay';
+import { getApiUrls } from './project/data/getApiUrls';
+import { getDisease } from './project/data/getDisease';
+import { getName } from './project/data/getName';
+import { getOrganism } from './project/data/getOrganism';
+import { getProjectId } from './project/data/getProjectId';
+import { getVersion } from './project/data/getVersion';
 import { getBounds } from './map/data/getBounds';
 import { fitBounds } from './map/fitBounds';
 import { getOpenMapId } from './map/getOpenMapId';
+import { setLegend } from './legend/setLegend';
+import { removeLegend } from './legend/removeLegend';
 
 export const PluginsManager: PluginsManagerType = {
   hashedPlugins: {},
@@ -45,6 +62,27 @@ export const PluginsManager: PluginsManagerType = {
         fitBounds,
         openMap,
         triggerSearch,
+        getZoom,
+        setZoom,
+        getCenter,
+        setCenter,
+      },
+      overviewImage: {
+        getCurrentOverviewImage,
+        getOverviewImages,
+        hideOverviewImageModal,
+        selectOverviewImage,
+        showOverviewImageModal,
+      },
+      overlays: {
+        data: {
+          getDataOverlays,
+          getVisibleDataOverlays,
+        },
+        showDataOverlay,
+        hideDataOverlay,
+        removeDataOverlay,
+        addDataOverlay,
       },
       project: {
         data: {
@@ -53,6 +91,7 @@ export const PluginsManager: PluginsManagerType = {
           getVersion,
           getDisease,
           getOrganism,
+          getApiUrls,
         },
       },
     };
@@ -91,10 +130,14 @@ export const PluginsManager: PluginsManagerType = {
     return {
       element,
       events: {
-        addListener: PluginsEventBus.addListener.bind(this, hash),
+        addListener: PluginsEventBus.addListener.bind(this, hash, pluginName),
         removeListener: PluginsEventBus.removeListener.bind(this, hash),
         removeAllListeners: PluginsEventBus.removeAllListeners.bind(this, hash),
       },
+      legend: {
+        setLegend: setLegend.bind(this, hash),
+        removeLegend: removeLegend.bind(this, hash),
+      },
     };
   },
   createAndGetPluginContent({ hash }) {
diff --git a/src/services/pluginsManager/project/data/getApiUrls.ts b/src/services/pluginsManager/project/data/getApiUrls.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd5bd97221a2ecb1f2a51db488746a3ae99e4d84
--- /dev/null
+++ b/src/services/pluginsManager/project/data/getApiUrls.ts
@@ -0,0 +1,11 @@
+import { BASE_API_URL, BASE_NEW_API_URL } from '@/constants';
+
+type ApiUrls = {
+  baseApiUrl: string;
+  baseNewApiUrl: string;
+};
+
+export const getApiUrls = (): ApiUrls => ({
+  baseApiUrl: BASE_API_URL,
+  baseNewApiUrl: BASE_NEW_API_URL,
+});
diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx
index 258f7a0901139e6d82f2cf9d38b91b0489f8344b..172101ad00188fee5177e6a74da2fa1e8a3cefa4 100644
--- a/src/shared/Icon/Icon.component.tsx
+++ b/src/shared/Icon/Icon.component.tsx
@@ -20,6 +20,7 @@ import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn';
 import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut';
 import { ReloadIcon } from './Icons/ReloadIcon';
 import { ThreeDotsIcon } from './Icons/ThreeDotsIcon';
+import { ClearIcon } from './Icons/ClearIcon';
 
 export interface IconProps {
   className?: string;
@@ -47,6 +48,7 @@ const icons: Record<IconTypes, IconComponentType> = {
   'magnifier-zoom-out': MaginfierZoomOutIcon,
   'three-dots': ThreeDotsIcon,
   reload: ReloadIcon,
+  clear: ClearIcon,
 } as const;
 
 export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => {
diff --git a/src/shared/Icon/Icons/ClearIcon.tsx b/src/shared/Icon/Icons/ClearIcon.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5b446bc5a3ce41d3549e0ab952e1e238ec2891bd
--- /dev/null
+++ b/src/shared/Icon/Icons/ClearIcon.tsx
@@ -0,0 +1,32 @@
+interface ClearIconProps {
+  className?: string;
+}
+
+export const ClearIcon = ({ className, ...rest }: ClearIconProps): JSX.Element => (
+  <svg
+    width="24"
+    height="24"
+    viewBox="0 0 24 24"
+    fill="none"
+    className={className}
+    xmlns="http://www.w3.org/2000/svg"
+    {...rest}
+  >
+    <g clipPath="url(#clip0_4431_10860)">
+      <path
+        d="M12 23.7279L5.63604 17.364C2.12132 13.8492 2.12132 8.15076 5.63604 4.63604C9.15076 1.12132 14.8492 1.12132 18.364 4.63604C21.8787 8.15076 21.8787 13.8492 18.364 17.364L12 23.7279ZM16.9497 15.9497C19.6834 13.2161 19.6834 8.78392 16.9497 6.05025C14.2161 3.31658 9.78392 3.31658 7.05025 6.05025C4.31658 8.78392 4.31658 13.2161 7.05025 15.9497L12 20.8995L16.9497 15.9497ZM12 13C10.8954 13 10 12.1046 10 11C10 9.89543 10.8954 9 12 9C13.1046 9 14 9.89543 14 11C14 12.1046 13.1046 13 12 13Z"
+        fill="currentColor"
+      />
+      <rect x="13" y="-2" width="12" height="12" rx="6" fill="white" />
+      <path
+        d="M18.9999 3.2952L21.4748 0.820312L22.1819 1.52742L19.707 4.0023L22.1819 6.47715L21.4748 7.18425L18.9999 4.7094L16.525 7.18425L15.8179 6.47715L18.2928 4.0023L15.8179 1.52742L16.525 0.820312L18.9999 3.2952Z"
+        fill="currentColor"
+      />
+    </g>
+    <defs>
+      <clipPath id="clip0_4431_10860">
+        <rect width="24" height="24" fill="white" />
+      </clipPath>
+    </defs>
+  </svg>
+);
diff --git a/src/types/error.ts b/src/types/error.ts
new file mode 100644
index 0000000000000000000000000000000000000000..48e6926ad764eeb1355d602ced1e5d149bcea3dc
--- /dev/null
+++ b/src/types/error.ts
@@ -0,0 +1,3 @@
+export interface PluginError {
+  message: string;
+}
diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts
index 1a7ec425177c339341635f76a994e836aacfd1c3..b33c7f6a41fc2d54e7928cc2d95172dc1005a136 100644
--- a/src/types/iconTypes.ts
+++ b/src/types/iconTypes.ts
@@ -18,6 +18,7 @@ export type IconTypes =
   | 'magnifier-zoom-out'
   | 'pin'
   | 'three-dots'
-  | 'reload';
+  | 'reload'
+  | 'clear';
 
 export type IconComponentType = ({ className }: { className: string }) => JSX.Element;
diff --git a/src/types/query.ts b/src/types/query.ts
index be3453f011b515a134cf5aff62e1549ae31553c1..e41e2520df7ff6550b80c93d0749a43c73fbcaa2 100644
--- a/src/types/query.ts
+++ b/src/types/query.ts
@@ -1,6 +1,7 @@
 import { Point } from './map';
 
 export interface QueryData {
+  id?: string;
   searchValue?: string[];
   perfectMatch: boolean;
   modelId?: number;
@@ -11,6 +12,7 @@ export interface QueryData {
 }
 
 export interface QueryDataParams {
+  id?: string;
   searchValue?: string;
   perfectMatch: boolean;
   modelId?: number;
@@ -23,6 +25,7 @@ export interface QueryDataParams {
 }
 
 export interface QueryDataRouterParams {
+  id?: string;
   searchValue?: string;
   perfectMatch?: string;
   modelId?: string;
diff --git a/src/utils/parseQueryToTypes.ts b/src/utils/parseQueryToTypes.ts
index f04abadfedebada9e8058ffa3d4eae08b9ffc731..5aebfe4aab60a8069093b693861d08f2460eee17 100644
--- a/src/utils/parseQueryToTypes.ts
+++ b/src/utils/parseQueryToTypes.ts
@@ -1,6 +1,7 @@
 import { QueryData, QueryDataRouterParams } from '@/types/query';
 
 export const parseQueryToTypes = (query: QueryDataRouterParams): QueryData => ({
+  id: query.id,
   searchValue: query.searchValue?.split(';'),
   perfectMatch: query?.perfectMatch === 'true' || false,
   modelId: Number(query.modelId) || undefined,
diff --git a/src/utils/query-manager/useReduxBusQueryManager.test.ts b/src/utils/query-manager/useReduxBusQueryManager.test.ts
index c77f8f3fbccbb56fdf3ace53f190b29b6ca62598..adecf2597242f2a690362fdfb3db7be8b0d7c332 100644
--- a/src/utils/query-manager/useReduxBusQueryManager.test.ts
+++ b/src/utils/query-manager/useReduxBusQueryManager.test.ts
@@ -48,6 +48,7 @@ describe('useReduxBusQueryManager - util', () => {
       project: {
         ...loadedDataMock,
         data: undefined,
+        projectId: '',
       },
       map: {
         ...loadedDataMock,