diff --git a/docs/plugins/bounds.md b/docs/plugins/bounds.md
new file mode 100644
index 0000000000000000000000000000000000000000..a9863e90f417737c0f37cbf9799ba3d9e4f0cf2f
--- /dev/null
+++ b/docs/plugins/bounds.md
@@ -0,0 +1,43 @@
+### Bounds
+
+#### Get Bounds
+
+To get bounds of the current active map, plugins can use the `getBounds` method defined in `window.minerva.map.data` object available globally. It returns object with properties x1, y1, x2, y2
+
+- x1, y1 - top left corner coordinates
+- x2, y2 - right bottom corner coordinates
+
+Example of returned object:
+
+```javascript
+{
+  x1: 12853,
+  y1: 4201,
+  x2: 23327,
+  y2: 9575
+}
+```
+
+##### Example of getBounds method usage:
+
+```javascript
+window.minerva.map.data.getBounds();
+```
+
+#### Fit bounds
+
+To zoom in the map in a way that rectangle defined by coordinates is visible, plugins can use the `fitBounds` method defined in `window.minerva.map` object available globally. This method takes one argument: object with properties x1, y1, x2, y2.
+
+- x1, y1 - top left corner coordinates
+- x2, y2 - right bottom corner coordinates
+
+##### Example of fitBounds method usage:
+
+```javascript
+window.minerva.map.fitBounds({
+  x1: 14057.166666666668,
+  y1: 6805.337365980873,
+  x2: 14057.166666666668,
+  y2: 6805.337365980873,
+});
+```
diff --git a/docs/plugins/errors.md b/docs/plugins/errors.md
index 08d08e23e7c71c881d0728d1fbc73d6c15dea2bc..fd7d29c75311eb1ca2028e60e570e57e7955e371 100644
--- a/docs/plugins/errors.md
+++ b/docs/plugins/errors.md
@@ -4,13 +4,15 @@
 
 - **Map with provided id does not exist**: This error occurs when the provided map id does not correspond to any existing map.
 
+- **Unable to retrieve the id of the active map: the modelId is not a number**: This error occurs when the modelId parameter provided from store to retrieve the id of the active map is not a number.
+
 ## Search Errors
 
 - **Invalid query type. The query should be of string type**: This error occurs when the query parameter is not of string type.
 
 - **Invalid coordinates type or values**: This error occurs when the coordinates parameter is missing keys, or its values are not of number type.
 
-- **Invalid model id type. The model should be of number type**: This error occurs when the modelId parameter is not of number type.
+- **Invalid model id type. The model id should be a number**: This error occurs when the modelId parameter is not of number type.
 
 ## Project Errors
 
diff --git a/docs/plugins/events.md b/docs/plugins/events.md
index 957b28a29c0bbde50cd8fc105001e55976bbe822..63072dc23baf38719a6e45a4362f1c1d1db1b263 100644
--- a/docs/plugins/events.md
+++ b/docs/plugins/events.md
@@ -201,6 +201,34 @@ To listen for specific events, plugins can use the `addListener` method in `even
 }
 ```
 
+- onPinIconClick - triggered when someone clicks on a pin icon; the element to which the pin is attached is passed as an argument. Example argument:
+
+```javascript
+{
+  "id": 40072,
+}
+```
+
+```javascript
+{
+  "id": "b0a478ad-7e7a-47f5-8130-e96cbeaa0cfe", // marker pin
+}
+```
+
+- onSurfaceClick - triggered when someone clicks on a overlay surface; the element to which the pin is attached is passed as an argument. Example argument:
+
+```javascript
+{
+  "id": 18,
+}
+```
+
+```javascript
+{
+  "id": "a3a5305f-acfa-47ff-bf77-a26d017c6eb3", // surface marker overlay
+}
+```
+
 - onSubmapOpen - triggered when submap opens; the submap identifier is passed as an argument. Example argument:
 
 ```
diff --git a/docs/plugins/submaps.md b/docs/plugins/submaps.md
index 54c6a340857d14e1e45fc1cdbd7d70c2d11c7f62..aab6dc23ce8b15e9b7d06273f188104a2dc4282a 100644
--- a/docs/plugins/submaps.md
+++ b/docs/plugins/submaps.md
@@ -1,5 +1,15 @@
 ### Submaps
 
+#### Get current open map id
+
+To get current open map id, plugins can use the `getOpenMapId` method defined in `window.minerva.map.data` object available globally. It returns id of current open map.
+
+##### Example of getOpenMapId method usage:
+
+```javascript
+window.minerva.map.data.getOpenMapId();
+```
+
 #### Get Models
 
 To get data about all available submaps, plugins can use the `getModels` method defined in `window.minerva.map.data`. This method returns array with data about all submaps.
diff --git a/index.d.ts b/index.d.ts
index bbde76a542bd803d1396d3a25d40df60ef9aa927..fcac0d8b269f59bfa4610ff8051b1266636ba44f 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,3 +1,9 @@
+import { getBounds } from '@/services/pluginsManager/map/data/getBounds';
+import { fitBounds } from '@/services/pluginsManager/map/fitBounds';
+import { getOpenMapId } from '@/services/pluginsManager/map/getOpenMapId';
+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 { openMap } from '@/services/pluginsManager/map/openMap';
 import { getCenter } from '@/services/pluginsManager/map/position/getCenter';
@@ -39,8 +45,11 @@ declare global {
       };
       map: {
         data: {
+          getBounds: typeof getBounds;
+          getOpenMapId: typeof getOpenMapId;
           getModels: typeof getModels;
         };
+        fitBounds: typeof fitBounds;
         openMap: typeof openMap;
         triggerSearch: typeof triggerSearch;
         getZoom: typeof getZoom;
diff --git a/package-lock.json b/package-lock.json
index 016ad6996bbef60abdeef8fe404277e991e7dca9..18eaa14c5846910e2f3e54e7f06fffe2987b4f93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
         "crypto-js": "^4.2.0",
         "downshift": "^8.2.3",
         "eslint-config-next": "13.4.19",
+        "is-uuid": "^1.0.2",
         "molart": "github:davidhoksza/MolArt",
         "next": "13.4.19",
         "ol": "^8.1.0",
@@ -34,6 +35,7 @@
         "react-dom": "18.2.0",
         "react-dropzone": "^14.2.3",
         "react-redux": "^8.1.2",
+        "sonner": "^1.4.3",
         "tailwind-merge": "^1.14.0",
         "tailwindcss": "3.3.3",
         "ts-deepmerge": "^6.2.0",
@@ -49,6 +51,7 @@
         "@testing-library/react": "^14.0.0",
         "@testing-library/user-event": "^14.5.2",
         "@types/crypto-js": "^4.2.2",
+        "@types/is-uuid": "^1.0.2",
         "@types/jest": "^29.5.5",
         "@types/react-redux": "^7.1.26",
         "@types/redux-mock-store": "^1.0.6",
@@ -2319,6 +2322,12 @@
         "hoist-non-react-statics": "^3.3.0"
       }
     },
+    "node_modules/@types/is-uuid": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@types/is-uuid/-/is-uuid-1.0.2.tgz",
+      "integrity": "sha512-S+gWwUEApOjGCCO5LQrft4kciGWatvB0LyiyWTXSlDkclZBr6glSgstET573GsC5QPFdw2NeOw2PHOOMuTDFSQ==",
+      "dev": true
+    },
     "node_modules/@types/istanbul-lib-coverage": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -7968,6 +7977,11 @@
       "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==",
       "dev": true
     },
+    "node_modules/is-uuid": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-uuid/-/is-uuid-1.0.2.tgz",
+      "integrity": "sha512-tCByphFcJgf2qmiMo5hMCgNAquNSagOetVetDvBXswGkNfoyEMvGH1yDlF8cbZbKnbVBr4Y5/rlpMz9umxyBkQ=="
+    },
     "node_modules/is-weakmap": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
@@ -12397,6 +12411,15 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/sonner": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.4.3.tgz",
+      "integrity": "sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw==",
+      "peerDependencies": {
+        "react": "^18.0.0",
+        "react-dom": "^18.0.0"
+      }
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -15724,6 +15747,12 @@
         "hoist-non-react-statics": "^3.3.0"
       }
     },
+    "@types/is-uuid": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@types/is-uuid/-/is-uuid-1.0.2.tgz",
+      "integrity": "sha512-S+gWwUEApOjGCCO5LQrft4kciGWatvB0LyiyWTXSlDkclZBr6glSgstET573GsC5QPFdw2NeOw2PHOOMuTDFSQ==",
+      "dev": true
+    },
     "@types/istanbul-lib-coverage": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -19786,6 +19815,11 @@
       "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==",
       "dev": true
     },
+    "is-uuid": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-uuid/-/is-uuid-1.0.2.tgz",
+      "integrity": "sha512-tCByphFcJgf2qmiMo5hMCgNAquNSagOetVetDvBXswGkNfoyEMvGH1yDlF8cbZbKnbVBr4Y5/rlpMz9umxyBkQ=="
+    },
     "is-weakmap": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
@@ -22939,6 +22973,12 @@
         }
       }
     },
+    "sonner": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.4.3.tgz",
+      "integrity": "sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw==",
+      "requires": {}
+    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
diff --git a/package.json b/package.json
index f3a8ec09f2e124b622df2947627ea0a7576d99a3..54dfdef6f81b5365f64141c7b74a92f419bdae94 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
     "crypto-js": "^4.2.0",
     "downshift": "^8.2.3",
     "eslint-config-next": "13.4.19",
+    "is-uuid": "^1.0.2",
     "molart": "github:davidhoksza/MolArt",
     "next": "13.4.19",
     "ol": "^8.1.0",
@@ -48,6 +49,7 @@
     "react-dom": "18.2.0",
     "react-dropzone": "^14.2.3",
     "react-redux": "^8.1.2",
+    "sonner": "^1.4.3",
     "tailwind-merge": "^1.14.0",
     "tailwindcss": "3.3.3",
     "ts-deepmerge": "^6.2.0",
@@ -63,6 +65,7 @@
     "@testing-library/react": "^14.0.0",
     "@testing-library/user-event": "^14.5.2",
     "@types/crypto-js": "^4.2.2",
+    "@types/is-uuid": "^1.0.2",
     "@types/jest": "^29.5.5",
     "@types/react-redux": "^7.1.26",
     "@types/redux-mock-store": "^1.0.6",
diff --git a/src/components/AppWrapper/AppWrapper.component.tsx b/src/components/AppWrapper/AppWrapper.component.tsx
index 3b59e82bec77dc93528d14168014000abd095242..5014ee69cbf7e8d0baed57003a58d81dfe049335 100644
--- a/src/components/AppWrapper/AppWrapper.component.tsx
+++ b/src/components/AppWrapper/AppWrapper.component.tsx
@@ -2,6 +2,7 @@ import { store } from '@/redux/store';
 import { MapInstanceProvider } from '@/utils/context/mapInstanceContext';
 import { ReactNode } from 'react';
 import { Provider } from 'react-redux';
+import { Toaster } from 'sonner';
 
 interface AppWrapperProps {
   children: ReactNode;
@@ -9,6 +10,17 @@ interface AppWrapperProps {
 
 export const AppWrapper = ({ children }: AppWrapperProps): JSX.Element => (
   <MapInstanceProvider>
-    <Provider store={store}>{children}</Provider>
+    <Provider store={store}>
+      <>
+        <Toaster
+          position="top-center"
+          visibleToasts={1}
+          style={{
+            width: '700px',
+          }}
+        />
+        {children}
+      </>
+    </Provider>
   </MapInstanceProvider>
 );
diff --git a/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx b/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx
index f0934d79f6f54d7ea9008e43f5e84ba7f5faac04..990e4fbdb080ffe640223359edc15521dcfb5053 100644
--- a/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx
+++ b/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx
@@ -11,10 +11,13 @@ import { HttpStatusCode } from 'axios';
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { act } from 'react-dom/test-utils';
 import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { showToast } from '@/utils/showToast';
 import { Modal } from '../Modal.component';
 
 const mockedAxiosClient = mockNetworkResponse();
 
+jest.mock('../../../../utils/showToast');
+
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
   const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
 
@@ -31,6 +34,9 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St
 };
 
 describe('EditOverlayModal - component', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
   it('should render modal with correct data', () => {
     renderComponent({
       modal: {
@@ -101,6 +107,39 @@ describe('EditOverlayModal - component', () => {
     expect(loading).toBe('succeeded');
     expect(removeButton).not.toBeVisible();
   });
+  it('should show toast after successful removing user overlay', async () => {
+    renderComponent({
+      user: {
+        authenticated: true,
+        loading: 'succeeded',
+        error: DEFAULT_ERROR,
+        login: 'test',
+      },
+      modal: {
+        isOpen: true,
+        modalTitle: overlayFixture.name,
+        modalName: 'edit-overlay',
+        editOverlayState: overlayFixture,
+        molArtState: {},
+        overviewImagesState: {},
+      },
+      overlays: OVERLAYS_INITIAL_STATE_MOCK,
+    });
+    mockedAxiosClient
+      .onDelete(apiPath.removeOverlay(overlayFixture.idObject))
+      .reply(HttpStatusCode.Ok, {});
+
+    const removeButton = screen.getByTestId('remove-button');
+    expect(removeButton).toBeVisible();
+    await act(() => {
+      removeButton.click();
+    });
+
+    expect(showToast).toHaveBeenCalledWith({
+      message: 'User overlay removed successfully',
+      type: 'success',
+    });
+  });
   it('should handle save edited user overlay', async () => {
     const { store } = renderComponent({
       user: {
@@ -133,6 +172,39 @@ describe('EditOverlayModal - component', () => {
     expect(loading).toBe('succeeded');
     expect(saveButton).not.toBeVisible();
   });
+  it('should show toast after successful editing user overlay', async () => {
+    renderComponent({
+      user: {
+        authenticated: true,
+        loading: 'succeeded',
+        error: DEFAULT_ERROR,
+        login: 'test',
+      },
+      modal: {
+        isOpen: true,
+        modalTitle: overlayFixture.name,
+        modalName: 'edit-overlay',
+        editOverlayState: overlayFixture,
+        molArtState: {},
+        overviewImagesState: {},
+      },
+      overlays: OVERLAYS_INITIAL_STATE_MOCK,
+    });
+    mockedAxiosClient
+      .onPatch(apiPath.updateOverlay(overlayFixture.idObject))
+      .reply(HttpStatusCode.Ok, overlayFixture);
+
+    const saveButton = screen.getByTestId('save-button');
+    expect(saveButton).toBeVisible();
+    await act(() => {
+      saveButton.click();
+    });
+
+    expect(showToast).toHaveBeenCalledWith({
+      message: 'User overlay updated successfully',
+      type: 'success',
+    });
+  });
 
   it('should handle cancel edit user overlay', async () => {
     const { store } = renderComponent({
diff --git a/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.ts b/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.ts
index 63a98d5e96cacc5fb1d6c5f34f0a5ba94a5f2ec2..a8d02776d6eb4320f841e7dbea919cffb3134a37 100644
--- a/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.ts
+++ b/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.ts
@@ -76,10 +76,10 @@ export const useEditOverlay = (): UseEditOverlayReturn => {
   };
 
   const handleSaveEditedOverlay = async (): Promise<void> => {
-    if (!currentEditedOverlay || !name || !description || !login) return;
+    if (!currentEditedOverlay || !name || !login) return;
     await handleUpdateOverlay({
       editedOverlay: currentEditedOverlay,
-      overlayDescription: description,
+      overlayDescription: description || '',
       overlayName: name,
     });
 
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.constants.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e628e141901704881c7243aea0324512518107ad
--- /dev/null
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.constants.ts
@@ -0,0 +1 @@
+export const PLUGIN_LOADING_ERROR_PREFIX = 'Failed to load plugin';
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 0ea19da098cf1edb70be6367ba25b12d06b7e992..f54c8f5bd2c0bf9ce531f8482d7c0cee13928338 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.test.ts
@@ -7,10 +7,12 @@ import { renderHook, waitFor } from '@testing-library/react';
 import axios, { HttpStatusCode } from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { act } from 'react-dom/test-utils';
+import { showToast } from '@/utils/showToast';
 import { useLoadPlugin } from './useLoadPlugin';
 
 const mockedAxiosClient = new MockAdapter(axios);
 jest.mock('../../../../../../services/pluginsManager/pluginsManager');
+jest.mock('../../../../../../utils/showToast');
 
 describe('useLoadPlugin', () => {
   afterEach(() => {
@@ -86,4 +88,31 @@ describe('useLoadPlugin', () => {
       });
     });
   });
+  it('should show toast if plugin failed to load', async () => {
+    const hash = 'pluginHash';
+    const pluginUrl = 'http://example.com/plugin.js';
+
+    const { Wrapper } = getReduxStoreWithActionsListener(INITIAL_STORE_STATE_MOCK);
+
+    mockedAxiosClient.onGet(pluginUrl).reply(HttpStatusCode.Forbidden, null);
+
+    const {
+      result: {
+        current: { togglePlugin },
+      },
+    } = renderHook(() => useLoadPlugin({ hash, pluginUrl }), {
+      wrapper: Wrapper,
+    });
+
+    togglePlugin();
+
+    await waitFor(() => {
+      expect(showToast).toHaveBeenCalled();
+      expect(showToast).toHaveBeenCalledWith({
+        message:
+          "Failed to load plugin: Access Forbidden! You don't have permission to access this resource.",
+        type: 'error',
+      });
+    });
+  });
 });
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
index 454347303ae79a4683debd742c0108152eef805b..ebce11ec92f224772b10aa40c48e33b1cc9051b7 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
@@ -7,7 +7,10 @@ import {
 } from '@/redux/plugins/plugins.selectors';
 import { removePlugin } from '@/redux/plugins/plugins.slice';
 import { PluginsManager } from '@/services/pluginsManager';
+import { showToast } from '@/utils/showToast';
 import axios from 'axios';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { PLUGIN_LOADING_ERROR_PREFIX } from '../../AvailablePluginsDrawer.constants';
 
 type UseLoadPluginReturnType = {
   togglePlugin: () => void;
@@ -37,21 +40,29 @@ export const useLoadPlugin = ({
   const dispatch = useAppDispatch();
 
   const handleLoadPlugin = async (): Promise<void> => {
-    const response = await axios(pluginUrl);
-    const pluginScript = response.data;
+    try {
+      const response = await axios(pluginUrl);
+      const pluginScript = response.data;
 
-    /* eslint-disable no-new-func */
-    const loadPlugin = new Function(pluginScript);
+      /* eslint-disable no-new-func */
+      const loadPlugin = new Function(pluginScript);
 
-    PluginsManager.setHashedPlugin({
-      pluginUrl,
-      pluginScript,
-    });
+      PluginsManager.setHashedPlugin({
+        pluginUrl,
+        pluginScript,
+      });
 
-    loadPlugin();
+      loadPlugin();
 
-    if (onPluginLoaded) {
-      onPluginLoaded();
+      if (onPluginLoaded) {
+        onPluginLoaded();
+      }
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: PLUGIN_LOADING_ERROR_PREFIX,
+      });
+      showToast({ type: 'error', message: errorMessage });
     }
   };
 
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx
index b74995ed19be857c1ae509c14fd7f27c32bbfda5..1b347d6eec569ab743acd711450aa4479f0cb808 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/LoadPluginFromUrl.component.test.tsx
@@ -9,15 +9,18 @@ import { StoreType } from '@/redux/store';
 import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import axios, { HttpStatusCode } from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { act } from 'react-dom/test-utils';
+import { showToast } from '@/utils/showToast';
 import { LoadPluginFromUrl } from './LoadPluginFromUrl.component';
 
 const mockedAxiosApiClient = mockNetworkResponse();
 const mockedAxiosClient = new MockAdapter(axios);
 
+jest.mock('../../../../../utils/showToast');
+
 const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
   const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
   return (
@@ -163,5 +166,65 @@ describe('LoadPluginFromUrl - component', () => {
       const button = screen.getByTestId('load-plugin-button');
       expect(button).toBeDisabled();
     });
+    it('should show toast if plugin failed to load', async () => {
+      const pluginUrl = 'http://example.com/plugin.js';
+      mockedAxiosClient.onGet(pluginUrl).reply(HttpStatusCode.Unauthorized, null);
+
+      global.URL.canParse = jest.fn().mockReturnValue(true);
+
+      renderComponent();
+      const input = screen.getByTestId('load-plugin-input-url');
+      expect(input).toBeVisible();
+
+      act(() => {
+        fireEvent.change(input, { target: { value: pluginUrl } });
+      });
+
+      const button = screen.getByTestId('load-plugin-button');
+
+      act(() => {
+        button.click();
+      });
+
+      await waitFor(() => {
+        expect(showToast).toHaveBeenCalled();
+        expect(showToast).toHaveBeenCalledWith({
+          message:
+            "Failed to load plugin: You're not authorized to access this resource. Please log in or check your credentials.",
+          type: 'error',
+        });
+      });
+    });
+    it('should set plugin active tab in drawer as loaded plugin', async () => {
+      const pluginUrl = 'http://example.com/plugin.js';
+      const pluginScript = `function init() {} init()`;
+      mockedAxiosClient.onGet(pluginUrl).reply(HttpStatusCode.Ok, pluginScript);
+
+      global.URL.canParse = jest.fn().mockReturnValue(true);
+
+      const { store } = renderComponent();
+
+      const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+      const input = screen.getByTestId('load-plugin-input-url');
+      expect(input).toBeVisible();
+
+      act(() => {
+        fireEvent.change(input, { target: { value: pluginUrl } });
+      });
+
+      const button = screen.getByTestId('load-plugin-button');
+
+      act(() => {
+        button.click();
+      });
+
+      await waitFor(() => {
+        expect(dispatchSpy).toHaveBeenCalledWith({
+          payload: 'e008fb2ceb97e3d6139ffe38a1b39d5d',
+          type: 'plugins/setCurrentDrawerPluginHash',
+        });
+      });
+    });
   });
 });
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
index 123e220ccc3e05c23d54bbcf62e8d9c607acb373..f95bdf7e4beaa78a655090c648652a8ff20fa388 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPluginFromUrl/hooks/useLoadPluginFromUrl.ts
@@ -1,8 +1,13 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { activePluginsDataSelector } from '@/redux/plugins/plugins.selectors';
+import { setCurrentDrawerPluginHash } from '@/redux/plugins/plugins.slice';
 import { PluginsManager } from '@/services/pluginsManager';
+import { showToast } from '@/utils/showToast';
 import axios from 'axios';
 import { ChangeEvent, useMemo, useState } from 'react';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { PLUGIN_LOADING_ERROR_PREFIX } from '../../AvailablePluginsDrawer.constants';
 
 type UseLoadPluginReturnType = {
   handleChangePluginUrl: (event: ChangeEvent<HTMLInputElement>) => void;
@@ -15,12 +20,17 @@ export const useLoadPluginFromUrl = (): UseLoadPluginReturnType => {
   const [pluginUrl, setPluginUrl] = useState('');
   const [isLoading, setIsLoading] = useState(false);
   const activePlugins = useAppSelector(activePluginsDataSelector);
+  const dispatch = useAppDispatch();
 
   const isPending = useMemo(
     () => !pluginUrl || isLoading || !URL.canParse(pluginUrl),
     [pluginUrl, isLoading],
   );
 
+  const handleSetCurrentDrawerPluginHash = (hash: string): void => {
+    dispatch(setCurrentDrawerPluginHash(hash));
+  };
+
   const handleLoadPlugin = async (): Promise<void> => {
     try {
       setIsLoading(true);
@@ -40,6 +50,14 @@ export const useLoadPluginFromUrl = (): UseLoadPluginReturnType => {
       }
 
       setPluginUrl('');
+
+      handleSetCurrentDrawerPluginHash(hash);
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: PLUGIN_LOADING_ERROR_PREFIX });
+      showToast({
+        type: 'error',
+        message: errorMessage,
+      });
     } finally {
       setIsLoading(false);
     }
diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx
index 2ee7eb5b5a6eb3e445c572fe5fa593062bd482a2..03c3f49587c5fb3ea0f8a6fb4bfd58549f77ee26 100644
--- a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx
+++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx
@@ -1,7 +1,7 @@
 import { ZERO } from '@/constants/common';
 import {
-  searchedFromMapBioEntityElement,
-  searchedFromMapBioEntityElementRelatedSubmapSelector,
+  currentDrawerBioEntityRelatedSubmapSelector,
+  currentDrawerBioEntitySelector,
 } from '@/redux/bioEntity/bioEntity.selectors';
 import {
   getChemicalsForBioEntityDrawerTarget,
@@ -22,8 +22,8 @@ const TARGET_PREFIX: ElementSearchResultType = `ALIAS`;
 
 export const BioEntityDrawer = (): React.ReactNode => {
   const dispatch = useAppDispatch();
-  const bioEntityData = useAppSelector(searchedFromMapBioEntityElement);
-  const relatedSubmap = useAppSelector(searchedFromMapBioEntityElementRelatedSubmapSelector);
+  const bioEntityData = useAppSelector(currentDrawerBioEntitySelector);
+  const relatedSubmap = useAppSelector(currentDrawerBioEntityRelatedSubmapSelector);
   const currentTargetId = bioEntityData?.id ? `${TARGET_PREFIX}:${bioEntityData.id}` : '';
 
   const fetchChemicalsForTarget = (): void => {
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 474934d8f7bf9805432330ca0361a9ee2a7572e3..b9966147dd4a91994d39bf8b39a03f66f9e6224c 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
@@ -21,8 +21,11 @@ import {
 } from '@/models/fixtures/overlaysFixture';
 import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
 import { DEFAULT_ERROR } from '@/constants/errors';
+import { showToast } from '@/utils/showToast';
 import { UserOverlayForm } from './UserOverlayForm.component';
 
+jest.mock('../../../../../utils/showToast');
+
 const mockedAxiosClient = mockNetworkResponse();
 
 const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
@@ -57,6 +60,9 @@ const renderComponentWithActionListener = (
 };
 
 describe('UserOverlayForm - Component', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
   it('renders the UserOverlayForm component', () => {
     renderComponent();
 
@@ -249,4 +255,58 @@ describe('UserOverlayForm - Component', () => {
       expect(refetchedUserOverlays).toEqual(overlaysFixture);
     });
   });
+  it('should show toast after successful creating user overlays', 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);
+
+    mockedAxiosClient
+      .onGet(apiPath.getAllUserOverlaysByCreatorQuery({ creator: 'test', publicOverlay: false }))
+      .reply(HttpStatusCode.Ok, overlaysFixture);
+
+    const { store } = renderComponent({
+      user: {
+        authenticated: true,
+        error: DEFAULT_ERROR,
+        login: 'test',
+        loading: 'succeeded',
+      },
+      project: {
+        data: projectFixture,
+        loading: 'succeeded',
+        error: DEFAULT_ERROR,
+      },
+      overlays: OVERLAYS_INITIAL_STATE_MOCK,
+    });
+
+    const userOverlays = store.getState().overlays.userOverlays.data;
+
+    expect(userOverlays).toEqual([]);
+
+    fireEvent.change(screen.getByTestId('overlay-name'), { target: { value: 'Test Overlay' } });
+    fireEvent.change(screen.getByTestId('overlay-description'), {
+      target: { value: 'Description Overlay' },
+    });
+    fireEvent.change(screen.getByTestId('overlay-elements-list'), {
+      target: { value: 'Elements List Overlay' },
+    });
+
+    fireEvent.click(screen.getByLabelText('upload overlay'));
+
+    await waitFor(() => {
+      expect(showToast).toHaveBeenCalled();
+      expect(showToast).toHaveBeenCalledWith({
+        message: 'User overlay added successfully',
+        type: 'success',
+      });
+    });
+  });
 });
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.tsx
index 483bf03b5cd68d898b3b539a97f204a24796365e..47f2e4b205195d6f35e457fb5920f79e327c516a 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.tsx
@@ -1,11 +1,13 @@
-import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { Icon } from '@/shared/Icon';
+import { LOCATION_BTN_ID } from '@/components/Map/MapAdditionalActions/MappAdditionalActions.constants';
+import { ZERO } from '@/constants/common';
 import { displayBioEntitiesList } from '@/redux/drawer/drawer.slice';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { BioEntityContent } from '@/types/models';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { mapModelIdSelector, mapOpenedMapsSelector } from '@/redux/map/map.selectors';
 import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { Icon } from '@/shared/Icon';
+import { BioEntityContent } from '@/types/models';
 
 export interface BioEntitiesSubmapItemProps {
   mapName: string;
@@ -42,6 +44,11 @@ export const BioEntitiesSubmapItem = ({
   const onSubmapClick = (): void => {
     openSubmap();
     dispatch(displayBioEntitiesList(bioEntities));
+
+    const locationButton = document.querySelector<HTMLButtonElement>(`#${LOCATION_BTN_ID}`);
+    if (locationButton) {
+      setTimeout(() => locationButton?.click(), ZERO);
+    }
   };
 
   return (
diff --git a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
index f65f5b605510ad4afb566e763c886fddbc9b1225..06bd09feed567c3515c7d3f305bc8eecb9426e78 100644
--- a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
+++ b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
@@ -54,7 +54,7 @@ const renderComponentWithMapInstance = (initialStore?: InitialStoreState): { sto
   const { Wrapper, store } = getReduxWrapperWithStore(initialStore, {
     mapInstanceContextValue: {
       mapInstance,
-      setMapInstance: () => {},
+      handleSetMapInstance: () => {},
     },
   });
 
diff --git a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.tsx b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.tsx
index 96f3870a818b1b86b7f482c47b9cccafc7351982..9faad208a93b93275a8b0b3f780cc62494ac2571 100644
--- a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.tsx
+++ b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.tsx
@@ -1,5 +1,6 @@
 import { Icon } from '@/shared/Icon';
 import { twMerge } from 'tailwind-merge';
+import { LOCATION_BTN_ID } from './MappAdditionalActions.constants';
 import { useAddtionalActions } from './utils/useAdditionalActions';
 
 export const MapAdditionalActions = (): JSX.Element => {
@@ -18,6 +19,7 @@ export const MapAdditionalActions = (): JSX.Element => {
         onClick={zoomInToBioEntities}
         data-testid="location-button"
         title="Center map"
+        id={LOCATION_BTN_ID}
       >
         <Icon className="h-[28px] w-[28px]" name="location" />
       </button>
diff --git a/src/components/Map/MapAdditionalActions/MappAdditionalActions.constants.ts b/src/components/Map/MapAdditionalActions/MappAdditionalActions.constants.ts
index 0803794471425c623acb0dd038ef4f4389e83898..b56bf65d7cfda35cf8d51acc091e5aa6650c5fe4 100644
--- a/src/components/Map/MapAdditionalActions/MappAdditionalActions.constants.ts
+++ b/src/components/Map/MapAdditionalActions/MappAdditionalActions.constants.ts
@@ -1,3 +1,5 @@
 export const MAP_ZOOM_IN_DELTA = 1;
 
 export const MAP_ZOOM_OUT_DELTA = -1;
+
+export const LOCATION_BTN_ID = 'location-button';
diff --git a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
index f711b392c959c975ed207dccd23b09365166ed6b..898b10e0424587a0420f66981339a2940e648c13 100644
--- a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
+++ b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
@@ -1,13 +1,13 @@
 /* eslint-disable no-magic-numbers */
 import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { modelsFixture } from '@/models/fixtures/modelsFixture';
 import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
+import { initialMapDataFixture } from '@/redux/map/map.fixtures';
 import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
 import Map from 'ol/Map';
-import { initialMapDataFixture } from '@/redux/map/map.fixtures';
-import { modelsFixture } from '@/models/fixtures/modelsFixture';
 import { useAddtionalActions } from './useAdditionalActions';
 import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
 
@@ -99,7 +99,7 @@ describe('useAddtionalActions - hook', () => {
           {
             mapInstanceContextValue: {
               mapInstance,
-              setMapInstance: () => {},
+              handleSetMapInstance: () => {},
             },
           },
         );
@@ -185,7 +185,7 @@ describe('useAddtionalActions - hook', () => {
         const position = store.getState().map?.data.position;
         expect(position?.last).toEqual(MAP_CONFIG.position);
         expect(actions[0]).toEqual({
-          payload: { x: 1750, y: 1000, z: 5 },
+          payload: { x: 1750, y: 1000, z: 4 },
           type: 'map/setMapPosition',
         });
       });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
index 121bfe2bb2b3c25f79ecb2f0dd7809a4378ace40..a7daf8437d747dce8f28e45f30268d6b8a3e3505 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
@@ -1,5 +1,18 @@
-import Polygon, { fromExtent } from 'ol/geom/Polygon';
+import { FEATURE_TYPE } from '@/constants/features';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import isUUID from 'is-uuid';
 import Feature from 'ol/Feature';
+import Polygon, { fromExtent } from 'ol/geom/Polygon';
+
+export const createFeatureFromExtent = (
+  [xMin, yMin, xMax, yMax]: number[],
+  entityId: OverlayBioEntityRender['id'],
+): Feature<Polygon> => {
+  const isMarker = isUUID.anyNonNil(`${entityId}`);
 
-export const createFeatureFromExtent = ([xMin, yMin, xMax, yMax]: number[]): Feature<Polygon> =>
-  new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) });
+  return new Feature({
+    geometry: fromExtent([xMin, yMin, xMax, yMax]),
+    id: entityId,
+    type: isMarker ? FEATURE_TYPE.SURFACE_MARKER : FEATURE_TYPE.SURFACE_OVERLAY,
+  });
+};
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts
index 8ee122219c608c7e6bedd00e1fd1e269e54255b1..4c94444ea89c6cdb72ce6c8bb3e4c292c8dbfc5c 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts
@@ -1,3 +1,4 @@
+import { FEATURE_TYPE } from '@/constants/features';
 import { createOverlayGeometryFeature } from './createOverlayGeometryFeature';
 
 describe('createOverlayGeometryFeature', () => {
@@ -7,8 +8,13 @@ describe('createOverlayGeometryFeature', () => {
     const xMax = 10;
     const yMax = 10;
     const colorHexString = '#FF0000';
+    const entityId = 2007;
 
-    const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString);
+    const feature = createOverlayGeometryFeature(
+      [xMin, yMin, xMax, yMax],
+      colorHexString,
+      entityId,
+    );
 
     expect(feature.getGeometry()!.getCoordinates()).toEqual([
       [
@@ -22,6 +28,9 @@ describe('createOverlayGeometryFeature', () => {
     // eslint-disable-next-line @typescript-eslint/ban-ts-comment
     // @ts-ignore - getStyle() is not typed
     expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString);
+
+    expect(feature.get('id')).toBe(entityId);
+    expect(feature.get('type')).toBe(FEATURE_TYPE.SURFACE_OVERLAY);
   });
 
   it('should create a feature with the correct geometry and style when using a different color', () => {
@@ -30,8 +39,13 @@ describe('createOverlayGeometryFeature', () => {
     const xMax = 5;
     const yMax = 5;
     const colorHexString = '#00FF00';
+    const entityId = 'a6e21d64-fd3c-4f7c-8acc-5fc305f4395a';
 
-    const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString);
+    const feature = createOverlayGeometryFeature(
+      [xMin, yMin, xMax, yMax],
+      colorHexString,
+      entityId,
+    );
 
     expect(feature.getGeometry()!.getCoordinates()).toEqual([
       [
@@ -45,5 +59,8 @@ describe('createOverlayGeometryFeature', () => {
     // eslint-disable-next-line @typescript-eslint/ban-ts-comment
     // @ts-ignore - getStyle() is not typed
     expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString);
+
+    expect(feature.get('id')).toBe(entityId);
+    expect(feature.get('type')).toBe(FEATURE_TYPE.SURFACE_MARKER);
   });
 });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
index b294d492153d7f74aea80a7836f30c0557032ba5..e11025d81b6401b8b26fc0879d8b51d255a1e034 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
@@ -1,6 +1,7 @@
-import { Fill, Stroke, Style } from 'ol/style';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
 import Feature from 'ol/Feature';
 import type Polygon from 'ol/geom/Polygon';
+import { Fill, Stroke, Style } from 'ol/style';
 import { createFeatureFromExtent } from './createFeatureFromExtent';
 
 const getBioEntityOverlayFeatureStyle = (color: string): Style =>
@@ -9,8 +10,9 @@ const getBioEntityOverlayFeatureStyle = (color: string): Style =>
 export const createOverlayGeometryFeature = (
   [xMin, yMin, xMax, yMax]: number[],
   color: string,
+  entityId: OverlayBioEntityRender['id'],
 ): Feature<Polygon> => {
-  const feature = createFeatureFromExtent([xMin, yMin, xMax, yMax]);
+  const feature = createFeatureFromExtent([xMin, yMin, xMax, yMax], entityId);
   feature.setStyle(getBioEntityOverlayFeatureStyle(color));
   return feature;
 };
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts
index 06d6074af86160a0318c919e1cda4a95105be466..cd5ba930bb62471fd335944f41d5c6e67b820751 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts
@@ -25,13 +25,13 @@ const CASES = [
 
 describe('createOverlaySubmapLinkRectangleFeature - util', () => {
   it.each(CASES)('should return Feature instance', points => {
-    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR, 1234);
 
     expect(feature).toBeInstanceOf(Feature);
   });
 
   it.each(CASES)('should return Feature instance with valid style and stroke', points => {
-    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR, 1234);
     const style = feature.getStyle();
 
     expect(style).toMatchObject({
@@ -43,7 +43,7 @@ describe('createOverlaySubmapLinkRectangleFeature - util', () => {
     });
   });
   it('should return object with transparent fill and black stroke color when color is null', () => {
-    const feature = createOverlaySubmapLinkRectangleFeature([0, 0, 0, 0], null);
+    const feature = createOverlaySubmapLinkRectangleFeature([0, 0, 0, 0], null, 1234);
     const style = feature.getStyle();
 
     expect(style).toMatchObject({
@@ -55,13 +55,13 @@ describe('createOverlaySubmapLinkRectangleFeature - util', () => {
     });
   });
   it.each(CASES)('should return Feature instance with valid geometry', (points, extent) => {
-    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR, 1234);
     const geometry = feature.getGeometry();
 
     expect(geometry?.getExtent()).toEqual(extent);
   });
 
   it('should throw error if extent is not valid', () => {
-    expect(() => createOverlaySubmapLinkRectangleFeature([100, 100, 0, 0], COLOR)).toThrow();
+    expect(() => createOverlaySubmapLinkRectangleFeature([100, 100, 0, 0], COLOR, 1234)).toThrow();
   });
 });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts
index cef983542a7777c57a8c551ed6e9d96aa1dcb35c..8a051f2f9fda0e7b21c601e40e1b9f6c96309831 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts
@@ -1,4 +1,5 @@
 /* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
 import Feature from 'ol/Feature';
 import type Polygon from 'ol/geom/Polygon';
 import { createFeatureFromExtent } from './createFeatureFromExtent';
@@ -7,8 +8,9 @@ import { getOverlaySubmapLinkRectangleFeatureStyle } from './getOverlaySubmapLin
 export const createOverlaySubmapLinkRectangleFeature = (
   [xMin, yMin, xMax, yMax]: number[],
   color: string | null,
+  entityId: OverlayBioEntityRender['id'],
 ): Feature<Polygon> => {
-  const feature = createFeatureFromExtent([xMin, yMin, xMax, yMax]);
+  const feature = createFeatureFromExtent([xMin, yMin, xMax, yMax], entityId);
   feature.setStyle(getOverlaySubmapLinkRectangleFeatureStyle(color));
   return feature;
 };
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
index 93e24ec85be8ac6dbab4bcfcf307448311c16fc0..107add31699aceb508557e10d9b54420c4d5171a 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
@@ -35,6 +35,7 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
             ...pointToProjection({ x: entity.x2, y: entity.y2 }),
           ],
           entity?.hexColor || color,
+          entity.id,
         );
       }),
     [getOverlayBioEntityColorByAvailableProperties, markersRender, pointToProjection],
@@ -67,6 +68,7 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
               ...pointToProjection({ x: xMax, y: entity.y2 }),
             ],
             entity.value === Infinity ? null : color,
+            entity.id,
           );
         }
 
@@ -77,6 +79,7 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
               ...pointToProjection({ x: xMax, y: entity.y2 }),
             ],
             color,
+            entity.id,
           );
         }
 
diff --git a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.test.ts b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.test.ts
index 28610fd818aa15ae7b6b9dc89fec3ab2e9042786..80af09f63b9e14b963a21b64de18e09c396451c8 100644
--- a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.test.ts
+++ b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.test.ts
@@ -24,7 +24,7 @@ describe('getPinFeature - subUtil', () => {
   });
 
   it('should return id as name', () => {
-    expect(result.get('name')).toBe(bioEntity.id);
+    expect(result.get('id')).toBe(bioEntity.id);
   });
 
   it('should return point parsed with point to projection', () => {
diff --git a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
index ff7bfb00b7af92fa23a3d9be3ae2e4d826320252..51d4f8d363697addadc40b00dae775744530ac0d 100644
--- a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
@@ -1,8 +1,10 @@
 import { ZERO } from '@/constants/common';
 import { HALF } from '@/constants/dividers';
+import { FEATURE_TYPE } from '@/constants/features';
 import { Marker } from '@/redux/markers/markers.types';
 import { BioEntity } from '@/types/models';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import isUUID from 'is-uuid';
 import { Feature } from 'ol';
 import { Point } from 'ol/geom';
 
@@ -10,13 +12,18 @@ export const getPinFeature = (
   { x, y, width, height, id }: Pick<BioEntity, 'id' | 'width' | 'height' | 'x' | 'y'> | Marker,
   pointToProjection: UsePointToProjectionResult,
 ): Feature => {
+  const isMarker = isUUID.anyNonNil(`${id}`);
+
   const point = {
     x: x + (width || ZERO) / HALF,
     y: y + (height || ZERO) / HALF,
   };
 
-  return new Feature({
+  const feature = new Feature({
     geometry: new Point(pointToProjection(point)),
-    name: id,
+    id,
+    type: isMarker ? FEATURE_TYPE.PIN_ICON_MARKER : FEATURE_TYPE.PIN_ICON_BIOENTITY,
   });
+
+  return feature;
 };
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
index ff40ba9134e054c7dd1cb444337071f2a7aef2ed..a42e967759c12b43c1ce2da7158b4a2641a2620d 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
@@ -22,7 +22,7 @@ export const useOlMapLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig[
       return;
     }
 
-    mapInstance.setLayers([tileLayer, reactionsLayer, pinsLayer, overlaysLayer]);
+    mapInstance.setLayers([tileLayer, reactionsLayer, overlaysLayer, pinsLayer]);
   }, [reactionsLayer, tileLayer, pinsLayer, mapInstance, overlaysLayer]);
 
   return [tileLayer, pinsLayer, reactionsLayer, overlaysLayer];
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
index b50d5c150dd2291feab71d5c5a5e0477f05e95aa..a636c57f4210f5ea5564bc93baa8e3bc37e0aecd 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.ts
@@ -3,6 +3,9 @@ import { OPTIONS } from '@/constants/map';
 import { currentBackgroundImagePathSelector } from '@/redux/backgrounds/background.selectors';
 import { mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { projectDataSelector } from '@/redux/project/project.selectors';
+import { Point } from '@/types/map';
+import { usePointToProjection } from '@/utils/map/usePointToProjection';
+import { Extent, boundingExtent } from 'ol/extent';
 import BaseLayer from 'ol/layer/Base';
 import TileLayer from 'ol/layer/Tile';
 import { XYZ } from 'ol/source';
@@ -16,6 +19,21 @@ export const useOlMapTileLayer = (): BaseLayer => {
   const mapSize = useSelector(mapDataSizeSelector);
   const currentBackgroundImagePath = useSelector(currentBackgroundImagePathSelector);
   const project = useSelector(projectDataSelector);
+  const pointToProjection = usePointToProjection();
+
+  const tileExtent = useMemo((): Extent => {
+    const topLeftPoint: Point = {
+      x: mapSize.width,
+      y: mapSize.height,
+    };
+
+    const bottomRightPoint: Point = {
+      x: 0,
+      y: 0,
+    };
+
+    return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection));
+  }, [pointToProjection, mapSize]);
 
   const sourceUrl = useMemo(
     () => getMapTileUrl({ projectDirectory: project?.directory, currentBackgroundImagePath }),
@@ -39,8 +57,9 @@ export const useOlMapTileLayer = (): BaseLayer => {
       new TileLayer({
         visible: true,
         source,
+        extent: tileExtent,
       }),
-    [source],
+    [source, tileExtent],
   );
 
   return tileLayer;
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
index 48b643ceab96db55df795a156aa6880fa3ac3e53..1b52b84c6ef55181b3415866093efc806539e979 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
@@ -48,7 +48,7 @@ describe('useOlMapView - util', () => {
     });
 
     const setViewSpy = jest.spyOn(hohResult.current.mapInstance as Map, 'setView');
-    const CALLED_ONCE = 1;
+    const CALLED_TWICE = 2;
 
     await act(() => {
       store.dispatch(
@@ -63,7 +63,7 @@ describe('useOlMapView - util', () => {
       wrapper: Wrapper,
     });
 
-    await waitFor(() => expect(setViewSpy).toBeCalledTimes(CALLED_ONCE));
+    await waitFor(() => expect(setViewSpy).toBeCalledTimes(CALLED_TWICE));
   });
 
   it('should return valid View instance', async () => {
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
index cd4f2ac09559adf8dfdb4ab35378b52c6da6ab41..ffa0b76e1564bd0fbcd0c638744ef529a650a27b 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
@@ -1,9 +1,10 @@
 /* eslint-disable no-magic-numbers */
-import { OPTIONS } from '@/constants/map';
+import { EXTENT_PADDING_MULTIPLICATOR, OPTIONS } from '@/constants/map';
 import { mapDataInitialPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { MapInstance, Point } from '@/types/map';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { View } from 'ol';
+import { Extent, boundingExtent } from 'ol/extent';
 import { useEffect, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 import { MapConfig } from '../../MapViewer.types';
@@ -17,6 +18,25 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
   const mapSize = useSelector(mapDataSizeSelector);
   const pointToProjection = usePointToProjection();
 
+  const extent = useMemo((): Extent => {
+    const extentPadding = {
+      horizontal: mapSize.width * EXTENT_PADDING_MULTIPLICATOR,
+      vertical: mapSize.height * EXTENT_PADDING_MULTIPLICATOR,
+    };
+
+    const topLeftPoint: Point = {
+      x: mapSize.width + extentPadding.horizontal,
+      y: mapSize.height + extentPadding.vertical,
+    };
+
+    const bottomRightPoint: Point = {
+      x: -extentPadding.horizontal,
+      y: -extentPadding.vertical,
+    };
+
+    return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection));
+  }, [pointToProjection, mapSize]);
+
   const center = useMemo((): Point => {
     const centerPoint: Point = {
       x: mapInitialPosition.x,
@@ -38,8 +58,9 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
       showFullExtent: OPTIONS.showFullExtent,
       maxZoom: mapSize.maxZoom,
       minZoom: mapSize.minZoom,
+      extent,
     }),
-    [center.x, center.y, mapInitialPosition.z, mapSize.maxZoom, mapSize.minZoom],
+    [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/handleSearchResultForRightClickAction.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts
index 858b57fb89c8d9ef42c65f70287ed453e718aa50..29056a8fe5467120d207ff2790af3828d3230661 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts
@@ -20,5 +20,5 @@ export const handleSearchResultForRightClickAction = async ({
     REACTION: handleReactionResults,
   }[type];
 
-  await action(dispatch)(closestSearchResult);
+  await action(dispatch, closestSearchResult)(closestSearchResult);
 };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
index 3d66ff05ac02bda8f981efb70e7cba7f813814d5..a957771e4eb8f3cd53824aab5de92347eb96af7f 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
@@ -1,12 +1,12 @@
+import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
+import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
-import { Pixel } from 'ol/pixel';
 import { Coordinate } from 'ol/coordinate';
-import { MapSize } from '@/redux/map/map.types';
-import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
+import { Pixel } from 'ol/pixel';
+import { getSearchResults } from '../mapSingleClick/getSearchResults';
 import { handleDataReset } from '../mapSingleClick/handleDataReset';
 import { handleSearchResultForRightClickAction } from './handleSearchResultForRightClickAction';
-import { getSearchResults } from '../mapSingleClick/getSearchResults';
 
 /* prettier-ignore */
 export const onMapRightClick =
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 44ac11c9c8e12c0b7a4d357f7851aae3292ee68d..088aa80fec038461d5e0664c44e3eead9fa29709 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
@@ -1,4 +1,9 @@
-import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
+import {
+  FIRST_ARRAY_ELEMENT,
+  SECOND_ARRAY_ELEMENT,
+  SIZE_OF_EMPTY_ARRAY,
+  THIRD_ARRAY_ELEMENT,
+} from '@/constants/common';
 import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
 import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock';
 import { apiPath } from '@/redux/apiPath';
@@ -24,22 +29,33 @@ describe('handleAliasResults - util', () => {
     .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
 
   beforeAll(async () => {
-    handleAliasResults(dispatch)(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+    handleAliasResults(
+      dispatch,
+      ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
+    )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
   });
 
-  it('should run openBioEntityDrawerById as first action', async () => {
+  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/openBioEntityDrawerById');
+      expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('drawer/selectTab');
     });
   });
 
-  it('should run getMultiBioEntity as second action', async () => {
+  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('project/getMultiBioEntity/pending');
+      expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('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');
     });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
index b14875831efa1192261f2f9dc7f3099a87c41a03..bab0bd6227775d92db2c5edfc8f15c1d9377aa6b 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
@@ -1,18 +1,33 @@
 import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
-import { openBioEntityDrawerById } from '@/redux/drawer/drawer.slice';
+import { 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';
 
 /* prettier-ignore */
 export const handleAliasResults =
-  (dispatch: AppDispatch) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
 
+      dispatch(selectTab(`${id}`));
       dispatch(openBioEntityDrawerById(id));
       dispatch(
         getMultiBioEntity({
           searchQueries: [id.toString()],
           isPerfectMatch: true
         }),
-      );
+      )
+        .unwrap().then((bioEntityContents) => {
+
+          PluginsEventBus.dispatchEvent('onSearch', {
+            type: 'bioEntity',
+            searchValues: [closestSearchResult],
+            results: [bioEntityContents],
+          });
+
+          if (hasFitBounds) {
+            searchFitBounds(fitBoundsZoom);
+          }
+        });
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2f23be1f53f13cfe3e5aea6ee8bd0674db12f26
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts
@@ -0,0 +1,117 @@
+import { FEATURE_TYPE } from '@/constants/features';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
+import { Feature } from 'ol';
+import { handleFeaturesClick } from './handleFeaturesClick';
+
+describe('handleFeaturesClick - util', () => {
+  beforeEach(() => {
+    PluginsEventBus.events = [];
+  });
+
+  describe('when feature contains pin icon marker', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+    const featureId = 1234;
+    const features = [
+      new Feature({
+        id: featureId,
+        type: FEATURE_TYPE.PIN_ICON_MARKER,
+      }),
+    ];
+
+    it('should dispatch event onPinIconClick', () => {
+      const dispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+      handleFeaturesClick(features, dispatch);
+      expect(dispatchEventSpy).toHaveBeenCalledWith('onPinIconClick', { id: featureId });
+    });
+
+    it('should return shouldBlockCoordSearch=false', () => {
+      expect(handleFeaturesClick(features, dispatch)).toStrictEqual({
+        shouldBlockCoordSearch: false,
+      });
+    });
+  });
+
+  describe('when feature contains pin icon bioentity', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+    const featureId = 1234;
+    const features = [
+      new Feature({
+        id: featureId,
+        type: FEATURE_TYPE.PIN_ICON_BIOENTITY,
+      }),
+    ];
+
+    it('should dispatch event onPinIconClick', () => {
+      const dispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+      handleFeaturesClick(features, dispatch);
+      expect(dispatchEventSpy).toHaveBeenCalledWith('onPinIconClick', { id: featureId });
+    });
+
+    it('should dispatch actions regarding opening entity drawer', () => {
+      const { store: localStore } = getReduxStoreWithActionsListener();
+      const { dispatch: localDispatch } = localStore;
+      handleFeaturesClick(features, localDispatch);
+      expect(store.getActions()).toStrictEqual([
+        { payload: undefined, type: 'search/clearSearchData' },
+        { payload: 1234, type: 'drawer/openBioEntityDrawerById' },
+      ]);
+    });
+
+    it('should return shouldBlockCoordSearch=true', () => {
+      expect(handleFeaturesClick(features, dispatch)).toStrictEqual({
+        shouldBlockCoordSearch: true,
+      });
+    });
+  });
+
+  describe('when feature contains surface overlay', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+    const featureId = 1234;
+    const features = [
+      new Feature({
+        id: featureId,
+        type: FEATURE_TYPE.SURFACE_OVERLAY,
+      }),
+    ];
+
+    it('should dispatch event onSurfaceClick', () => {
+      const dispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+      handleFeaturesClick(features, dispatch);
+      expect(dispatchEventSpy).toHaveBeenCalledWith('onSurfaceClick', { id: featureId });
+    });
+
+    it('should return shouldBlockCoordSearch=false', () => {
+      expect(handleFeaturesClick(features, dispatch)).toStrictEqual({
+        shouldBlockCoordSearch: false,
+      });
+    });
+  });
+
+  describe('when feature contains surface marker', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+    const featureId = 1234;
+    const features = [
+      new Feature({
+        id: featureId,
+        type: FEATURE_TYPE.SURFACE_MARKER,
+      }),
+    ];
+
+    it('should dispatch event onSurfaceClick', () => {
+      const dispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
+      handleFeaturesClick(features, dispatch);
+      expect(dispatchEventSpy).toHaveBeenCalledWith('onSurfaceClick', { id: featureId });
+    });
+
+    it('should return shouldBlockCoordSearch=false', () => {
+      expect(handleFeaturesClick(features, dispatch)).toStrictEqual({
+        shouldBlockCoordSearch: false,
+      });
+    });
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c1e5923d2290df828598a4b46d8c175979f020c
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts
@@ -0,0 +1,39 @@
+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';
+import { AppDispatch } from '@/redux/store';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { FeatureLike } from 'ol/Feature';
+
+interface HandleFeaturesClickResult {
+  shouldBlockCoordSearch: boolean;
+}
+
+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')));
+
+  pinFeatures.forEach(pin => {
+    const pinId = pin.get('id') as string | number;
+    PluginsEventBus.dispatchEvent('onPinIconClick', { id: pinId });
+
+    if (pin.get('type') === FEATURE_TYPE.PIN_ICON_BIOENTITY) {
+      dispatch(clearSearchData());
+      dispatch(openBioEntityDrawerById(pinId));
+      shouldBlockCoordSearch = true;
+    }
+  });
+
+  surfaceFeatures.forEach(surface => {
+    const surfaceId = surface.get('id') as string | number;
+    PluginsEventBus.dispatchEvent('onSurfaceClick', { id: surfaceId });
+  });
+
+  return {
+    shouldBlockCoordSearch,
+  };
+};
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 b514095b3654a64d101e79df172d841b60abb01d..020f0c6480a1298b8799319fab20ca3e3f9c77b1 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
@@ -33,7 +33,10 @@ describe('handleReactionResults - util', () => {
     .reply(HttpStatusCode.Ok, reactionsFixture);
 
   beforeAll(async () => {
-    handleReactionResults(dispatch)(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+    handleReactionResults(
+      dispatch,
+      ELEMENT_SEARCH_RESULT_MOCK_REACTION,
+    )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
   });
 
   it('should run getReactionsByIds as first action', () => {
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
index 55c244ad04bd1a9cb6021085247101025742fa68..bcadc4a1e476956a6c62d7a190f502924294251b 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
@@ -3,12 +3,14 @@ import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
 import { openReactionDrawerById } 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 { ElementSearchResult, Reaction } from '@/types/models';
 import { PayloadAction } from '@reduxjs/toolkit';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 
 /* prettier-ignore */
 export const handleReactionResults =
-  (dispatch: AppDispatch) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
       const data = await dispatch(getReactionsByIds([id])) as PayloadAction<Reaction[] | undefined>;
       const payload = data?.payload;
@@ -28,6 +30,16 @@ export const handleReactionResults =
         getMultiBioEntity({
           searchQueries: bioEntitiesIds,
           isPerfectMatch: true },
-        ),
-      );
+        )
+      ).unwrap().then((bioEntityContents) => {
+        PluginsEventBus.dispatchEvent('onSearch', {
+          type: 'bioEntity',
+          searchValues: [closestSearchResult],
+          results: [bioEntityContents],
+        });
+
+        if (hasFitBounds) {
+          searchFitBounds(fitBoundsZoom);
+        }
+      });
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
index 23ae912e98c9d24124f0c61be6ff6fa655ed6ca9..39dea1009c6cbacb44eb5819f49911b801aa7a04 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
@@ -8,11 +8,15 @@ import { handleReactionResults } from './handleReactionResults';
 interface HandleSearchResultActionInput {
   searchResults: ElementSearchResult[];
   dispatch: AppDispatch;
+  hasFitBounds?: boolean;
+  fitBoundsZoom?: number;
 }
 
 export const handleSearchResultAction = async ({
   searchResults,
   dispatch,
+  hasFitBounds,
+  fitBoundsZoom,
 }: HandleSearchResultActionInput): Promise<void> => {
   const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT];
   const { type } = closestSearchResult;
@@ -21,7 +25,7 @@ export const handleSearchResultAction = async ({
     REACTION: handleReactionResults,
   }[type];
 
-  await action(dispatch)(closestSearchResult);
+  await action(dispatch, closestSearchResult, hasFitBounds, fitBoundsZoom)(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 806d2a7ca5638841276a7fb90c6ae2b692bc0648..4270a32407558717ed920b0a1740c2286a75f7a2 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
@@ -1,4 +1,6 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /* eslint-disable no-magic-numbers */
+import { FEATURE_TYPE } from '@/constants/features';
 import {
   ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
   ELEMENT_SEARCH_RESULT_MOCK_REACTION,
@@ -8,7 +10,7 @@ import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
-import { MapBrowserEvent } from 'ol';
+import { Feature, Map, MapBrowserEvent } from 'ol';
 import * as handleDataReset from './handleDataReset';
 import * as handleSearchResultAction from './handleSearchResultAction';
 import { onMapSingleClick } from './onMapSingleClick';
@@ -56,8 +58,12 @@ describe('onMapSingleClick - util', () => {
     const coordinate = [90, 90];
     const event = getEvent(coordinate);
 
+    const mapInstanceMock = {
+      forEachFeatureAtPixel: (): void => {},
+    } as unknown as Map;
+
     it('should fire data reset handler', async () => {
-      await handler(event);
+      await handler(event, mapInstanceMock);
       expect(handleDataResetSpy).toBeCalled();
     });
   });
@@ -82,8 +88,12 @@ describe('onMapSingleClick - util', () => {
       .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
       .reply(HttpStatusCode.Ok, undefined);
 
+    const mapInstanceMock = {
+      forEachFeatureAtPixel: (): void => {},
+    } as unknown as Map;
+
     it('does not fire search result action', async () => {
-      await handler(event);
+      await handler(event, mapInstanceMock);
       expect(handleSearchResultActionSpy).not.toBeCalled();
     });
   });
@@ -110,12 +120,53 @@ describe('onMapSingleClick - util', () => {
       .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
       .reply(HttpStatusCode.Ok, []);
 
+    const mapInstanceMock = {
+      forEachFeatureAtPixel: (): void => {},
+    } as unknown as Map;
+
     it('does not fire search result action', async () => {
-      await handler(event);
+      await handler(event, mapInstanceMock);
       expect(handleSearchResultActionSpy).not.toBeCalled();
     });
   });
 
+  describe('when clicked on feature type = pin icon bioentity', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+    const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS;
+    const mapSize = {
+      width: 270,
+      height: 270,
+      tileSize: 256,
+      minZoom: 2,
+      maxZoom: 9,
+    };
+    const coordinate = [270, 270];
+    const point = { x: 540.0072763538013, y: 539.9927236461986 };
+    const event = getEvent(coordinate);
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
+      .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
+
+    const mapInstanceMock = {
+      forEachFeatureAtPixel: (pixel: any, mappingFunction: (feature: Feature) => void): void => {
+        [
+          new Feature({
+            id: 1000,
+            type: FEATURE_TYPE.PIN_ICON_BIOENTITY,
+          }),
+        ].forEach(mappingFunction);
+      },
+    } as unknown as Map;
+
+    it('does NOT fire search result action handler', async () => {
+      const handler = onMapSingleClick(mapSize, modelId, dispatch);
+      await handler(event, mapInstanceMock);
+      await waitFor(() => expect(handleSearchResultActionSpy).not.toBeCalled());
+    });
+  });
+
   describe('when searchResults are valid', () => {
     describe('when results type is ALIAS', () => {
       const { store } = getReduxStoreWithActionsListener();
@@ -136,9 +187,13 @@ describe('onMapSingleClick - util', () => {
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
 
+      const mapInstanceMock = {
+        forEachFeatureAtPixel: (): void => {},
+      } as unknown as Map;
+
       it('does fire search result action handler', async () => {
         const handler = onMapSingleClick(mapSize, modelId, dispatch);
-        await handler(event);
+        await handler(event, mapInstanceMock);
         await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled());
       });
     });
@@ -165,9 +220,13 @@ describe('onMapSingleClick - util', () => {
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_REACTION]);
 
+      const mapInstanceMock = {
+        forEachFeatureAtPixel: (): void => {},
+      } as unknown as Map;
+
       it('does fire search result action - handle reaction', async () => {
         const handler = onMapSingleClick(mapSize, modelId, dispatch);
-        await handler(event);
+        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 6940b226047f357dbbcb9f900c32d78ffafd5934..3d1e425c2e5a8d894df37ecc3f04eceee865ead5 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
@@ -1,15 +1,25 @@
 import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
-import { MapBrowserEvent } from 'ol';
+import { Map, MapBrowserEvent } from 'ol';
+import { FeatureLike } from 'ol/Feature';
 import { getSearchResults } from './getSearchResults';
 import { handleDataReset } from './handleDataReset';
+import { handleFeaturesClick } from './handleFeaturesClick';
 import { handleSearchResultAction } from './handleSearchResultAction';
 
 /* prettier-ignore */
 export const onMapSingleClick =
   (mapSize: MapSize, modelId: number, dispatch: AppDispatch) =>
-    async ({ coordinate }: MapBrowserEvent<UIEvent>): Promise<void> => {
+    async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
+      const featuresAtPixel: FeatureLike[] = [];
+      mapInstance.forEachFeatureAtPixel(pixel, (feature) => featuresAtPixel.push(feature));
+      const { shouldBlockCoordSearch } = handleFeaturesClick(featuresAtPixel, dispatch);
+
+      if (shouldBlockCoordSearch) {
+        return;
+      }
+
       // 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);
diff --git a/src/components/Map/MapViewer/utils/listeners/onPointerMove.test.ts b/src/components/Map/MapViewer/utils/listeners/onPointerMove.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db8737b1a2cb77c716a60f8cb3a2754949ef843b
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/onPointerMove.test.ts
@@ -0,0 +1,75 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Feature, Map, MapBrowserEvent } from 'ol';
+import { onPointerMove } from './onPointerMove';
+
+const TARGET_STRING = 'abcd';
+
+const EVENT_DRAGGING_MOCK = {
+  dragging: true,
+} as unknown as MapBrowserEvent<PointerEvent>;
+
+const EVENT_MOCK = {
+  dragging: false,
+  originalEvent: undefined,
+} as unknown as MapBrowserEvent<PointerEvent>;
+
+const MAP_INSTANCE_BASE_MOCK = {
+  getEventPixel: (): void => {},
+  forEachFeatureAtPixel: (): void => {},
+};
+
+describe('onPointerMove - util', () => {
+  describe('when event dragging', () => {
+    const target = document.createElement('div');
+    const mapInstance = {
+      ...MAP_INSTANCE_BASE_MOCK,
+      getTarget: () => target,
+    } as unknown as Map;
+
+    it('should return nothing and not modify target', () => {
+      expect(onPointerMove(mapInstance, EVENT_DRAGGING_MOCK)).toBe(undefined);
+      expect((mapInstance as any).getTarget().style.cursor).toBe('');
+    });
+  });
+
+  describe('when pin feature present and target is html', () => {
+    const target = document.createElement('div');
+    const mapInstance = {
+      ...MAP_INSTANCE_BASE_MOCK,
+      forEachFeatureAtPixel: () => new Feature(),
+      getTarget: () => target,
+    } as unknown as Map;
+
+    it('should return nothing and modify target', () => {
+      expect(onPointerMove(mapInstance, EVENT_MOCK)).toBe(undefined);
+      expect((mapInstance as any).getTarget().style.cursor).toBe('pointer');
+    });
+  });
+
+  describe('when pin feature present and target is string', () => {
+    const mapInstance = {
+      ...MAP_INSTANCE_BASE_MOCK,
+      forEachFeatureAtPixel: () => new Feature(),
+      getTarget: () => TARGET_STRING,
+    } as unknown as Map;
+
+    it('should return nothing and not modify target', () => {
+      expect(onPointerMove(mapInstance, EVENT_MOCK)).toBe(undefined);
+      expect((mapInstance as any).getTarget()).toBe(TARGET_STRING);
+    });
+  });
+
+  describe('when pin feature is not present and target is html', () => {
+    const target = document.createElement('div');
+    const mapInstance = {
+      ...MAP_INSTANCE_BASE_MOCK,
+      forEachFeatureAtPixel: () => undefined,
+      getTarget: () => target,
+    } as unknown as Map;
+
+    it('should return nothing and not modify target', () => {
+      expect(onPointerMove(mapInstance, EVENT_MOCK)).toBe(undefined);
+      expect((mapInstance as any).getTarget().style.cursor).toBe('');
+    });
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/onPointerMove.ts b/src/components/Map/MapViewer/utils/listeners/onPointerMove.ts
new file mode 100644
index 0000000000000000000000000000000000000000..868c3f3359df5e457e793f4ae6e676d6adf16442
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/onPointerMove.ts
@@ -0,0 +1,29 @@
+import { PIN_ICON_ANY } from '@/constants/features';
+import { Map } from 'ol';
+import MapBrowserEvent from 'ol/MapBrowserEvent';
+
+const isTargetHTMLElement = (target: string | HTMLElement | undefined): target is HTMLElement =>
+  !!target && typeof target !== 'string' && 'style' in target;
+
+/* prettier-ignore */
+export const onPointerMove =
+    (mapInstance: Map, event: MapBrowserEvent<PointerEvent>): void => {
+      if (event.dragging) {
+        return;
+      }
+
+      const pixel = mapInstance.getEventPixel(event.originalEvent);
+      const feature = mapInstance.forEachFeatureAtPixel(pixel, firstFeature => {
+        const isPinIcon = PIN_ICON_ANY.includes(firstFeature.get('type'));
+        if (!isPinIcon) {
+          return undefined;
+        }
+
+        return firstFeature;
+      });
+
+      const target = mapInstance.getTarget();
+      if (isTargetHTMLElement(target)) {
+        target.style.cursor = feature ? 'pointer' : '';
+      }
+    };
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index 5d7631ff007bc82ddcc675b81e67bd5eb384fdf4..72016ec314a2fcfbdd4c31adf3b79416cc99fcb1 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -13,6 +13,7 @@ import { useDebouncedCallback } from 'use-debounce';
 import { onMapRightClick } from './mapRightClick/onMapRightClick';
 import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
 import { onMapPositionChange } from './onMapPositionChange';
+import { onPointerMove } from './onPointerMove';
 
 interface UseOlMapListenersInput {
   view: View;
@@ -57,7 +58,20 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
       return;
     }
 
-    const key = mapInstance.on('singleclick', handleMapSingleClick);
+    const key = mapInstance.on('pointermove', event => onPointerMove(mapInstance, event));
+
+    // eslint-disable-next-line consistent-return
+    return () => unByKey(key);
+  }, [mapInstance]);
+
+  useEffect(() => {
+    if (!mapInstance) {
+      return;
+    }
+
+    const key = mapInstance.on('singleclick', event =>
+      handleMapSingleClick({ coordinate: event.coordinate, pixel: event.pixel }, mapInstance),
+    );
 
     // eslint-disable-next-line consistent-return
     return () => unByKey(key);
diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts
index 326e8ec8822585de8fe9397ec380143a7f040016..49ec30027077460bfdde9b9ff2b6e89961c0273a 100644
--- a/src/components/Map/MapViewer/utils/useOlMap.ts
+++ b/src/components/Map/MapViewer/utils/useOlMap.ts
@@ -19,7 +19,7 @@ type UseOlMap = (input?: UseOlMapInput) => UseOlMapOutput;
 
 export const useOlMap: UseOlMap = ({ target } = {}) => {
   const mapRef = React.useRef<null | HTMLDivElement>(null);
-  const { mapInstance, setMapInstance } = useMapInstance();
+  const { mapInstance, handleSetMapInstance } = useMapInstance();
   const view = useOlMapView({ mapInstance });
   useOlMapLayers({ mapInstance });
   useOlMapListeners({ view, mapInstance });
@@ -41,8 +41,8 @@ export const useOlMap: UseOlMap = ({ target } = {}) => {
       }
     });
 
-    setMapInstance(currentMap => currentMap || map);
-  }, [target, setMapInstance]);
+    handleSetMapInstance(map);
+  }, [target, handleSetMapInstance]);
 
   return {
     mapRef,
diff --git a/src/constants/features.ts b/src/constants/features.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4995bbcf07ff0173cb4c2760bf56874b1b9b9b1c
--- /dev/null
+++ b/src/constants/features.ts
@@ -0,0 +1,9 @@
+export const FEATURE_TYPE = {
+  PIN_ICON_BIOENTITY: 'PIN_ICON_BIOENTITY',
+  PIN_ICON_MARKER: 'PIN_ICON_MARKER',
+  SURFACE_OVERLAY: 'SURFACE_OVERLAY',
+  SURFACE_MARKER: 'SURFACE_MARKER',
+} as const;
+
+export const PIN_ICON_ANY = [FEATURE_TYPE.PIN_ICON_BIOENTITY, FEATURE_TYPE.PIN_ICON_MARKER];
+export const SURFACE_ANY = [FEATURE_TYPE.SURFACE_OVERLAY, FEATURE_TYPE.SURFACE_MARKER];
diff --git a/src/constants/map.ts b/src/constants/map.ts
index 47d6c6ce41a1bf0d636052c8320b2edadb189d53..ae669ac9cae9e68cb574d6146438918f9814f88c 100644
--- a/src/constants/map.ts
+++ b/src/constants/map.ts
@@ -5,11 +5,12 @@ import { HALF_SECOND_MS, ONE_HUNDRED_MS } from './time';
 export const DEFAULT_TILE_SIZE = 256;
 export const DEFAULT_MIN_ZOOM = 2;
 export const DEFAULT_MAX_ZOOM = 9;
-export const DEFAULT_ZOOM = 5;
+export const DEFAULT_ZOOM = 4;
 export const DEFAULT_CENTER_X = 0;
 export const DEFAULT_CENTER_Y = 0;
 // eslint-disable-next-line no-magic-numbers
 export const LATLNG_FALLBACK: LatLng = [0, 0];
+export const EXTENT_PADDING_MULTIPLICATOR = 1;
 
 export const DEFAULT_CENTER_POINT: Point = {
   x: DEFAULT_CENTER_X,
diff --git a/src/redux/backgrounds/backgrounds.constants.ts b/src/redux/backgrounds/backgrounds.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0fa103f27a2b1c77b8521798570f223a6ac4f6d0
--- /dev/null
+++ b/src/redux/backgrounds/backgrounds.constants.ts
@@ -0,0 +1 @@
+export const BACKGROUNDS_FETCHING_ERROR_PREFIX = 'Failed to fetch backgrounds';
diff --git a/src/redux/backgrounds/backgrounds.reducers.test.ts b/src/redux/backgrounds/backgrounds.reducers.test.ts
index 9b99161ae9290ee475248566ffe020cc56dc0561..4f70938fa505c0da1813ec8df6cf16e2f9fe4ad8 100644
--- a/src/redux/backgrounds/backgrounds.reducers.test.ts
+++ b/src/redux/backgrounds/backgrounds.reducers.test.ts
@@ -11,6 +11,8 @@ import backgroundsReducer from './backgrounds.slice';
 import { getAllBackgroundsByProjectId } from './backgrounds.thunks';
 import { BackgroundsState } from './backgrounds.types';
 
+jest.mock('../../utils/showToast');
+
 const mockedAxiosClient = mockNetworkResponse();
 
 const INITIAL_STATE: BackgroundsState = {
@@ -49,13 +51,16 @@ describe('backgrounds reducer', () => {
       .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID))
       .reply(HttpStatusCode.NotFound, []);
 
-    const { type } = await store.dispatch(getAllBackgroundsByProjectId(PROJECT_ID));
+    const { type, payload } = await store.dispatch(getAllBackgroundsByProjectId(PROJECT_ID));
     const { data, loading, error } = store.getState().backgrounds;
 
     expect(type).toBe('backgrounds/getAllBackgroundsByProjectId/rejected');
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
     expect(data).toEqual([]);
+    expect(payload).toBe(
+      "Failed to fetch backgrounds: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
   });
 
   it('should update store on loading getAllBackgroundsByProjectId query', async () => {
diff --git a/src/redux/backgrounds/backgrounds.thunks.ts b/src/redux/backgrounds/backgrounds.thunks.ts
index 3741a1c8ef88c80078adf1462bdeb39807f92e65..18a0c56bcfed6861ffabd3fb944378930dad13c4 100644
--- a/src/redux/backgrounds/backgrounds.thunks.ts
+++ b/src/redux/backgrounds/backgrounds.thunks.ts
@@ -4,17 +4,26 @@ import { MapBackground } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
+import { BACKGROUNDS_FETCHING_ERROR_PREFIX } from './backgrounds.constants';
 
-export const getAllBackgroundsByProjectId = createAsyncThunk(
+export const getAllBackgroundsByProjectId = createAsyncThunk<MapBackground[], string, ThunkConfig>(
   'backgrounds/getAllBackgroundsByProjectId',
-  async (projectId: string): Promise<MapBackground[]> => {
-    const response = await axiosInstance.get<MapBackground[]>(
-      apiPath.getAllBackgroundsByProjectIdQuery(projectId),
-    );
+  async (projectId: string, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<MapBackground[]>(
+        apiPath.getAllBackgroundsByProjectIdQuery(projectId),
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapBackground));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapBackground));
 
-    return isDataValid ? response.data : [];
+      return isDataValid ? response.data : [];
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: BACKGROUNDS_FETCHING_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/bioEntity/bioEntity.constants.ts b/src/redux/bioEntity/bioEntity.constants.ts
index 3c109ae8c9124dc08e0687a06cd0088029110028..b719afdedbecef74f12b3712ae166430ab7d20f1 100644
--- a/src/redux/bioEntity/bioEntity.constants.ts
+++ b/src/redux/bioEntity/bioEntity.constants.ts
@@ -1,3 +1,6 @@
 export const DEFAULT_BIOENTITY_PARAMS = {
   perfectMatch: false,
 };
+
+export const BIO_ENTITY_FETCHING_ERROR_PREFIX = 'Failed to fetch bio entity';
+export const MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX = 'Failed to fetch multi bio entity';
diff --git a/src/redux/bioEntity/bioEntity.reducers.test.ts b/src/redux/bioEntity/bioEntity.reducers.test.ts
index 52062b406da85f623f5d3a273ead35a9dabb59ae..78482731a21eb2acf4c47279fc272f8a236dec44 100644
--- a/src/redux/bioEntity/bioEntity.reducers.test.ts
+++ b/src/redux/bioEntity/bioEntity.reducers.test.ts
@@ -72,7 +72,7 @@ describe('bioEntity reducer', () => {
       )
       .reply(HttpStatusCode.NotFound, bioEntityResponseFixture);
 
-    const { type } = await store.dispatch(
+    const { type, payload } = await store.dispatch(
       getBioEntity({
         searchQuery: SEARCH_QUERY,
         isPerfectMatch: false,
@@ -84,6 +84,9 @@ describe('bioEntity reducer', () => {
       bioEntity => bioEntity.searchQueryElement === SEARCH_QUERY,
     );
     expect(type).toBe('project/getBioEntityContents/rejected');
+    expect(payload).toBe(
+      "Failed to fetch bio entity: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(bioEnityWithSearchElement).toEqual({
       searchQueryElement: SEARCH_QUERY,
       data: undefined,
diff --git a/src/redux/bioEntity/bioEntity.selectors.ts b/src/redux/bioEntity/bioEntity.selectors.ts
index b9566eb75fbac7db3ed74d8ccdfdda56bd607adb..8bf9877d86bb70a7665c50a5f400895b585baaae 100644
--- a/src/redux/bioEntity/bioEntity.selectors.ts
+++ b/src/redux/bioEntity/bioEntity.selectors.ts
@@ -3,13 +3,19 @@ import { rootSelector } from '@/redux/root/root.selectors';
 import { MultiSearchData } from '@/types/fetchDataState';
 import { BioEntity, BioEntityContent, MapModel } from '@/types/models';
 import { createSelector } from '@reduxjs/toolkit';
-import { searchedChemicalsBioEntitesOfCurrentMapSelector } from '../chemicals/chemicals.selectors';
+import {
+  allChemicalsBioEntitesOfAllMapsSelector,
+  searchedChemicalsBioEntitesOfCurrentMapSelector,
+} from '../chemicals/chemicals.selectors';
 import { currentSelectedBioEntityIdSelector } from '../contextMenu/contextMenu.selector';
 import {
   currentSearchedBioEntityId,
   currentSelectedSearchElement,
 } from '../drawer/drawer.selectors';
-import { searchedDrugsBioEntitesOfCurrentMapSelector } from '../drugs/drugs.selectors';
+import {
+  allDrugsBioEntitesOfAllMapsSelector,
+  searchedDrugsBioEntitesOfCurrentMapSelector,
+} from '../drugs/drugs.selectors';
 import { currentModelIdSelector, modelsDataSelector } from '../models/models.selectors';
 
 export const bioEntitySelector = createSelector(rootSelector, state => state.bioEntity);
@@ -122,3 +128,40 @@ export const allVisibleBioEntitiesSelector = createSelector(
     return [content, chemicals, drugs].flat();
   },
 );
+
+export const allContentBioEntitesSelectorOfAllMaps = createSelector(
+  bioEntitySelector,
+  (bioEntities): BioEntity[] => {
+    if (!bioEntities) {
+      return [];
+    }
+
+    return (bioEntities?.data || [])
+      .map(({ data }) => data || [])
+      .flat()
+      .map(({ bioEntity }) => bioEntity);
+  },
+);
+
+export const allBioEntitiesSelector = createSelector(
+  allContentBioEntitesSelectorOfAllMaps,
+  allChemicalsBioEntitesOfAllMapsSelector,
+  allDrugsBioEntitesOfAllMapsSelector,
+  (content, chemicals, drugs): BioEntity[] => {
+    return [content, chemicals, drugs].flat();
+  },
+);
+
+export const currentDrawerBioEntitySelector = createSelector(
+  allBioEntitiesSelector,
+  currentSearchedBioEntityId,
+  (bioEntities, currentBioEntityId): BioEntity | undefined =>
+    bioEntities.find(({ id }) => id === currentBioEntityId),
+);
+
+export const currentDrawerBioEntityRelatedSubmapSelector = createSelector(
+  currentDrawerBioEntitySelector,
+  modelsDataSelector,
+  (bioEntity, models): MapModel | undefined =>
+    models.find(({ idObject }) => idObject === bioEntity?.submodel?.mapId),
+);
diff --git a/src/redux/bioEntity/bioEntity.thunks.test.ts b/src/redux/bioEntity/bioEntity.thunks.test.ts
index b45d1941398d5ac6f99c10e30b7b3ab6c4b03587..f2b54f3abdd5a5e258c51bfc8d224a3877d02fdf 100644
--- a/src/redux/bioEntity/bioEntity.thunks.test.ts
+++ b/src/redux/bioEntity/bioEntity.thunks.test.ts
@@ -7,7 +7,7 @@ import {
 import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { HttpStatusCode } from 'axios';
 import contentsReducer from './bioEntity.slice';
-import { getBioEntity } from './bioEntity.thunks';
+import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks';
 import { BioEntityContentsState } from './bioEntity.types';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
@@ -55,5 +55,72 @@ describe('bioEntityContents thunks', () => {
       );
       expect(payload).toEqual(undefined);
     });
+    it('should handle error message when getBioEntityContents failed', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: SEARCH_QUERY,
+            isPerfectMatch: false,
+          }),
+        )
+        .reply(HttpStatusCode.NotFound, null);
+
+      const { payload } = await store.dispatch(
+        getBioEntity({
+          searchQuery: SEARCH_QUERY,
+          isPerfectMatch: false,
+        }),
+      );
+      expect(payload).toEqual(
+        "Failed to fetch bio entity: The page you're looking for doesn't exist. Please verify the URL and try again.",
+      );
+    });
+  });
+  describe('getMultiBioEntity', () => {
+    it('should return transformed bioEntityContent array', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: SEARCH_QUERY,
+            isPerfectMatch: false,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+      const data = await store
+        .dispatch(
+          getMultiBioEntity({
+            searchQueries: [SEARCH_QUERY],
+            isPerfectMatch: false,
+          }),
+        )
+        .unwrap();
+
+      expect(data).toEqual(bioEntityResponseFixture.content);
+    });
+    it('should combine all returned bioEntityContent arrays and return array with all provided bioEntityContent elements', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: SEARCH_QUERY,
+            isPerfectMatch: false,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+      const data = await store
+        .dispatch(
+          getMultiBioEntity({
+            searchQueries: [SEARCH_QUERY, SEARCH_QUERY],
+            isPerfectMatch: false,
+          }),
+        )
+        .unwrap();
+
+      expect(data).toEqual([
+        ...bioEntityResponseFixture.content,
+        ...bioEntityResponseFixture.content,
+      ]);
+    });
   });
 });
diff --git a/src/redux/bioEntity/bioEntity.thunks.ts b/src/redux/bioEntity/bioEntity.thunks.ts
index e1b59d1ed97a7fc4bb77006b7350a8d3e79980d7..20c312f52071290648b7dec2949e4bfd1e6e113c 100644
--- a/src/redux/bioEntity/bioEntity.thunks.ts
+++ b/src/redux/bioEntity/bioEntity.thunks.ts
@@ -1,19 +1,25 @@
 import { PerfectMultiSearchParams, PerfectSearchParams } from '@/types/search';
-import { createAsyncThunk } from '@reduxjs/toolkit';
+import { PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
 import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema';
 import { apiPath } from '@/redux/apiPath';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { BioEntityContent, BioEntityResponse } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
+import {
+  BIO_ENTITY_FETCHING_ERROR_PREFIX,
+  MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+} from './bioEntity.constants';
 
 type GetBioEntityProps = PerfectSearchParams;
 
-export const getBioEntity = createAsyncThunk(
-  'project/getBioEntityContents',
-  async ({
-    searchQuery,
-    isPerfectMatch,
-  }: GetBioEntityProps): Promise<BioEntityContent[] | undefined> => {
+export const getBioEntity = createAsyncThunk<
+  BioEntityContent[] | undefined,
+  GetBioEntityProps,
+  ThunkConfig
+>('project/getBioEntityContents', async ({ searchQuery, isPerfectMatch }, { rejectWithValue }) => {
+  try {
     const response = await axiosInstanceNewAPI.get<BioEntityResponse>(
       apiPath.getBioEntityContentsStringWithQuery({ searchQuery, isPerfectMatch }),
     );
@@ -21,21 +27,47 @@ export const getBioEntity = createAsyncThunk(
     const isDataValid = validateDataUsingZodSchema(response.data, bioEntityResponseSchema);
 
     return isDataValid ? response.data.content : undefined;
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({
+      error,
+      prefix: BIO_ENTITY_FETCHING_ERROR_PREFIX,
+    });
+    return rejectWithValue(errorMessage);
+  }
+});
 
 type GetMultiBioEntityProps = PerfectMultiSearchParams;
+type GetMultiBioEntityActions = PayloadAction<BioEntityContent[] | undefined>[];
 
-export const getMultiBioEntity = createAsyncThunk(
+export const getMultiBioEntity = createAsyncThunk<
+  BioEntityContent[],
+  GetMultiBioEntityProps,
+  ThunkConfig
+>(
   'project/getMultiBioEntity',
-  async (
-    { searchQueries, isPerfectMatch }: GetMultiBioEntityProps,
-    { dispatch },
-  ): Promise<void> => {
-    const asyncGetBioEntityFunctions = searchQueries.map(searchQuery =>
-      dispatch(getBioEntity({ searchQuery, isPerfectMatch })),
-    );
+  // eslint-disable-next-line consistent-return
+  async ({ searchQueries, isPerfectMatch }, { dispatch, rejectWithValue }) => {
+    try {
+      const asyncGetBioEntityFunctions = searchQueries.map(searchQuery =>
+        dispatch(getBioEntity({ searchQuery, isPerfectMatch })),
+      );
+
+      const bioEntityContentsActions = (await Promise.all(
+        asyncGetBioEntityFunctions,
+      )) as GetMultiBioEntityActions;
+
+      const bioEntityContents = bioEntityContentsActions
+        .map(bioEntityContentsAction => bioEntityContentsAction.payload || [])
+        .flat();
+
+      return bioEntityContents;
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: MULTI_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+      });
 
-    await Promise.all(asyncGetBioEntityFunctions);
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/chemicals/chemicals.constants.ts b/src/redux/chemicals/chemicals.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3311122e0b0eaf0ea055af2892ef537c9807d0b6
--- /dev/null
+++ b/src/redux/chemicals/chemicals.constants.ts
@@ -0,0 +1,2 @@
+export const CHEMICALS_FETCHING_ERROR_PREFIX = 'Failed to fetch chemicals';
+export const MULTI_CHEMICALS_FETCHING_ERROR_PREFIX = 'Failed to fetch multi chemicals';
diff --git a/src/redux/chemicals/chemicals.reducers.test.ts b/src/redux/chemicals/chemicals.reducers.test.ts
index 59a87b20f784e059d63c5ea030274275b2562a6b..9b66afe34421293ef0e89ec2b02b45767c5c151c 100644
--- a/src/redux/chemicals/chemicals.reducers.test.ts
+++ b/src/redux/chemicals/chemicals.reducers.test.ts
@@ -57,7 +57,7 @@ describe('chemicals reducer', () => {
       .onGet(apiPath.getChemicalsStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.NotFound, chemicalsFixture);
 
-    const { type } = await store.dispatch(getChemicals(SEARCH_QUERY));
+    const { type, payload } = await store.dispatch(getChemicals(SEARCH_QUERY));
     const { data } = store.getState().chemicals;
 
     const chemicalsWithSearchElement = data.find(
@@ -65,6 +65,9 @@ describe('chemicals reducer', () => {
     );
 
     expect(type).toBe('project/getChemicals/rejected');
+    expect(payload).toBe(
+      "Failed to fetch chemicals: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(chemicalsWithSearchElement).toEqual({
       searchQueryElement: SEARCH_QUERY,
       data: undefined,
diff --git a/src/redux/chemicals/chemicals.selectors.ts b/src/redux/chemicals/chemicals.selectors.ts
index 03f829f8de425665f9f0a4f6d15d94fd37fee2b8..bb8d4aee3e28ffe7c65490e541e04a96ffdee38d 100644
--- a/src/redux/chemicals/chemicals.selectors.ts
+++ b/src/redux/chemicals/chemicals.selectors.ts
@@ -41,6 +41,18 @@ export const searchedChemicalsBioEntitesOfCurrentMapSelector = createSelector(
   },
 );
 
+export const allChemicalsBioEntitesOfAllMapsSelector = createSelector(
+  chemicalsSelector,
+  (chemicalsState): BioEntity[] => {
+    return (chemicalsState?.data || [])
+      .map(({ data }) => data || [])
+      .flat()
+      .map(({ targets }) => targets.map(({ targetElements }) => targetElements))
+      .flat()
+      .flat();
+  },
+);
+
 export const loadingChemicalsStatusSelector = createSelector(
   chemicalsForSelectedSearchElementSelector,
   state => state?.loading,
diff --git a/src/redux/chemicals/chemicals.thunks.test.ts b/src/redux/chemicals/chemicals.thunks.test.ts
index 88926792155e930d39ae396f25733fcb4e1fe099..73cb2d0ef46659c702a4cda23ea6eda640bb871e 100644
--- a/src/redux/chemicals/chemicals.thunks.test.ts
+++ b/src/redux/chemicals/chemicals.thunks.test.ts
@@ -35,5 +35,15 @@ describe('chemicals thunks', () => {
       const { payload } = await store.dispatch(getChemicals(SEARCH_QUERY));
       expect(payload).toEqual(undefined);
     });
+    it('should handle error message when getChemiclas failed ', async () => {
+      mockedAxiosClient
+        .onGet(apiPath.getChemicalsStringWithQuery(SEARCH_QUERY))
+        .reply(HttpStatusCode.Forbidden, { randomProperty: 'randomValue' });
+
+      const { payload } = await store.dispatch(getChemicals(SEARCH_QUERY));
+      expect(payload).toEqual(
+        "Failed to fetch chemicals: Access Forbidden! You don't have permission to access this resource.",
+      );
+    });
   });
 });
diff --git a/src/redux/chemicals/chemicals.thunks.ts b/src/redux/chemicals/chemicals.thunks.ts
index 0afc94db5f8f686b845e8e514082866b18219908..5b25951d7bf7e4ebdaefecdca4afa4bd54501346 100644
--- a/src/redux/chemicals/chemicals.thunks.ts
+++ b/src/redux/chemicals/chemicals.thunks.ts
@@ -2,30 +2,51 @@ import { chemicalSchema } from '@/models/chemicalSchema';
 import { apiPath } from '@/redux/apiPath';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { Chemical } from '@/types/models';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
+import { ThunkConfig } from '@/types/store';
+import {
+  CHEMICALS_FETCHING_ERROR_PREFIX,
+  MULTI_CHEMICALS_FETCHING_ERROR_PREFIX,
+} from './chemicals.constants';
 
-export const getChemicals = createAsyncThunk(
+export const getChemicals = createAsyncThunk<Chemical[] | undefined, string, ThunkConfig>(
   'project/getChemicals',
-  async (searchQuery: string): Promise<Chemical[] | undefined> => {
-    const response = await axiosInstanceNewAPI.get<Chemical[]>(
-      apiPath.getChemicalsStringWithQuery(searchQuery),
-    );
+  async (searchQuery, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstanceNewAPI.get<Chemical[]>(
+        apiPath.getChemicalsStringWithQuery(searchQuery),
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(chemicalSchema));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(chemicalSchema));
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: CHEMICALS_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
-export const getMultiChemicals = createAsyncThunk(
+export const getMultiChemicals = createAsyncThunk<void, string[], ThunkConfig>(
   'project/getMultChemicals',
-  async (searchQueries: string[], { dispatch }): Promise<void> => {
-    const asyncGetChemicalsFunctions = searchQueries.map(searchQuery =>
-      dispatch(getChemicals(searchQuery)),
-    );
+  // eslint-disable-next-line consistent-return
+  async (searchQueries, { dispatch, rejectWithValue }) => {
+    try {
+      const asyncGetChemicalsFunctions = searchQueries.map(searchQuery =>
+        dispatch(getChemicals(searchQuery)),
+      );
 
-    await Promise.all(asyncGetChemicalsFunctions);
+      await Promise.all(asyncGetChemicalsFunctions);
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: MULTI_CHEMICALS_FETCHING_ERROR_PREFIX,
+      });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/compartmentPathways/comparmentPathways.constants.ts b/src/redux/compartmentPathways/comparmentPathways.constants.ts
index 2bf4d5195d6e6e7fca140310ea21ce69f373da03..cc54322af2521fa94bbe3c5b201b1cd77f5fffd7 100644
--- a/src/redux/compartmentPathways/comparmentPathways.constants.ts
+++ b/src/redux/compartmentPathways/comparmentPathways.constants.ts
@@ -1 +1,3 @@
 export const MAX_NUMBER_OF_IDS_IN_GET_QUERY = 100;
+
+export const COMPARMENT_PATHWAYS_FETCHING_ERROR_PREFIX = 'Failed to fetch compartment pathways';
diff --git a/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts b/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts
index 037eefa24fe80a7a82141bdedf7ce5c757e0b9b6..94d445b845aa63f1c45fef92ea66a5a75a05fd00 100644
--- a/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts
+++ b/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts
@@ -112,8 +112,11 @@ describe('compartmentPathways reducer', () => {
     expect(loading).toEqual('pending');
     expect(data).toEqual([]);
 
-    await compartmentPathwaysPromise;
+    const dispatchData = await compartmentPathwaysPromise;
 
+    expect(dispatchData.payload).toBe(
+      "Failed to fetch compartment pathways: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     const { loading: promiseFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
 
     expect(promiseFulfilled).toEqual('failed');
diff --git a/src/redux/compartmentPathways/compartmentPathways.thunks.ts b/src/redux/compartmentPathways/compartmentPathways.thunks.ts
index e0d69617a704c2361043c98bf1104fb7ced0e827..b8143dbe2627fe7239a9eadf8546497e70b5bbfe 100644
--- a/src/redux/compartmentPathways/compartmentPathways.thunks.ts
+++ b/src/redux/compartmentPathways/compartmentPathways.thunks.ts
@@ -8,7 +8,11 @@ import {
   compartmentPathwaySchema,
 } from '@/models/compartmentPathwaySchema';
 import { z } from 'zod';
-import { MAX_NUMBER_OF_IDS_IN_GET_QUERY } from './comparmentPathways.constants';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import {
+  COMPARMENT_PATHWAYS_FETCHING_ERROR_PREFIX,
+  MAX_NUMBER_OF_IDS_IN_GET_QUERY,
+} from './comparmentPathways.constants';
 import { apiPath } from '../apiPath';
 
 /** UTILS */
@@ -112,9 +116,18 @@ export const fetchCompartmentPathways = async (
 
 export const getCompartmentPathways = createAsyncThunk(
   'compartmentPathways/getCompartmentPathways',
-  async (modelsIds: number[] | undefined) => {
-    const compartmentIds = await fetchCompartmentPathwaysIds(modelsIds);
-    const comparmentPathways = await fetchCompartmentPathways(compartmentIds);
-    return comparmentPathways;
+  async (modelsIds: number[] | undefined, { rejectWithValue }) => {
+    try {
+      const compartmentIds = await fetchCompartmentPathwaysIds(modelsIds);
+      const comparmentPathways = await fetchCompartmentPathways(compartmentIds);
+
+      return comparmentPathways;
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: COMPARMENT_PATHWAYS_FETCHING_ERROR_PREFIX,
+      });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/configuration/configuration.constants.ts b/src/redux/configuration/configuration.constants.ts
index bcf3c906f5ab72e0012749ad77fe2d710e2cd866..9a2762d91efeac7a80edb56d071e2fee999a172d 100644
--- a/src/redux/configuration/configuration.constants.ts
+++ b/src/redux/configuration/configuration.constants.ts
@@ -19,3 +19,7 @@ export const SBGN_ML_HANDLER_NAME_ID = 'SBGN-ML';
 export const PNG_IMAGE_HANDLER_NAME_ID = 'PNG image';
 export const PDF_HANDLER_NAME_ID = 'PDF';
 export const SVG_IMAGE_HANDLER_NAME_ID = 'SVG image';
+
+export const CONFIGURATION_OPTIONS_FETCHING_ERROR_PREFIX = 'Failed to fetch configuration options';
+
+export const CONFIGURATION_FETCHING_ERROR_PREFIX = 'Failed to fetch configuration';
diff --git a/src/redux/configuration/configuration.thunks.ts b/src/redux/configuration/configuration.thunks.ts
index 012e8b1c5184c9ed39c948d76b4124d29dac57d4..8b4db5ea19083cc0f044966eb567cc12610aa66f 100644
--- a/src/redux/configuration/configuration.thunks.ts
+++ b/src/redux/configuration/configuration.thunks.ts
@@ -5,11 +5,20 @@ import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { configurationSchema } from '@/models/configurationSchema';
 import { configurationOptionSchema } from '@/models/configurationOptionSchema';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
+import {
+  CONFIGURATION_FETCHING_ERROR_PREFIX,
+  CONFIGURATION_OPTIONS_FETCHING_ERROR_PREFIX,
+} from './configuration.constants';
 
-export const getConfigurationOptions = createAsyncThunk(
-  'configuration/getConfigurationOptions',
-  async (): Promise<ConfigurationOption[] | undefined> => {
+export const getConfigurationOptions = createAsyncThunk<
+  ConfigurationOption[] | undefined,
+  void,
+  ThunkConfig
+>('configuration/getConfigurationOptions', async (_, { rejectWithValue }) => {
+  try {
     const response = await axiosInstance.get<ConfigurationOption[]>(
       apiPath.getConfigurationOptions(),
     );
@@ -20,16 +29,27 @@ export const getConfigurationOptions = createAsyncThunk(
     );
 
     return isDataValid ? response.data : undefined;
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({
+      error,
+      prefix: CONFIGURATION_OPTIONS_FETCHING_ERROR_PREFIX,
+    });
+    return rejectWithValue(errorMessage);
+  }
+});
 
-export const getConfiguration = createAsyncThunk(
+export const getConfiguration = createAsyncThunk<Configuration | undefined, void, ThunkConfig>(
   'configuration/getConfiguration',
-  async (): Promise<Configuration | undefined> => {
-    const response = await axiosInstance.get<Configuration>(apiPath.getConfiguration());
+  async (_, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<Configuration>(apiPath.getConfiguration());
 
-    const isDataValid = validateDataUsingZodSchema(response.data, configurationSchema);
+      const isDataValid = validateDataUsingZodSchema(response.data, configurationSchema);
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: CONFIGURATION_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/drawer/drawer.constants.ts b/src/redux/drawer/drawer.constants.ts
index 33890a23abf2b03c24b47f53326ea00cf37024f8..bef3fd3d8a0b585baf19e421da3536e218e1403a 100644
--- a/src/redux/drawer/drawer.constants.ts
+++ b/src/redux/drawer/drawer.constants.ts
@@ -19,3 +19,7 @@ export const DRAWER_INITIAL_STATE: DrawerState = {
     currentStep: 0,
   },
 };
+
+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.reducers.ts b/src/redux/drawer/drawer.reducers.ts
index 3a72aa534ed977ae551c3a289ae05c4a29a3582a..29333ec2c70a27cb8088e55cee67a010d4d27b0f 100644
--- a/src/redux/drawer/drawer.reducers.ts
+++ b/src/redux/drawer/drawer.reducers.ts
@@ -111,7 +111,6 @@ export const openBioEntityDrawerByIdReducer = (
   state.isOpen = true;
   state.drawerName = 'bio-entity';
   state.bioEntityDrawerState.bioentityId = action.payload;
-  state.searchDrawerState.selectedSearchElement = action.payload.toString();
 };
 
 export const getBioEntityDrugsForTargetReducers = (
diff --git a/src/redux/drawer/drawer.thunks.ts b/src/redux/drawer/drawer.thunks.ts
index 7e60536f9d9218e75db2e516c8edbf649e43e050..f08955f9c5aafd0d79a7b960fe584abf7b688ac1 100644
--- a/src/redux/drawer/drawer.thunks.ts
+++ b/src/redux/drawer/drawer.thunks.ts
@@ -5,7 +5,13 @@ import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { Chemical, Drug, TargetSearchNameResult } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
+import { ThunkConfig } from '@/types/store';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { apiPath } from '../apiPath';
+import {
+  CHEMICALS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+  DRUGS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+} from './drawer.constants';
 
 const QUERY_COLUMN_NAME = 'name';
 
@@ -28,16 +34,25 @@ const getDrugsByName = async (drugName: string): Promise<Drug[]> => {
   return response.data.filter(isDataValid);
 };
 
-export const getDrugsForBioEntityDrawerTarget = createAsyncThunk(
+export const getDrugsForBioEntityDrawerTarget = createAsyncThunk<Drug[], string, ThunkConfig>(
   'drawer/getDrugsForBioEntityDrawerTarget',
-  async (target: string): Promise<Drug[]> => {
-    const drugsNames = await getDrugsNamesForTarget(target);
-    const drugsArrays = await Promise.all(
-      drugsNames.map(({ name }) => getDrugsByName(encodeURIComponent(name))),
-    );
-    const drugs = drugsArrays.flat();
-
-    return drugs;
+  async (target, { rejectWithValue }) => {
+    try {
+      const drugsNames = await getDrugsNamesForTarget(target);
+      const drugsArrays = await Promise.all(
+        drugsNames.map(({ name }) => getDrugsByName(encodeURIComponent(name))),
+      );
+      const drugs = drugsArrays.flat();
+
+      return drugs;
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: DRUGS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+      });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
@@ -63,9 +78,12 @@ const getChemicalsByName = async (chemicalName: string): Promise<Chemical[]> =>
   return response.data.filter(isDataValid);
 };
 
-export const getChemicalsForBioEntityDrawerTarget = createAsyncThunk(
-  'drawer/getChemicalsForBioEntityDrawerTarget',
-  async (target: string): Promise<Chemical[]> => {
+export const getChemicalsForBioEntityDrawerTarget = createAsyncThunk<
+  Chemical[],
+  string,
+  ThunkConfig
+>('drawer/getChemicalsForBioEntityDrawerTarget', async (target, { rejectWithValue }) => {
+  try {
     const chemicalsNames = await getChemicalsNamesForTarget(target);
     const chemicalsArrays = await Promise.all(
       chemicalsNames.map(({ name }) => getChemicalsByName(encodeURIComponent(name))),
@@ -73,5 +91,12 @@ export const getChemicalsForBioEntityDrawerTarget = createAsyncThunk(
     const chemicals = chemicalsArrays.flat();
 
     return chemicals;
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({
+      error,
+      prefix: CHEMICALS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+    });
+
+    return rejectWithValue(errorMessage);
+  }
+});
diff --git a/src/redux/drawer/drawer.types.ts b/src/redux/drawer/drawer.types.ts
index a348517b0e6beea005555e28e870734205a4a090..a0d5198979607da4cd56dd22cfd129a5c3517418 100644
--- a/src/redux/drawer/drawer.types.ts
+++ b/src/redux/drawer/drawer.types.ts
@@ -43,3 +43,6 @@ export type OpenReactionDrawerByIdAction = PayloadAction<OpenReactionDrawerByIdP
 
 export type OpenBioEntityDrawerByIdPayload = number | string;
 export type OpenBioEntityDrawerByIdAction = PayloadAction<OpenBioEntityDrawerByIdPayload>;
+
+export type SetSelectedSearchElementPayload = string;
+export type SetSelectedSearchElementAction = PayloadAction<SetSelectedSearchElementPayload>;
diff --git a/src/redux/drugs/drugs.constants.ts b/src/redux/drugs/drugs.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c31cff0fe0dd4e776ff6aa8a3a7044bb7ffedf78
--- /dev/null
+++ b/src/redux/drugs/drugs.constants.ts
@@ -0,0 +1,2 @@
+export const DRUGS_FETCHING_ERROR_PREFIX = 'Failed to fetch drugs';
+export const MULTI_DRUGS_FETCHING_ERROR_PREFIX = 'Failed to fetch multi drugs';
diff --git a/src/redux/drugs/drugs.reducers.test.ts b/src/redux/drugs/drugs.reducers.test.ts
index 3ad034db1d614a691017c221f541dfc39e4b6fcf..f6e6c671bc29975194a163390e717a5bc91275e3 100644
--- a/src/redux/drugs/drugs.reducers.test.ts
+++ b/src/redux/drugs/drugs.reducers.test.ts
@@ -56,12 +56,15 @@ describe('drugs reducer', () => {
       .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.NotFound, []);
 
-    const { type } = await store.dispatch(getDrugs(SEARCH_QUERY));
+    const { type, payload } = await store.dispatch(getDrugs(SEARCH_QUERY));
     const { data } = store.getState().drugs;
     const drugsWithSearchElement = data.find(
       bioEntity => bioEntity.searchQueryElement === SEARCH_QUERY,
     );
 
+    expect(payload).toBe(
+      "Failed to fetch drugs: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(type).toBe('project/getDrugs/rejected');
     expect(drugsWithSearchElement).toEqual({
       searchQueryElement: SEARCH_QUERY,
diff --git a/src/redux/drugs/drugs.selectors.ts b/src/redux/drugs/drugs.selectors.ts
index 45e9c16c828d894e31b3b5b3faf04a9fdd0981dc..f5c74de2ffeaf4b06ef5a34bbf8ae899e276e717 100644
--- a/src/redux/drugs/drugs.selectors.ts
+++ b/src/redux/drugs/drugs.selectors.ts
@@ -54,3 +54,15 @@ export const searchedDrugsBioEntitesOfCurrentMapSelector = createSelector(
       .filter(bioEntity => bioEntity.model === currentModelId);
   },
 );
+
+export const allDrugsBioEntitesOfAllMapsSelector = createSelector(
+  drugsSelector,
+  (drugsState): BioEntity[] => {
+    return (drugsState?.data || [])
+      .map(({ data }) => data || [])
+      .flat()
+      .map(({ targets }) => targets.map(({ targetElements }) => targetElements))
+      .flat()
+      .flat();
+  },
+);
diff --git a/src/redux/drugs/drugs.thunks.ts b/src/redux/drugs/drugs.thunks.ts
index 30074e33d6ee751c2f8886e3793050ba84e1d41f..6bd3b532665b877cdd8a7a95263ebdd9ac2bb5de 100644
--- a/src/redux/drugs/drugs.thunks.ts
+++ b/src/redux/drugs/drugs.thunks.ts
@@ -2,30 +2,45 @@ import { drugSchema } from '@/models/drugSchema';
 import { apiPath } from '@/redux/apiPath';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { Drug } from '@/types/models';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
+import { ThunkConfig } from '@/types/store';
+import { DRUGS_FETCHING_ERROR_PREFIX, MULTI_DRUGS_FETCHING_ERROR_PREFIX } from './drugs.constants';
 
-export const getDrugs = createAsyncThunk(
+export const getDrugs = createAsyncThunk<Drug[] | undefined, string, ThunkConfig>(
   'project/getDrugs',
-  async (searchQuery: string): Promise<Drug[] | undefined> => {
-    const response = await axiosInstanceNewAPI.get<Drug[]>(
-      apiPath.getDrugsStringWithQuery(searchQuery),
-    );
+  async (searchQuery: string, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstanceNewAPI.get<Drug[]>(
+        apiPath.getDrugsStringWithQuery(searchQuery),
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema));
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: DRUGS_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
-export const getMultiDrugs = createAsyncThunk(
+export const getMultiDrugs = createAsyncThunk<void, string[], ThunkConfig>(
   'project/getMultiDrugs',
-  async (searchQueries: string[], { dispatch }): Promise<void> => {
-    const asyncGetDrugsFunctions = searchQueries.map(searchQuery =>
-      dispatch(getDrugs(searchQuery)),
-    );
+  // eslint-disable-next-line consistent-return
+  async (searchQueries, { dispatch, rejectWithValue }) => {
+    try {
+      const asyncGetDrugsFunctions = searchQueries.map(searchQuery =>
+        dispatch(getDrugs(searchQuery)),
+      );
 
-    await Promise.all(asyncGetDrugsFunctions);
+      await Promise.all(asyncGetDrugsFunctions);
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: MULTI_DRUGS_FETCHING_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/export/export.constants.ts b/src/redux/export/export.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8f464a4d9241f6d8e4dc875e48c75a4611c16da8
--- /dev/null
+++ b/src/redux/export/export.constants.ts
@@ -0,0 +1,2 @@
+export const ELEMENTS_DOWNLOAD_ERROR_PREFIX = 'Failed to download elements';
+export const NETWORK_DOWNLOAD_ERROR_PREFIX = 'Failed to download network';
diff --git a/src/redux/export/export.reducers.test.ts b/src/redux/export/export.reducers.test.ts
index 894ee98a802339b948da3a12556716284b4aa41d..778aca4f95a58b094b126f449643804fcab85287 100644
--- a/src/redux/export/export.reducers.test.ts
+++ b/src/redux/export/export.reducers.test.ts
@@ -76,7 +76,7 @@ describe('export reducer', () => {
     mockedAxiosClient
       .onPost(apiPath.downloadNetworkCsv())
       .reply(HttpStatusCode.NotFound, undefined);
-    await store.dispatch(
+    const { payload, type } = await store.dispatch(
       downloadNetwork({
         annotations: [],
         columns: [],
@@ -85,8 +85,11 @@ describe('export reducer', () => {
         submaps: [],
       }),
     );
+    expect(type).toBe('export/downloadNetwork/rejected');
+    expect(payload).toBe(
+      "Failed to download network: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     const { loading } = store.getState().export.downloadNetwork;
-
     expect(loading).toEqual('failed');
   });
 
@@ -132,7 +135,7 @@ describe('export reducer', () => {
     mockedAxiosClient
       .onPost(apiPath.downloadElementsCsv())
       .reply(HttpStatusCode.NotFound, undefined);
-    await store.dispatch(
+    const { payload } = await store.dispatch(
       downloadElements({
         annotations: [],
         columns: [],
@@ -144,5 +147,8 @@ describe('export reducer', () => {
     const { loading } = store.getState().export.downloadElements;
 
     expect(loading).toEqual('failed');
+    expect(payload).toEqual(
+      "Failed to download elements: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
   });
 });
diff --git a/src/redux/export/export.thunks.ts b/src/redux/export/export.thunks.ts
index 3a25cd8c40f76e9442d43fa354bd7877d29b4306..bf49e0b04e79e1eea61cac05f2eb79810b23ce55 100644
--- a/src/redux/export/export.thunks.ts
+++ b/src/redux/export/export.thunks.ts
@@ -5,8 +5,11 @@ import { PROJECT_ID } from '@/constants';
 import { ExportNetwork, ExportElements } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { exportNetworkchema, exportElementsSchema } from '@/models/exportSchema';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
 import { downloadFileFromBlob } from './export.utils';
+import { ELEMENTS_DOWNLOAD_ERROR_PREFIX, NETWORK_DOWNLOAD_ERROR_PREFIX } from './export.constants';
 
 type DownloadElementsBodyRequest = {
   columns: string[];
@@ -16,9 +19,13 @@ type DownloadElementsBodyRequest = {
   excludedCompartmentIds: number[];
 };
 
-export const downloadElements = createAsyncThunk(
-  'export/downloadElements',
-  async (data: DownloadElementsBodyRequest): Promise<void> => {
+export const downloadElements = createAsyncThunk<
+  undefined,
+  DownloadElementsBodyRequest,
+  ThunkConfig
+  // eslint-disable-next-line consistent-return
+>('export/downloadElements', async (data, { rejectWithValue }) => {
+  try {
     const response = await axiosInstanceNewAPI.post<ExportElements>(
       apiPath.downloadElementsCsv(),
       data,
@@ -32,8 +39,12 @@ export const downloadElements = createAsyncThunk(
     if (isDataValid) {
       downloadFileFromBlob(response.data, `${PROJECT_ID}-elementExport.csv`);
     }
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: ELEMENTS_DOWNLOAD_ERROR_PREFIX });
+
+    return rejectWithValue(errorMessage);
+  }
+});
 
 type DownloadNetworkBodyRequest = {
   columns: string[];
@@ -43,21 +54,28 @@ type DownloadNetworkBodyRequest = {
   excludedCompartmentIds: number[];
 };
 
-export const downloadNetwork = createAsyncThunk(
+export const downloadNetwork = createAsyncThunk<undefined, DownloadNetworkBodyRequest, ThunkConfig>(
   'export/downloadNetwork',
-  async (data: DownloadNetworkBodyRequest): Promise<void> => {
-    const response = await axiosInstanceNewAPI.post<ExportNetwork>(
-      apiPath.downloadNetworkCsv(),
-      data,
-      {
-        withCredentials: true,
-      },
-    );
+  // eslint-disable-next-line consistent-return
+  async (data, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstanceNewAPI.post<ExportNetwork>(
+        apiPath.downloadNetworkCsv(),
+        data,
+        {
+          withCredentials: true,
+        },
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, exportNetworkchema);
+      const isDataValid = validateDataUsingZodSchema(response.data, exportNetworkchema);
 
-    if (isDataValid) {
-      downloadFileFromBlob(response.data, `${PROJECT_ID}-networkExport.csv`);
+      if (isDataValid) {
+        downloadFileFromBlob(response.data, `${PROJECT_ID}-networkExport.csv`);
+      }
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: NETWORK_DOWNLOAD_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
     }
   },
 );
diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts
index 27a63ef2a593b6e1fd2f031870ae4b489ba6ba25..70126c71e6dbd43d510b1662096b9178d20452f1 100644
--- a/src/redux/map/map.constants.ts
+++ b/src/redux/map/map.constants.ts
@@ -54,3 +54,11 @@ export const MAP_INITIAL_STATE: MapState = {
   error: { name: '', message: '' },
   openedMaps: OPENED_MAPS_INITIAL_STATE,
 };
+
+export const INIT_MAP_SIZE_MODEL_ID_ERROR_PREFIX = 'Failed to initialize map size and model ID';
+
+export const INIT_MAP_POSITION_ERROR_PREFIX = 'Failed to initialize map position';
+
+export const INIT_MAP_BACKGROUND_ERROR_PREFIX = 'Failed to initialize map background';
+
+export const INIT_OPENED_MAPS_ERROR_PREFIX = 'Failed to initialize opened maps';
diff --git a/src/redux/map/map.thunks.test.ts b/src/redux/map/map.thunks.test.ts
index 6d5e94a4f207d2e0e176a3a1c18e4744a0bcc893..bdcf388d147f15d8d91dd8976c00bae44e0f04b8 100644
--- a/src/redux/map/map.thunks.test.ts
+++ b/src/redux/map/map.thunks.test.ts
@@ -2,16 +2,16 @@ import { MODELS_MOCK } from '@/models/mocks/modelsMock';
 /* eslint-disable no-magic-numbers */
 import { QueryData } from '@/types/query';
 import { BACKGROUNDS_MOCK, BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock';
-import { RootState } from '../store';
-import { INITIAL_STORE_STATE_MOCK } from '../root/root.fixtures';
 import { MODELS_INITIAL_STATE_MOCK } from '../models/models.mock';
+import { INITIAL_STORE_STATE_MOCK } from '../root/root.fixtures';
+import { RootState } from '../store';
+import { initialMapDataFixture, initialMapStateFixture } from './map.fixtures';
 import {
   getBackgroundId,
   getInitMapPosition,
   getInitMapSizeAndModelId,
   getOpenedMaps,
 } from './map.thunks';
-import { initialMapDataFixture, initialMapStateFixture } from './map.fixtures';
 
 const EMPTY_QUERY_DATA: QueryData = {
   modelId: undefined,
@@ -84,8 +84,8 @@ describe('map thunks - utils', () => {
     it('should return valid map position if query params do not include position', () => {
       const position = getInitMapPosition(STATE_WITH_MODELS, EMPTY_QUERY_DATA);
       expect(position).toEqual({
-        initial: { x: 13389.625, y: 6751.5, z: 5 },
-        last: { x: 13389.625, y: 6751.5, z: 5 },
+        initial: { x: 13389.625, y: 6751.5, z: 4 },
+        last: { x: 13389.625, y: 6751.5, z: 4 },
       });
     });
     it('should return default map position', () => {
diff --git a/src/redux/map/map.thunks.ts b/src/redux/map/map.thunks.ts
index e9c3c7ae8703638ab1942c366cfb6ecc32006955..05b675bba64d223d63cab0865e8692f5f9620da4 100644
--- a/src/redux/map/map.thunks.ts
+++ b/src/redux/map/map.thunks.ts
@@ -5,6 +5,8 @@ import { QueryData } from '@/types/query';
 import { DEFAULT_ZOOM } from '@/constants/map';
 import { getPointMerged } from '@/utils/object/getPointMerged';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import type { AppDispatch, RootState } from '../store';
 import {
   InitMapBackgroundActionPayload,
@@ -26,7 +28,14 @@ import {
   modelByIdSelector,
   modelsDataSelector,
 } from '../models/models.selectors';
-import { DEFAULT_POSITION, MAIN_MAP } from './map.constants';
+import {
+  DEFAULT_POSITION,
+  MAIN_MAP,
+  INIT_MAP_BACKGROUND_ERROR_PREFIX,
+  INIT_MAP_POSITION_ERROR_PREFIX,
+  INIT_MAP_SIZE_MODEL_ID_ERROR_PREFIX,
+  INIT_OPENED_MAPS_ERROR_PREFIX,
+} from './map.constants';
 
 /** UTILS - in the same file because of dependancy cycle */
 
@@ -135,47 +144,62 @@ export const getOpenedMaps = (state: RootState, queryData: QueryData): OppenedMa
 export const initMapSizeAndModelId = createAsyncThunk<
   InitMapSizeAndModelIdActionPayload,
   InitMapSizeAndModelIdParams,
-  { dispatch: AppDispatch; state: RootState }
->(
-  'map/initMapSizeAndModelId',
-  async ({ queryData }, { getState }): Promise<InitMapSizeAndModelIdActionPayload> => {
+  { dispatch: AppDispatch; state: RootState } & ThunkConfig
+>('map/initMapSizeAndModelId', async ({ queryData }, { getState, rejectWithValue }) => {
+  try {
     const state = getState();
 
     return getInitMapSizeAndModelId(state, queryData);
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: INIT_MAP_SIZE_MODEL_ID_ERROR_PREFIX });
+
+    return rejectWithValue(errorMessage);
+  }
+});
 
 export const initMapPosition = createAsyncThunk<
   InitMapPositionActionPayload,
   InitMapPositionParams,
-  { dispatch: AppDispatch; state: RootState }
->(
-  'map/initMapPosition',
-  async ({ queryData }, { getState }): Promise<InitMapPositionActionPayload> => {
+  { dispatch: AppDispatch; state: RootState } & ThunkConfig
+>('map/initMapPosition', async ({ queryData }, { getState, rejectWithValue }) => {
+  try {
     const state = getState();
 
     return getInitMapPosition(state, queryData);
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: INIT_MAP_POSITION_ERROR_PREFIX });
+
+    return rejectWithValue(errorMessage);
+  }
+});
 
 export const initMapBackground = createAsyncThunk<
   InitMapBackgroundActionPayload,
   InitMapBackgroundParams,
-  { dispatch: AppDispatch; state: RootState }
->(
-  'map/initMapBackground',
-  async ({ queryData }, { getState }): Promise<InitMapBackgroundActionPayload> => {
+  { dispatch: AppDispatch; state: RootState } & ThunkConfig
+>('map/initMapBackground', async ({ queryData }, { getState, rejectWithValue }) => {
+  try {
     const state = getState();
     return getBackgroundId(state, queryData);
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: INIT_MAP_BACKGROUND_ERROR_PREFIX });
+
+    return rejectWithValue(errorMessage);
+  }
+});
 
 export const initOpenedMaps = createAsyncThunk<
   InitOpenedMapsActionPayload,
   InitOpenedMapsProps,
-  { dispatch: AppDispatch; state: RootState }
->('appInit/initOpenedMaps', async ({ queryData }, { getState }): Promise<OppenedMap[]> => {
-  const state = getState();
+  { dispatch: AppDispatch; state: RootState } & ThunkConfig
+>('appInit/initOpenedMaps', async ({ queryData }, { getState, rejectWithValue }) => {
+  try {
+    const state = getState();
+
+    return getOpenedMaps(state, queryData);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: INIT_OPENED_MAPS_ERROR_PREFIX });
 
-  return getOpenedMaps(state, queryData);
+    return rejectWithValue(errorMessage);
+  }
 });
diff --git a/src/redux/middlewares/error.middleware.test.ts b/src/redux/middlewares/error.middleware.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7662714e2f08a2dccd3a6329db08716d0103bc2d
--- /dev/null
+++ b/src/redux/middlewares/error.middleware.test.ts
@@ -0,0 +1,87 @@
+import { showToast } from '@/utils/showToast';
+import { errorMiddlewareListener } from './error.middleware';
+
+jest.mock('../../utils/showToast', () => ({
+  showToast: jest.fn(),
+}));
+
+describe('errorMiddlewareListener', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should show toast with error message when action is rejected with value', async () => {
+    const action = {
+      type: 'action/rejected',
+      payload: 'Error message',
+      meta: {
+        requestId: '421',
+        rejectedWithValue: true,
+        requestStatus: 'rejected',
+      },
+    };
+    await errorMiddlewareListener(action);
+    expect(showToast).toHaveBeenCalledWith({ type: 'error', message: 'Error message' });
+  });
+
+  it('should show toast with unknown error when action is rejected without value', async () => {
+    const action = {
+      type: 'action/rejected',
+      payload: null,
+      meta: {
+        requestId: '421',
+        rejectedWithValue: true,
+        requestStatus: 'rejected',
+      },
+    };
+    await errorMiddlewareListener(action);
+    expect(showToast).toHaveBeenCalledWith({
+      type: 'error',
+      message: 'An unknown error occurred. Please try again later.',
+    });
+  });
+
+  it('should not show toast when action is not rejected', async () => {
+    const action = {
+      type: 'action/loading',
+      payload: null,
+      meta: {
+        requestId: '421',
+        requestStatus: 'fulfilled',
+      },
+    };
+    await errorMiddlewareListener(action);
+    expect(showToast).not.toHaveBeenCalled();
+  });
+
+  it('should show toast with unknown error when action payload is not a string', async () => {
+    const action = {
+      type: 'action/rejected',
+      payload: {},
+      meta: {
+        requestId: '421',
+        rejectedWithValue: true,
+        requestStatus: 'rejected',
+      },
+    };
+    await errorMiddlewareListener(action);
+    expect(showToast).toHaveBeenCalledWith({
+      type: 'error',
+      message: 'An unknown error occurred. Please try again later.',
+    });
+  });
+
+  it('should show toast with custom message when action payload is a string', async () => {
+    const action = {
+      type: 'action/rejected',
+      payload: 'Failed to fetch',
+      meta: {
+        requestId: '421',
+        rejectedWithValue: true,
+        requestStatus: 'rejected',
+      },
+    };
+    await errorMiddlewareListener(action);
+    expect(showToast).toHaveBeenCalledWith({ type: 'error', message: 'Failed to fetch' });
+  });
+});
diff --git a/src/redux/middlewares/error.middleware.ts b/src/redux/middlewares/error.middleware.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ba6a0f75452822908c449ba4d3dcb0ac59dddd9
--- /dev/null
+++ b/src/redux/middlewares/error.middleware.ts
@@ -0,0 +1,34 @@
+import type { AppStartListening } from '@/redux/store';
+import { UNKNOWN_ERROR } from '@/utils/getErrorMessage/getErrorMessage.constants';
+import { showToast } from '@/utils/showToast';
+import {
+  Action,
+  createListenerMiddleware,
+  isRejected,
+  isRejectedWithValue,
+} from '@reduxjs/toolkit';
+
+export const errorListenerMiddleware = createListenerMiddleware();
+
+const startListening = errorListenerMiddleware.startListening as AppStartListening;
+
+export const errorMiddlewareListener = async (action: Action): Promise<void> => {
+  if (isRejectedWithValue(action)) {
+    let message: string;
+    if (typeof action.payload === 'string') {
+      message = action.payload;
+    } else {
+      message = UNKNOWN_ERROR;
+    }
+
+    showToast({
+      type: 'error',
+      message,
+    });
+  }
+};
+
+startListening({
+  matcher: isRejected,
+  effect: errorMiddlewareListener,
+});
diff --git a/src/redux/models/models.constants.ts b/src/redux/models/models.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8855ad369fa1edf8e68a99b6171983a52ea42793
--- /dev/null
+++ b/src/redux/models/models.constants.ts
@@ -0,0 +1 @@
+export const MODELS_FETCHING_ERROR_PREFIX = 'Failed to fetch models';
diff --git a/src/redux/models/models.reducers.test.ts b/src/redux/models/models.reducers.test.ts
index 1677afdfd86b83f6a1ea7834cbc15ceb0d71018e..fc6e0af1e28e47a3bb0e2ed3a38186549c053907 100644
--- a/src/redux/models/models.reducers.test.ts
+++ b/src/redux/models/models.reducers.test.ts
@@ -44,10 +44,13 @@ describe('models reducer', () => {
   it('should update store after failed getModels query', async () => {
     mockedAxiosClient.onGet(apiPath.getModelsString()).reply(HttpStatusCode.NotFound, []);
 
-    const { type } = await store.dispatch(getModels());
+    const { type, payload } = await store.dispatch(getModels());
     const { data, loading, error } = store.getState().models;
 
     expect(type).toBe('project/getModels/rejected');
+    expect(payload).toBe(
+      "Failed to fetch models: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
     expect(data).toEqual([]);
diff --git a/src/redux/models/models.thunks.ts b/src/redux/models/models.thunks.ts
index 5880ddcd4cd8f2494790f321e05fac0ab0c25021..2e0fd68d89e09cff7b66e6c5e1aefff3acc15486 100644
--- a/src/redux/models/models.thunks.ts
+++ b/src/redux/models/models.thunks.ts
@@ -2,17 +2,26 @@ import { mapModelSchema } from '@/models/modelSchema';
 import { apiPath } from '@/redux/apiPath';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { MapModel } from '@/types/models';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
+import { ThunkConfig } from '@/types/store';
+import { MODELS_FETCHING_ERROR_PREFIX } from './models.constants';
 
-export const getModels = createAsyncThunk(
+export const getModels = createAsyncThunk<MapModel[] | undefined, void, ThunkConfig>(
   'project/getModels',
-  async (): Promise<MapModel[] | undefined> => {
-    const response = await axiosInstance.get<MapModel[]>(apiPath.getModelsString());
+  async (_, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<MapModel[]>(apiPath.getModelsString());
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapModelSchema));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapModelSchema));
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: MODELS_FETCHING_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.constants.ts b/src/redux/overlayBioEntity/overlayBioEntity.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5b9a636a6b2b287f4ea7f1e259df1b9375df4c1c
--- /dev/null
+++ b/src/redux/overlayBioEntity/overlayBioEntity.constants.ts
@@ -0,0 +1,4 @@
+export const OVERLAY_BIO_ENTITY_FETCHING_ERROR_PREFIX = 'Failed to fetch overlay bio entity';
+export const OVERLAY_BIO_ENTITY_ALL_MODELS_FETCHING_ERROR_PREFIX =
+  'Failed to fetch overlay bio entity for all models';
+export const INIT_OVERLAYS_ERROR_PREFIX = 'Failed to initialize overlays';
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
index 1fed7a9709ff7807203437b18647776fbd667ee1..30222f300c8a31f6d6104e23634c9d9b0d6d33ac 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts
@@ -3,6 +3,8 @@ import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { OverlayBioEntity } from '@/types/models';
 import { OverlayBioEntityRender } from '@/types/OLrendering';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import {
   getValidOverlayBioEntities,
   parseOverlayBioEntityToOlRenderingFormat,
@@ -13,18 +15,23 @@ import type { RootState } from '../store';
 import { setMapBackground } from '../map/map.slice';
 import { emptyBackgroundIdSelector } from '../backgrounds/background.selectors';
 import { overlaySelector, userOverlaySelector } from '../overlays/overlays.selectors';
+import {
+  INIT_OVERLAYS_ERROR_PREFIX,
+  OVERLAY_BIO_ENTITY_ALL_MODELS_FETCHING_ERROR_PREFIX,
+  OVERLAY_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+} from './overlayBioEntity.constants';
 
 type GetOverlayBioEntityThunkProps = {
   overlayId: number;
   modelId: number;
 };
 
-export const getOverlayBioEntity = createAsyncThunk(
-  'overlayBioEntity/getOverlayBioEntity',
-  async ({
-    overlayId,
-    modelId,
-  }: GetOverlayBioEntityThunkProps): Promise<OverlayBioEntityRender[] | undefined> => {
+export const getOverlayBioEntity = createAsyncThunk<
+  OverlayBioEntityRender[] | undefined,
+  GetOverlayBioEntityThunkProps,
+  ThunkConfig
+>('overlayBioEntity/getOverlayBioEntity', async ({ overlayId, modelId }, { rejectWithValue }) => {
+  try {
     const response = await axiosInstanceNewAPI.get<OverlayBioEntity[]>(
       apiPath.getOverlayBioEntity({ overlayId, modelId }),
       {
@@ -39,34 +46,55 @@ export const getOverlayBioEntity = createAsyncThunk(
     }
 
     return undefined;
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({
+      error,
+      prefix: OVERLAY_BIO_ENTITY_FETCHING_ERROR_PREFIX,
+    });
+
+    return rejectWithValue(errorMessage);
+  }
+});
 
 type GetOverlayBioEntityForAllModelsThunkProps = { overlayId: number };
 
 export const getOverlayBioEntityForAllModels = createAsyncThunk<
   void,
   GetOverlayBioEntityForAllModelsThunkProps,
-  { state: RootState }
+  { state: RootState } & ThunkConfig
 >(
   'overlayBioEntity/getOverlayBioEntityForAllModels',
-  async ({ overlayId }, { dispatch, getState }): Promise<void> => {
-    const state = getState();
-    const modelsIds = modelsIdsSelector(state);
+  // eslint-disable-next-line consistent-return
+  async ({ overlayId }, { dispatch, getState, rejectWithValue }) => {
+    try {
+      const state = getState();
+      const modelsIds = modelsIdsSelector(state);
 
-    const asyncGetOverlayBioEntityFunctions = modelsIds.map(id =>
-      dispatch(getOverlayBioEntity({ overlayId, modelId: id })),
-    );
+      const asyncGetOverlayBioEntityFunctions = modelsIds.map(id =>
+        dispatch(getOverlayBioEntity({ overlayId, modelId: id })),
+      );
 
-    await Promise.all(asyncGetOverlayBioEntityFunctions);
+      await Promise.all(asyncGetOverlayBioEntityFunctions);
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: OVERLAY_BIO_ENTITY_ALL_MODELS_FETCHING_ERROR_PREFIX,
+      });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
 type GetInitOverlaysProps = { overlaysId: number[] };
 
-export const getInitOverlays = createAsyncThunk<void, GetInitOverlaysProps, { state: RootState }>(
-  'appInit/getInitOverlays',
-  async ({ overlaysId }, { dispatch, getState }): Promise<void> => {
+export const getInitOverlays = createAsyncThunk<
+  void,
+  GetInitOverlaysProps,
+  { state: RootState } & ThunkConfig
+  // eslint-disable-next-line consistent-return
+>('appInit/getInitOverlays', async ({ overlaysId }, { dispatch, getState, rejectWithValue }) => {
+  try {
     const state = getState();
 
     const emptyBackgroundId = emptyBackgroundIdSelector(state);
@@ -86,5 +114,9 @@ export const getInitOverlays = createAsyncThunk<void, GetInitOverlaysProps, { st
 
       dispatch(getOverlayBioEntityForAllModels({ overlayId: id }));
     });
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: INIT_OVERLAYS_ERROR_PREFIX });
+
+    return rejectWithValue(errorMessage);
+  }
+});
diff --git a/src/redux/overlays/overlays.constants.ts b/src/redux/overlays/overlays.constants.ts
index dda564cda0098e449d5adadb219d86b8320378e4..fa7efe227279b72b850a0e256771b6bb6288163b 100644
--- a/src/redux/overlays/overlays.constants.ts
+++ b/src/redux/overlays/overlays.constants.ts
@@ -1,2 +1,11 @@
 /* eslint-disable no-magic-numbers */
 export const CHUNK_SIZE = 65535 * 8;
+
+export const OVERLAYS_FETCHING_ERROR_PREFIX = 'Failed to fetch overlays';
+export const USER_OVERLAY_ADD_ERROR_PREFIX = 'Failed to add user overlay';
+export const USER_OVERLAY_ADD_SUCCESS_MESSAGE = 'User overlay added successfully';
+export const USER_OVERLAYS_FETCHING_ERROR_PREFIX = 'Failed to fetch user overlays';
+export const USER_OVERLAY_UPDATE_ERROR_PREFIX = 'Failed to update user overlay';
+export const USER_OVERLAY_UPDATE_SUCCESS_MESSAGE = 'User overlay updated successfully';
+export const USER_OVERLAY_REMOVE_ERROR_PREFIX = 'Failed to remove user overlay';
+export const USER_OVERLAY_REMOVE_SUCCESS_MESSAGE = 'User overlay removed successfully';
diff --git a/src/redux/overlays/overlays.reducers.test.ts b/src/redux/overlays/overlays.reducers.test.ts
index f774974f526383c5ed83a27c9343f7a758e2478c..38b4652f0f0c29e09c3917d9de1160f9dc27fc4a 100644
--- a/src/redux/overlays/overlays.reducers.test.ts
+++ b/src/redux/overlays/overlays.reducers.test.ts
@@ -80,10 +80,13 @@ describe('overlays reducer', () => {
       .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true }))
       .reply(HttpStatusCode.NotFound, []);
 
-    const { type } = await store.dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID));
+    const { type, payload } = await store.dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID));
     const { data, loading, error } = store.getState().overlays;
 
     expect(type).toBe('overlays/getAllPublicOverlaysByProjectId/rejected');
+    expect(payload).toBe(
+      "Failed to fetch overlays: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
     expect(data).toEqual([]);
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
index 6331642162ed8cfcc3b37f756bcbafbabf74a1a2..4b1fa460e4d60e8604d9bf36a351597d9db3929f 100644
--- a/src/redux/overlays/overlays.thunks.ts
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -11,51 +11,75 @@ import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { showToast } from '@/utils/showToast';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
-import { CHUNK_SIZE } from './overlays.constants';
+import {
+  CHUNK_SIZE,
+  OVERLAYS_FETCHING_ERROR_PREFIX,
+  USER_OVERLAYS_FETCHING_ERROR_PREFIX,
+  USER_OVERLAY_ADD_ERROR_PREFIX,
+  USER_OVERLAY_ADD_SUCCESS_MESSAGE,
+  USER_OVERLAY_REMOVE_ERROR_PREFIX,
+  USER_OVERLAY_REMOVE_SUCCESS_MESSAGE,
+  USER_OVERLAY_UPDATE_ERROR_PREFIX,
+  USER_OVERLAY_UPDATE_SUCCESS_MESSAGE,
+} from './overlays.constants';
 import { closeModal } from '../modal/modal.slice';
 import type { RootState } from '../store';
 
-export const getAllPublicOverlaysByProjectId = createAsyncThunk(
+export const getAllPublicOverlaysByProjectId = createAsyncThunk<MapOverlay[], string, ThunkConfig>(
   'overlays/getAllPublicOverlaysByProjectId',
-  async (projectId: string): Promise<MapOverlay[]> => {
-    const response = await axiosInstance.get<MapOverlay[]>(
-      apiPath.getAllOverlaysByProjectIdQuery(projectId, { publicOverlay: true }),
-    );
+  async (projectId: string, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<MapOverlay[]>(
+        apiPath.getAllOverlaysByProjectIdQuery(projectId, { publicOverlay: true }),
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
 
-    return isDataValid ? response.data : [];
+      return isDataValid ? response.data : [];
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: OVERLAYS_FETCHING_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
-export const getAllUserOverlaysByCreator = createAsyncThunk(
+export const getAllUserOverlaysByCreator = createAsyncThunk<MapOverlay[], void, ThunkConfig>(
   'overlays/getAllUserOverlaysByCreator',
-  async (_, { getState }): Promise<MapOverlay[]> => {
-    const state = getState() as RootState;
-    const creator = state.user.login;
-    if (!creator) return [];
-
-    const response = await axiosInstance<MapOverlay[]>(
-      apiPath.getAllUserOverlaysByCreatorQuery({
-        creator,
-        publicOverlay: false,
-      }),
-      {
-        withCredentials: true,
-      },
-    );
+  async (_, { getState, rejectWithValue }) => {
+    try {
+      const state = getState() as RootState;
+      const creator = state.user.login;
+      if (!creator) return [];
+
+      const response = await axiosInstance<MapOverlay[]>(
+        apiPath.getAllUserOverlaysByCreatorQuery({
+          creator,
+          publicOverlay: false,
+        }),
+        {
+          withCredentials: true,
+        },
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
 
-    const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => {
-      if (userOverlayA.order > userOverlayB.order) return 1;
-      return -1;
-    };
+      const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => {
+        if (userOverlayA.order > userOverlayB.order) return 1;
+        return -1;
+      };
 
-    const sortedUserOverlays = response.data.sort(sortByOrder);
+      const sortedUserOverlays = response.data.sort(sortByOrder);
 
-    return isDataValid ? sortedUserOverlays : [];
+      return isDataValid ? sortedUserOverlays : [];
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: USER_OVERLAYS_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
@@ -168,68 +192,93 @@ type AddOverlayArgs = {
   projectId: string;
 };
 
-export const addOverlay = createAsyncThunk(
+export const addOverlay = createAsyncThunk<undefined, AddOverlayArgs, ThunkConfig>(
   'overlays/addOverlay',
   async (
-    { filename, content, description, name, type, projectId }: AddOverlayArgs,
-    { dispatch },
-  ): Promise<void> => {
-    const createdFile = await createFile({
-      filename,
-      content,
-    });
-
-    await uploadContent({
-      createdFile,
-      overlayContent: content,
-    });
-
-    await creteOverlay({
-      createdFile,
-      description,
-      name,
-      type,
-      projectId,
-    });
-
-    dispatch(getAllUserOverlaysByCreator());
+    { filename, content, description, name, type, projectId },
+    { rejectWithValue, dispatch },
+    // eslint-disable-next-line consistent-return
+  ) => {
+    try {
+      const createdFile = await createFile({
+        filename,
+        content,
+      });
+
+      await uploadContent({
+        createdFile,
+        overlayContent: content,
+      });
+
+      await creteOverlay({
+        createdFile,
+        description,
+        name,
+        type,
+        projectId,
+      });
+
+      await dispatch(getAllUserOverlaysByCreator());
+
+      showToast({ type: 'success', message: USER_OVERLAY_ADD_SUCCESS_MESSAGE });
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: USER_OVERLAY_ADD_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
-export const updateOverlays = createAsyncThunk(
+export const updateOverlays = createAsyncThunk<undefined, MapOverlay[], ThunkConfig>(
   'overlays/updateOverlays',
-  async (userOverlays: MapOverlay[]): Promise<void> => {
-    const userOverlaysPromises = userOverlays.map(userOverlay =>
-      axiosInstance.patch<MapOverlay>(
-        apiPath.updateOverlay(userOverlay.idObject),
-        {
-          overlay: userOverlay,
-        },
-        {
-          withCredentials: true,
-        },
-      ),
-    );
-
-    const userOverlaysResponses = await Promise.all(userOverlaysPromises);
-
-    const updatedUserOverlays = userOverlaysResponses.map(
-      updatedUserOverlay => updatedUserOverlay.data,
-    );
-
-    validateDataUsingZodSchema(updatedUserOverlays, z.array(mapOverlay));
+  // eslint-disable-next-line consistent-return
+  async (userOverlays, { rejectWithValue }) => {
+    try {
+      const userOverlaysPromises = userOverlays.map(userOverlay =>
+        axiosInstance.patch<MapOverlay>(
+          apiPath.updateOverlay(userOverlay.idObject),
+          {
+            overlay: userOverlay,
+          },
+          {
+            withCredentials: true,
+          },
+        ),
+      );
+
+      const userOverlaysResponses = await Promise.all(userOverlaysPromises);
+
+      const updatedUserOverlays = userOverlaysResponses.map(
+        updatedUserOverlay => updatedUserOverlay.data,
+      );
+
+      validateDataUsingZodSchema(updatedUserOverlays, z.array(mapOverlay));
+
+      showToast({ type: 'success', message: USER_OVERLAY_UPDATE_SUCCESS_MESSAGE });
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: USER_OVERLAY_UPDATE_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
-export const removeOverlay = createAsyncThunk(
+export const removeOverlay = createAsyncThunk<undefined, { overlayId: number }, ThunkConfig>(
   'overlays/removeOverlay',
-  async ({ overlayId }: { overlayId: number }, thunkApi): Promise<void> => {
-    await axiosInstance.delete(apiPath.removeOverlay(overlayId), {
-      withCredentials: true,
-    });
+  // eslint-disable-next-line consistent-return
+  async ({ overlayId }, { dispatch, rejectWithValue }) => {
+    try {
+      await axiosInstance.delete(apiPath.removeOverlay(overlayId), {
+        withCredentials: true,
+      });
+
+      PluginsEventBus.dispatchEvent('onRemoveDataOverlay', overlayId);
+      await dispatch(getAllUserOverlaysByCreator());
+      dispatch(closeModal());
 
-    PluginsEventBus.dispatchEvent('onRemoveDataOverlay', overlayId);
-    await thunkApi.dispatch(getAllUserOverlaysByCreator());
-    thunkApi.dispatch(closeModal());
+      showToast({ type: 'success', message: USER_OVERLAY_REMOVE_SUCCESS_MESSAGE });
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: USER_OVERLAY_REMOVE_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/plugins/plugins.constants.ts b/src/redux/plugins/plugins.constants.ts
index cdd616c5580b1f31c7837bfc2cc84e3d1d7940cb..812e85036ae0588e93006646aa700663aa88c0f5 100644
--- a/src/redux/plugins/plugins.constants.ts
+++ b/src/redux/plugins/plugins.constants.ts
@@ -15,3 +15,7 @@ export const PLUGINS_INITIAL_STATE: PluginsState = {
     currentPluginHash: undefined,
   },
 };
+
+export const PLUGIN_REGISTER_ERROR_PREFIX = 'Failed to register plugin';
+export const PLUGIN_INIT_FETCHING_ERROR_PREFIX = 'Failed to initialize fetching plugin';
+export const PLUGIN_FETCHING_ALL_ERROR_PREFIX = 'Failed to fetch all plugins';
diff --git a/src/redux/plugins/plugins.reducers.test.ts b/src/redux/plugins/plugins.reducers.test.ts
index edc59097d4d53fe239bace4d11948391b3857847..885f421215d099663d278f47f5498a04050ba6a3 100644
--- a/src/redux/plugins/plugins.reducers.test.ts
+++ b/src/redux/plugins/plugins.reducers.test.ts
@@ -69,7 +69,9 @@ describe('plugins reducer', () => {
     );
 
     expect(type).toBe('plugins/registerPlugin/rejected');
-    expect(payload).toEqual(undefined);
+    expect(payload).toEqual(
+      "Failed to register plugin: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     const { data, pluginsId } = store.getState().plugins.activePlugins;
 
     expect(data).toEqual({});
diff --git a/src/redux/plugins/plugins.thunks.ts b/src/redux/plugins/plugins.thunks.ts
index afe657f91d3473aa7cca56d7c2a90a1304a6d129..1d69a6cbb318e9a02a78ce882e9c638199ebd324 100644
--- a/src/redux/plugins/plugins.thunks.ts
+++ b/src/redux/plugins/plugins.thunks.ts
@@ -5,7 +5,14 @@ import type { MinervaPlugin } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import axios from 'axios';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
+import {
+  PLUGIN_FETCHING_ALL_ERROR_PREFIX,
+  PLUGIN_INIT_FETCHING_ERROR_PREFIX,
+  PLUGIN_REGISTER_ERROR_PREFIX,
+} from './plugins.constants';
 
 type RegisterPlugin = {
   hash: string;
@@ -15,38 +22,41 @@ type RegisterPlugin = {
   isPublic: boolean;
 };
 
-export const registerPlugin = createAsyncThunk(
+export const registerPlugin = createAsyncThunk<
+  MinervaPlugin | undefined,
+  RegisterPlugin,
+  ThunkConfig
+>(
   'plugins/registerPlugin',
-  async ({
-    hash,
-    isPublic,
-    pluginName,
-    pluginUrl,
-    pluginVersion,
-  }: RegisterPlugin): Promise<MinervaPlugin | undefined> => {
-    const payload = {
-      hash,
-      url: pluginUrl,
-      name: pluginName,
-      version: pluginVersion,
-      isPublic: isPublic.toString(),
-    } as const;
+  async ({ hash, isPublic, pluginName, pluginUrl, pluginVersion }, { rejectWithValue }) => {
+    try {
+      const payload = {
+        hash,
+        url: pluginUrl,
+        name: pluginName,
+        version: pluginVersion,
+        isPublic: isPublic.toString(),
+      } as const;
 
-    const response = await axiosInstance.post<MinervaPlugin>(
-      apiPath.registerPluign(),
-      new URLSearchParams(payload),
-      {
-        withCredentials: true,
-      },
-    );
+      const response = await axiosInstance.post<MinervaPlugin>(
+        apiPath.registerPluign(),
+        new URLSearchParams(payload),
+        {
+          withCredentials: true,
+        },
+      );
 
-    const isDataValid = validateDataUsingZodSchema(response.data, pluginSchema);
+      const isDataValid = validateDataUsingZodSchema(response.data, pluginSchema);
 
-    if (isDataValid) {
-      return response.data;
-    }
+      if (isDataValid) {
+        return response.data;
+      }
 
-    return undefined;
+      return undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: PLUGIN_REGISTER_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
@@ -61,38 +71,49 @@ type GetInitPluginsProps = {
   }) => void;
 };
 
-export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>(
+export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps, ThunkConfig>(
   'plugins/getInitPlugins',
-  async ({ pluginsId, setHashedPlugin }): Promise<void> => {
-    /* eslint-disable no-restricted-syntax, no-await-in-loop */
-    for (const pluginId of pluginsId) {
-      const res = await axiosInstance<MinervaPlugin>(apiPath.getPlugin(pluginId));
+  // eslint-disable-next-line consistent-return
+  async ({ pluginsId, setHashedPlugin }, { rejectWithValue }) => {
+    try {
+      /* eslint-disable no-restricted-syntax, no-await-in-loop */
+      for (const pluginId of pluginsId) {
+        const res = await axiosInstance<MinervaPlugin>(apiPath.getPlugin(pluginId));
 
-      const isDataValid = validateDataUsingZodSchema(res.data, pluginSchema);
+        const isDataValid = validateDataUsingZodSchema(res.data, pluginSchema);
 
-      if (isDataValid) {
-        const { urls } = res.data;
-        const scriptRes = await axios(urls[0]);
-        const pluginScript = scriptRes.data;
-        setHashedPlugin({ pluginUrl: urls[0], pluginScript });
+        if (isDataValid) {
+          const { urls } = res.data;
+          const scriptRes = await axios(urls[0]);
+          const pluginScript = scriptRes.data;
+          setHashedPlugin({ pluginUrl: urls[0], pluginScript });
 
-        /* eslint-disable no-new-func */
-        const loadPlugin = new Function(pluginScript);
-        loadPlugin();
+          /* eslint-disable no-new-func */
+          const loadPlugin = new Function(pluginScript);
+          loadPlugin();
+        }
       }
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: PLUGIN_INIT_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
     }
   },
 );
 
-export const getAllPlugins = createAsyncThunk(
+export const getAllPlugins = createAsyncThunk<MinervaPlugin[], void, ThunkConfig>(
   'plugins/getAllPlugins',
-  async (): Promise<MinervaPlugin[]> => {
-    const response = await axiosInstance.get<MinervaPlugin[]>(apiPath.getAllPlugins());
+  async (_, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<MinervaPlugin[]>(apiPath.getAllPlugins());
 
-    const isPluginDataValid = (pluginData: MinervaPlugin): boolean =>
-      validateDataUsingZodSchema(pluginData, pluginSchema);
-    const validPlugins = response.data.filter(isPluginDataValid);
+      const isPluginDataValid = (pluginData: MinervaPlugin): boolean =>
+        validateDataUsingZodSchema(pluginData, pluginSchema);
+      const validPlugins = response.data.filter(isPluginDataValid);
 
-    return validPlugins;
+      return validPlugins;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: PLUGIN_FETCHING_ALL_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/project/project.constants.ts b/src/redux/project/project.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46757eb5187a0e2e6e8720aa67af4ee97dbb31b4
--- /dev/null
+++ b/src/redux/project/project.constants.ts
@@ -0,0 +1 @@
+export const PROJECT_FETCHING_ERROR_PREFIX = 'Failed to fetch project by id';
diff --git a/src/redux/project/project.reducers.test.ts b/src/redux/project/project.reducers.test.ts
index 744f725261ca8605d915b0da3474c3300526d24d..f3c007766b44ea26789d010ffccb631808e843c0 100644
--- a/src/redux/project/project.reducers.test.ts
+++ b/src/redux/project/project.reducers.test.ts
@@ -47,10 +47,13 @@ describe('project reducer', () => {
   it('should update store after failed getProjectById query', async () => {
     mockedAxiosClient.onGet(apiPath.getProjectById(PROJECT_ID)).reply(HttpStatusCode.NotFound, []);
 
-    const { type } = await store.dispatch(getProjectById(PROJECT_ID));
+    const { type, payload } = await store.dispatch(getProjectById(PROJECT_ID));
     const { data, loading, error } = store.getState().project;
 
     expect(type).toBe('project/getProjectById/rejected');
+    expect(payload).toBe(
+      "Failed to fetch project by id: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
     expect(data).toEqual(undefined);
diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts
index 649f867d87cf8581543559dc5f2c2a2911ea3388..2ef8de8212f136df6f1a6810bea8e0ebf31806bc 100644
--- a/src/redux/project/project.thunks.ts
+++ b/src/redux/project/project.thunks.ts
@@ -3,15 +3,23 @@ import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
 import { Project } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
+import { PROJECT_FETCHING_ERROR_PREFIX } from './project.constants';
 
-export const getProjectById = createAsyncThunk(
+export const getProjectById = createAsyncThunk<Project | undefined, string, ThunkConfig>(
   'project/getProjectById',
-  async (id: string): Promise<Project | undefined> => {
-    const response = await axiosInstanceNewAPI.get<Project>(apiPath.getProjectById(id));
+  async (id, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstanceNewAPI.get<Project>(apiPath.getProjectById(id));
 
-    const isDataValid = validateDataUsingZodSchema(response.data, projectSchema);
+      const isDataValid = validateDataUsingZodSchema(response.data, projectSchema);
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: PROJECT_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/publications/publications.constatns.ts b/src/redux/publications/publications.constatns.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f5ea0a3dfe3f353ad71316f677d88888b68fd2a
--- /dev/null
+++ b/src/redux/publications/publications.constatns.ts
@@ -0,0 +1 @@
+export const PUBLICATIONS_FETCHING_ERROR_PREFIX = 'Problem with fetching publications';
diff --git a/src/redux/publications/publications.thunks.ts b/src/redux/publications/publications.thunks.ts
index e95a1d06a07c05963b29f22e740bab6e1e264ad4..f50b611eec013b7e09a0b9773775f6fafc9e4cba 100644
--- a/src/redux/publications/publications.thunks.ts
+++ b/src/redux/publications/publications.thunks.ts
@@ -3,16 +3,25 @@ import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { PublicationsResponse } from '@/types/models';
 import { publicationsResponseSchema } from '@/models/publicationsResponseSchema';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { GetPublicationsParams } from './publications.types';
 import { apiPath } from '../apiPath';
+import { PUBLICATIONS_FETCHING_ERROR_PREFIX } from './publications.constatns';
 
-export const getPublications = createAsyncThunk(
-  'publications/getPublications',
-  async (params: GetPublicationsParams): Promise<PublicationsResponse | undefined> => {
+export const getPublications = createAsyncThunk<
+  PublicationsResponse | undefined,
+  GetPublicationsParams,
+  ThunkConfig
+>('publications/getPublications', async (params, { rejectWithValue }) => {
+  try {
     const response = await axiosInstance.get<PublicationsResponse>(apiPath.getPublications(params));
 
     const isDataValid = validateDataUsingZodSchema(response.data, publicationsResponseSchema);
 
     return isDataValid ? response.data : undefined;
-  },
-);
+  } catch (error) {
+    const errorMessage = getErrorMessage({ error, prefix: PUBLICATIONS_FETCHING_ERROR_PREFIX });
+    return rejectWithValue(errorMessage);
+  }
+});
diff --git a/src/redux/reactions/reactions.constants.ts b/src/redux/reactions/reactions.constants.ts
index a7b9f099193540abc66d052d902faee2d5775c76..c5f51f9d4bfb67f52a7e543eb115c69bcec43eef 100644
--- a/src/redux/reactions/reactions.constants.ts
+++ b/src/redux/reactions/reactions.constants.ts
@@ -5,3 +5,5 @@ export const REACTIONS_INITIAL_STATE: ReactionsState = {
   loading: 'idle',
   error: { name: '', message: '' },
 };
+
+export const REACTIONS_FETCHING_ERROR_PREFIX = 'Failed to fetch reactions';
diff --git a/src/redux/reactions/reactions.thunks.ts b/src/redux/reactions/reactions.thunks.ts
index dbbf7fc725b6501dc9f4136705df1ae56b539ba4..fde44e5d8436d162a53b1b76e0eb0fb09b0753fb 100644
--- a/src/redux/reactions/reactions.thunks.ts
+++ b/src/redux/reactions/reactions.thunks.ts
@@ -2,20 +2,28 @@ import { reactionSchema } from '@/models/reaction';
 import { apiPath } from '@/redux/apiPath';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { Reaction } from '@/types/models';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
+import { ThunkConfig } from '@/types/store';
+import { REACTIONS_FETCHING_ERROR_PREFIX } from './reactions.constants';
 
-export const getReactionsByIds = createAsyncThunk<Reaction[] | undefined, number[]>(
+export const getReactionsByIds = createAsyncThunk<Reaction[] | undefined, number[], ThunkConfig>(
   'reactions/getByIds',
-  async (ids: number[]): Promise<Reaction[] | undefined> => {
-    const response = await axiosInstance.get<Reaction[]>(apiPath.getReactionsWithIds(ids));
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(reactionSchema));
+  async (ids, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<Reaction[]>(apiPath.getReactionsWithIds(ids));
+      const isDataValid = validateDataUsingZodSchema(response.data, z.array(reactionSchema));
 
-    if (!isDataValid) {
-      return undefined;
-    }
+      if (!isDataValid) {
+        return undefined;
+      }
 
-    return response.data;
+      return response.data;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: REACTIONS_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/search/search.constants.ts b/src/redux/search/search.constants.ts
index 962778465db9e927729e9369e826702d72b1a166..5c13f6fd0e196fd7b0a3119983d6dd229a77c072 100644
--- a/src/redux/search/search.constants.ts
+++ b/src/redux/search/search.constants.ts
@@ -5,3 +5,5 @@ export const SEARCH_INITIAL_STATE: SearchState = {
   perfectMatch: false,
   loading: 'idle',
 };
+
+export const DATA_SEARCHING_ERROR_PREFIX = 'Failed to search data';
diff --git a/src/redux/search/search.thunks.ts b/src/redux/search/search.thunks.ts
index 05debcd0c57d9a3adc4872ef02b7bb5d876c8e0f..078947d3586dc7be7319db5b4196992779539f2d 100644
--- a/src/redux/search/search.thunks.ts
+++ b/src/redux/search/search.thunks.ts
@@ -3,20 +3,34 @@ import { getMultiChemicals } from '@/redux/chemicals/chemicals.thunks';
 import { getMultiDrugs } from '@/redux/drugs/drugs.thunks';
 import { PerfectMultiSearchParams } from '@/types/search';
 import { createAsyncThunk } from '@reduxjs/toolkit';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import type { RootState } from '../store';
 import { dispatchPluginsEvents } from './search.thunks.utils';
+import { DATA_SEARCHING_ERROR_PREFIX } from './search.constants';
 
 type GetSearchDataProps = PerfectMultiSearchParams;
 
-export const getSearchData = createAsyncThunk<void, GetSearchDataProps, { state: RootState }>(
+export const getSearchData = createAsyncThunk<
+  void,
+  GetSearchDataProps,
+  { state: RootState } & ThunkConfig
+>(
   'project/getSearchData',
-  async ({ searchQueries, isPerfectMatch }, { dispatch, getState }): Promise<void> => {
-    await Promise.all([
-      dispatch(getMultiBioEntity({ searchQueries, isPerfectMatch })),
-      dispatch(getMultiDrugs(searchQueries)),
-      dispatch(getMultiChemicals(searchQueries)),
-    ]);
+  // eslint-disable-next-line consistent-return
+  async ({ searchQueries, isPerfectMatch }, { dispatch, getState, rejectWithValue }) => {
+    try {
+      await Promise.all([
+        dispatch(getMultiBioEntity({ searchQueries, isPerfectMatch })),
+        dispatch(getMultiDrugs(searchQueries)),
+        dispatch(getMultiChemicals(searchQueries)),
+      ]);
 
-    dispatchPluginsEvents(searchQueries, getState());
+      dispatchPluginsEvents(searchQueries, getState());
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: DATA_SEARCHING_ERROR_PREFIX });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/statistics/statistics.constants.ts b/src/redux/statistics/statistics.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8b28b57b0cd8e505631ebdc0a2e16d003cea8544
--- /dev/null
+++ b/src/redux/statistics/statistics.constants.ts
@@ -0,0 +1 @@
+export const STATISTICS_FETCHING_ERROR_PREFIX = 'Failed to fetch statistics';
diff --git a/src/redux/statistics/statistics.reducers.test.ts b/src/redux/statistics/statistics.reducers.test.ts
index af16b53be20193600a546122f08bba5e3d6f9b85..6d620db1720db9fa9f929ed84fe94c1cb32d8b0c 100644
--- a/src/redux/statistics/statistics.reducers.test.ts
+++ b/src/redux/statistics/statistics.reducers.test.ts
@@ -56,10 +56,13 @@ describe('statistics reducer', () => {
       .onGet(apiPath.getStatisticsById(PROJECT_ID))
       .reply(HttpStatusCode.NotFound, undefined);
 
-    const { type } = await store.dispatch(getStatisticsById(PROJECT_ID));
+    const { type, payload } = await store.dispatch(getStatisticsById(PROJECT_ID));
     const { loading } = store.getState().statistics;
 
     expect(type).toBe('statistics/getStatisticsById/rejected');
+    expect(payload).toBe(
+      "Failed to fetch statistics: The page you're looking for doesn't exist. Please verify the URL and try again.",
+    );
 
     waitFor(() => {
       expect(loading).toEqual('pending');
diff --git a/src/redux/statistics/statistics.thunks.ts b/src/redux/statistics/statistics.thunks.ts
index df5b6589b37a03472c231a24a0ea8fa4b0b7bbd2..1a683a4e3ea3e50238f1fce85b47dbde93b59b98 100644
--- a/src/redux/statistics/statistics.thunks.ts
+++ b/src/redux/statistics/statistics.thunks.ts
@@ -3,15 +3,23 @@ import { Statistics } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { statisticsSchema } from '@/models/statisticsSchema';
+import { getErrorMessage } from '@/utils/getErrorMessage';
+import { ThunkConfig } from '@/types/store';
 import { apiPath } from '../apiPath';
+import { STATISTICS_FETCHING_ERROR_PREFIX } from './statistics.constants';
 
-export const getStatisticsById = createAsyncThunk(
+export const getStatisticsById = createAsyncThunk<Statistics | undefined, string, ThunkConfig>(
   'statistics/getStatisticsById',
-  async (id: string): Promise<Statistics | undefined> => {
-    const response = await axiosInstance.get<Statistics>(apiPath.getStatisticsById(id));
+  async (id, { rejectWithValue }) => {
+    try {
+      const response = await axiosInstance.get<Statistics>(apiPath.getStatisticsById(id));
 
-    const isDataValid = validateDataUsingZodSchema(response.data, statisticsSchema);
+      const isDataValid = validateDataUsingZodSchema(response.data, statisticsSchema);
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({ error, prefix: STATISTICS_FETCHING_ERROR_PREFIX });
+      return rejectWithValue(errorMessage);
+    }
   },
 );
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 2391ea22a323314c9cb853939d9492a6d036e924..3fb15d8bb7a91d5e21285b67ba6fcf586ae93a2b 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -29,6 +29,7 @@ import { mapListenerMiddleware } from './map/middleware/map.middleware';
 import markersReducer from './markers/markers.slice';
 import pluginsReducer from './plugins/plugins.slice';
 import publicationsReducer from './publications/publications.slice';
+import { errorListenerMiddleware } from './middlewares/error.middleware';
 import statisticsReducer from './statistics/statistics.slice';
 
 export const reducers = {
@@ -58,7 +59,7 @@ export const reducers = {
   markers: markersReducer,
 };
 
-export const middlewares = [mapListenerMiddleware.middleware];
+export const middlewares = [mapListenerMiddleware.middleware, errorListenerMiddleware.middleware];
 
 export const store = configureStore({
   reducer: reducers,
diff --git a/src/redux/user/user.thunks.ts b/src/redux/user/user.thunks.ts
index 95c567c2af83b31f62eb05c30d219f6f17bdb6af..e6241bb6f2ecad36de3b16f7f73146951c40dc8b 100644
--- a/src/redux/user/user.thunks.ts
+++ b/src/redux/user/user.thunks.ts
@@ -4,21 +4,31 @@ import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { loginSchema } from '@/models/loginSchema';
 import { sessionSchemaValid } from '@/models/sessionValidSchema';
 import { Login, SessionValid } from '@/types/models';
+import { getErrorMessage } from '@/utils/getErrorMessage';
 import { apiPath } from '../apiPath';
 import { closeModal } from '../modal/modal.slice';
 
 export const login = createAsyncThunk(
   'user/login',
-  async (credentials: { login: string; password: string }, { dispatch }) => {
-    const searchParams = new URLSearchParams(credentials);
-    const response = await axiosInstance.post<Login>(apiPath.postLogin(), searchParams, {
-      withCredentials: true,
-    });
+  async (credentials: { login: string; password: string }, { dispatch, rejectWithValue }) => {
+    try {
+      const searchParams = new URLSearchParams(credentials);
+      const response = await axiosInstance.post<Login>(apiPath.postLogin(), searchParams, {
+        withCredentials: true,
+      });
 
-    const isDataValid = validateDataUsingZodSchema(response.data, loginSchema);
-    dispatch(closeModal());
+      const isDataValid = validateDataUsingZodSchema(response.data, loginSchema);
+      dispatch(closeModal());
 
-    return isDataValid ? response.data : undefined;
+      return isDataValid ? response.data : undefined;
+    } catch (error) {
+      const errorMessage = getErrorMessage({
+        error,
+        prefix: 'Login',
+      });
+
+      return rejectWithValue(errorMessage);
+    }
   },
 );
 
diff --git a/src/services/pluginsManager/errorMessages.ts b/src/services/pluginsManager/errorMessages.ts
index 7d35649435f1f99a73a8f6b84b267ac76b01143e..753b06f04cadabf975857a4a773c8574f3b1befb 100644
--- a/src/services/pluginsManager/errorMessages.ts
+++ b/src/services/pluginsManager/errorMessages.ts
@@ -1,6 +1,7 @@
 export const ERROR_MAP_NOT_FOUND = 'Map with provided id does not exist';
 export const ERROR_INVALID_QUERY_TYPE = 'Invalid query type. The query should be of string type';
 export const ERROR_INVALID_COORDINATES = 'Invalid coordinates type or values';
-export const ERROR_INVALID_MODEL_ID_TYPE =
-  'Invalid model id type. The model should be of number type';
+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_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL =
+  'Unable to retrieve the id of the active map: the modelId is not a number';
diff --git a/src/services/pluginsManager/map/data/getBounds.test.ts b/src/services/pluginsManager/map/data/getBounds.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99f587dab60eacdc703580964f06e5333f4f559c
--- /dev/null
+++ b/src/services/pluginsManager/map/data/getBounds.test.ts
@@ -0,0 +1,60 @@
+/* eslint-disable no-magic-numbers */
+import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
+import { store } from '@/redux/store';
+import { Map } from 'ol';
+import { MapManager } from '../mapManager';
+import { getBounds } from './getBounds';
+
+describe('getBounds', () => {
+  it('should return undefined if map instance does not exist', () => {
+    expect(getBounds()).toEqual(undefined);
+  });
+  it('should return current bounds if map instance exist', () => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({ target: dummyElement });
+    MapManager.setMapInstance(mapInstance);
+
+    jest.spyOn(mapInstance, 'getView').mockImplementation(
+      () =>
+        ({
+          calculateExtent: () => [
+            -14409068.309137221, 17994265.029590994, -13664805.690862779, 18376178.970409006,
+          ],
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+    );
+
+    const getStateSpy = jest.spyOn(store, 'getState');
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          map: {
+            data: {
+              ...MAP_DATA_INITIAL_STATE,
+              size: {
+                width: 26779.25,
+                height: 13503,
+                tileSize: 256,
+                minZoom: 2,
+                maxZoom: 9,
+              },
+            },
+            loading: 'idle',
+            error: {
+              name: '',
+              message: '',
+            },
+            openedMaps: [],
+          },
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+    );
+
+    expect(getBounds()).toEqual({
+      x1: 15044,
+      y1: 4441,
+      x2: 17034,
+      y2: 5461,
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/data/getBounds.ts b/src/services/pluginsManager/map/data/getBounds.ts
new file mode 100644
index 0000000000000000000000000000000000000000..626e13f705c08dcf163ed1a1017e99d857a7723b
--- /dev/null
+++ b/src/services/pluginsManager/map/data/getBounds.ts
@@ -0,0 +1,37 @@
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { store } from '@/redux/store';
+import { latLngToPoint } from '@/utils/map/latLngToPoint';
+import { toLonLat } from 'ol/proj';
+import { MapManager } from '../mapManager';
+
+type GetBoundsReturnType =
+  | {
+      x1: number;
+      x2: number;
+      y1: number;
+      y2: number;
+    }
+  | undefined;
+
+export const getBounds = (): GetBoundsReturnType => {
+  const mapInstance = MapManager.getMapInstance();
+
+  if (!mapInstance) return undefined;
+
+  const [minx, miny, maxx, maxy] = mapInstance.getView().calculateExtent(mapInstance.getSize());
+
+  const mapSize = mapDataSizeSelector(store.getState());
+
+  const [lngX1, latY1] = toLonLat([minx, maxy]);
+  const [lngX2, latY2] = toLonLat([maxx, miny]);
+
+  const { x: x1, y: y1 } = latLngToPoint([latY1, lngX1], mapSize, { rounded: true });
+  const { x: x2, y: y2 } = latLngToPoint([latY2, lngX2], mapSize, { rounded: true });
+
+  return {
+    x1,
+    y1,
+    x2,
+    y2,
+  };
+};
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.constants.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af85941c3340682b1b2644705b7e7c887e12182f
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.constants.ts
@@ -0,0 +1,5 @@
+import { HALF } from '@/constants/dividers';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
+
+const BOUNDS_PADDING = DEFAULT_TILE_SIZE / HALF;
+export const DEFAULT_PADDING = [BOUNDS_PADDING, BOUNDS_PADDING, BOUNDS_PADDING, BOUNDS_PADDING];
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f45aecfb1e53db5f3d515fdbe3c3722a1ff0f2e
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts
@@ -0,0 +1,122 @@
+/* eslint-disable no-magic-numbers */
+import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
+import { Map } from 'ol';
+import { store } from '@/redux/store';
+import { fitBounds } from './fitBounds';
+import { MapManager } from '../mapManager';
+
+jest.mock('../../../../redux/store');
+
+describe('fitBounds', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+    MapManager.mapInstance = null;
+  });
+  it('fitBounds should return undefined', () => {
+    expect(
+      fitBounds({
+        x1: 5,
+        y1: 10,
+        x2: 15,
+        y2: 20,
+      }),
+    ).toBe(undefined);
+  });
+
+  describe('when mapInstance is set', () => {
+    it('should call and set map instance view properly', () => {
+      const dummyElement = document.createElement('div');
+      const mapInstance = new Map({ target: dummyElement });
+      MapManager.setMapInstance(mapInstance);
+      const view = mapInstance.getView();
+      const getViewSpy = jest.spyOn(mapInstance, 'getView');
+      const fitSpy = jest.spyOn(view, 'fit');
+      const getStateSpy = jest.spyOn(store, 'getState');
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            map: {
+              data: {
+                ...MAP_DATA_INITIAL_STATE,
+                size: {
+                  width: 256,
+                  height: 256,
+                  tileSize: 256,
+                  minZoom: 1,
+                  maxZoom: 1,
+                },
+              },
+              loading: 'idle',
+              error: {
+                name: '',
+                message: '',
+              },
+              openedMaps: [],
+            },
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+          }) as any,
+      );
+
+      fitBounds({
+        x1: 10,
+        y1: 10,
+        x2: 15,
+        y2: 20,
+      });
+
+      expect(getViewSpy).toHaveBeenCalledTimes(1);
+      expect(fitSpy).toHaveBeenCalledWith([-18472078, 16906648, -17689363, 18472078], {
+        maxZoom: 1,
+        padding: [128, 128, 128, 128],
+        size: undefined,
+      });
+    });
+    it('should use max zoom value', () => {
+      const dummyElement = document.createElement('div');
+      const mapInstance = new Map({ target: dummyElement });
+      MapManager.setMapInstance(mapInstance);
+      const view = mapInstance.getView();
+      const getViewSpy = jest.spyOn(mapInstance, 'getView');
+      const fitSpy = jest.spyOn(view, 'fit');
+      const getStateSpy = jest.spyOn(store, 'getState');
+      getStateSpy.mockImplementation(
+        () =>
+          ({
+            map: {
+              data: {
+                ...MAP_DATA_INITIAL_STATE,
+                size: {
+                  width: 256,
+                  height: 256,
+                  tileSize: 256,
+                  minZoom: 1,
+                  maxZoom: 99,
+                },
+              },
+              loading: 'idle',
+              error: {
+                name: '',
+                message: '',
+              },
+              openedMaps: [],
+            },
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+          }) as any,
+      );
+
+      fitBounds({
+        x1: 10,
+        y1: 10,
+        x2: 15,
+        y2: 20,
+      });
+
+      expect(getViewSpy).toHaveBeenCalledTimes(1);
+      expect(fitSpy).toHaveBeenCalledWith([-18472078, 16906648, -17689363, 18472078], {
+        maxZoom: 99,
+        padding: [128, 128, 128, 128],
+        size: undefined,
+      });
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.ts
new file mode 100644
index 0000000000000000000000000000000000000000..079c6d9cc5c50bd5f1f5150a4e907de157a81498
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.ts
@@ -0,0 +1,45 @@
+import { FitOptions } from 'ol/View';
+import { boundingExtent } from 'ol/extent';
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { store } from '@/redux/store';
+import { MapManager } from '../mapManager';
+import { pointToProjection } from './fitBounds.utils';
+import { DEFAULT_PADDING } from './fitBounds.constants';
+
+type FitBoundsArgs = {
+  x1: number;
+  x2: number;
+  y1: number;
+  y2: number;
+};
+
+export const fitBounds = ({ x1, y1, x2, y2 }: FitBoundsArgs): void => {
+  const mapInstance = MapManager.getMapInstance();
+
+  if (!mapInstance) return;
+
+  const mapSize = mapDataSizeSelector(store.getState());
+
+  const points = [
+    {
+      x: x1,
+      y: y2,
+    },
+    {
+      x: x2,
+      y: y1,
+    },
+  ];
+
+  const coordinates = points.map(point => pointToProjection(point, mapSize));
+
+  const extent = boundingExtent(coordinates);
+
+  const options: FitOptions = {
+    size: mapInstance.getSize(),
+    padding: DEFAULT_PADDING,
+    maxZoom: mapSize.maxZoom,
+  };
+
+  mapInstance.getView().fit(extent, options);
+};
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.utils.test.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.utils.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d2f5cace7114605d75e196fed183c2521c477f0
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.utils.test.ts
@@ -0,0 +1,66 @@
+/* eslint-disable no-magic-numbers */
+import { pointToProjection } from './fitBounds.utils';
+
+describe('pointToProjection - util', () => {
+  describe('when mapSize arg is invalid', () => {
+    const validPoint = {
+      x: 0,
+      y: 0,
+    };
+
+    const invalidMapSize = {
+      width: -256 * 10,
+      height: -256 * 10,
+      tileSize: -256,
+      minZoom: -1,
+      maxZoom: -10,
+    };
+
+    it('should return fallback value on function call', () => {
+      expect(pointToProjection(validPoint, invalidMapSize)).toStrictEqual([0, -0]);
+    });
+  });
+
+  describe('when point and map size is valid', () => {
+    const validPoint = {
+      x: 256 * 100,
+      y: 256 * 100,
+    };
+
+    const validMapSize = {
+      width: 256 * 10,
+      height: 256 * 10,
+      tileSize: 256,
+      minZoom: 1,
+      maxZoom: 10,
+    };
+
+    const results = [380712659, -238107693];
+
+    it('should return valid lat lng value on function call', () => {
+      const [x, y] = pointToProjection(validPoint, validMapSize);
+
+      expect(x).toBe(results[0]);
+      expect(y).toBe(results[1]);
+    });
+  });
+  describe('when point arg is invalid', () => {
+    const invalidPoint = {
+      x: 'x',
+      y: 'y',
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    } as any;
+
+    const validMapSize = {
+      width: 256 * 10,
+      height: 256 * 10,
+      tileSize: 256,
+      minZoom: 1,
+      maxZoom: 10,
+    };
+
+    it('should return fallback value on function call', () => {
+      expect(pointToProjection(invalidPoint, validMapSize)).toStrictEqual([0, 0]);
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.utils.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de206bd4204bf1b3b86a36b2b95fed66f25621b9
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.utils.ts
@@ -0,0 +1,14 @@
+import { LATLNG_FALLBACK } from '@/constants/map';
+import { MapSize } from '@/redux/map/map.types';
+import { Point } from '@/types/map';
+import { pointToLngLat } from '@/utils/map/pointToLatLng';
+import { fromLonLat } from 'ol/proj';
+
+export const pointToProjection = (point: Point, mapSize: MapSize): number[] => {
+  const [lng, lat] = pointToLngLat(point, mapSize);
+  const projection = fromLonLat([lng, lat]);
+  const projectionRounded = projection.map(v => Math.round(v));
+  const isValid = !projectionRounded.some(v => Number.isNaN(v));
+
+  return isValid ? projectionRounded : LATLNG_FALLBACK;
+};
diff --git a/src/services/pluginsManager/map/fitBounds/index.ts b/src/services/pluginsManager/map/fitBounds/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28bd7717581efd4f6b5cb49e47cc974f9d5082e9
--- /dev/null
+++ b/src/services/pluginsManager/map/fitBounds/index.ts
@@ -0,0 +1 @@
+export { fitBounds } from './fitBounds';
diff --git a/src/services/pluginsManager/map/getOpenMapId.test.ts b/src/services/pluginsManager/map/getOpenMapId.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cdbe214786e9e884bf49fa1f70003e0569b018d5
--- /dev/null
+++ b/src/services/pluginsManager/map/getOpenMapId.test.ts
@@ -0,0 +1,40 @@
+import { RootState, store } from '@/redux/store';
+import { initialMapStateFixture } from '@/redux/map/map.fixtures';
+import { getOpenMapId } from './getOpenMapId';
+import { ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL } from '../errorMessages';
+
+describe('getOpenMapId', () => {
+  const getStateMock = jest.spyOn(store, 'getState');
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should return the modelId of the current map', () => {
+    getStateMock.mockImplementation(
+      () =>
+        ({
+          map: initialMapStateFixture,
+        }) as RootState,
+    );
+
+    expect(getOpenMapId()).toEqual(initialMapStateFixture.data.modelId);
+  });
+
+  it('should throw an error if modelId is not a number', () => {
+    getStateMock.mockImplementation(
+      () =>
+        ({
+          map: {
+            ...initialMapStateFixture,
+            data: {
+              ...initialMapStateFixture.data,
+              modelId: null,
+            },
+          },
+          // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        }) as any,
+    );
+
+    expect(() => getOpenMapId()).toThrowError(ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL);
+  });
+});
diff --git a/src/services/pluginsManager/map/getOpenMapId.ts b/src/services/pluginsManager/map/getOpenMapId.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd3cc808d3f66f423719c5dc0e4f84c54e55ad54
--- /dev/null
+++ b/src/services/pluginsManager/map/getOpenMapId.ts
@@ -0,0 +1,13 @@
+import { store } from '@/redux/store';
+import { ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL } from '../errorMessages';
+
+export const getOpenMapId = (): number => {
+  const currentMap = store.getState().map.data;
+  const openMapId = currentMap.modelId;
+
+  if (typeof openMapId !== 'number') {
+    throw new Error(ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL);
+  }
+
+  return openMapId;
+};
diff --git a/src/services/pluginsManager/map/mapManager.test.ts b/src/services/pluginsManager/map/mapManager.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f3349c99af786ee65b9ca85a57e8ae3e45415e8
--- /dev/null
+++ b/src/services/pluginsManager/map/mapManager.test.ts
@@ -0,0 +1,34 @@
+import { Map } from 'ol';
+
+import { MapInstance } from '@/types/map';
+import { MapManager } from './mapManager';
+
+describe('MapManager', () => {
+  describe('getMapInstance', () => {
+    it('should return null if no map instance is set', () => {
+      expect(MapManager.getMapInstance()).toBeNull();
+    });
+
+    it('should return the set map instance', () => {
+      const dummyElement = document.createElement('div');
+      const mapInstance = new Map({ target: dummyElement });
+      MapManager.setMapInstance(mapInstance);
+      expect(MapManager.getMapInstance()).toEqual(mapInstance);
+    });
+  });
+
+  describe('setMapInstance', () => {
+    beforeEach(() => {
+      MapManager.mapInstance = null;
+    });
+    it('should set the map instance', () => {
+      const dummyElement = document.createElement('div');
+      const mapInstance = new Map({ target: dummyElement });
+      MapManager.setMapInstance(mapInstance);
+      expect(MapManager.mapInstance).toEqual(mapInstance);
+    });
+    it('should throw error if map instance is not valid', () => {
+      expect(() => MapManager.setMapInstance({} as MapInstance)).toThrow('Not valid map instance');
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/mapManager.ts b/src/services/pluginsManager/map/mapManager.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a710f208d99e0f8fbf8bdfd521eba778b0708bc4
--- /dev/null
+++ b/src/services/pluginsManager/map/mapManager.ts
@@ -0,0 +1,18 @@
+import { MapInstance } from '@/types/map';
+import { Map } from 'ol';
+
+type MapManagerType = {
+  mapInstance: null | MapInstance;
+  setMapInstance: (mapInstance: MapInstance) => void;
+  getMapInstance: () => MapInstance | null;
+};
+
+export const MapManager: MapManagerType = {
+  mapInstance: null,
+
+  setMapInstance: (mapInstance: MapInstance) => {
+    if (!(mapInstance instanceof Map)) throw new Error('Not valid map instance');
+    MapManager.mapInstance = mapInstance;
+  },
+  getMapInstance: () => MapManager.mapInstance,
+};
diff --git a/src/services/pluginsManager/map/triggerSearch/getPolygonPoints.ts b/src/services/pluginsManager/map/triggerSearch/getPolygonPoints.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce27f4ba68a74409e4623fa2c737161df54c4261
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/getPolygonPoints.ts
@@ -0,0 +1,33 @@
+import { allVisibleBioEntitiesSelector } from '@/redux/bioEntity/bioEntity.selectors';
+import { store } from '@/redux/store';
+import { isPointValid } from '@/utils/point/isPointValid';
+
+type Points = {
+  x: number;
+  y: number;
+}[];
+
+export const getPolygonPoints = (): Points => {
+  const allVisibleBioEntities = allVisibleBioEntitiesSelector(store.getState());
+  const allX = allVisibleBioEntities.map(({ x }) => x);
+  const allY = allVisibleBioEntities.map(({ y }) => y);
+
+  const minX = Math.min(...allX);
+  const maxX = Math.max(...allX);
+
+  const minY = Math.min(...allY);
+  const maxY = Math.max(...allY);
+
+  const points = [
+    {
+      x: minX,
+      y: maxY,
+    },
+    {
+      x: maxX,
+      y: minY,
+    },
+  ];
+
+  return points.filter(isPointValid);
+};
diff --git a/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.test.ts b/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..47fc1165f558c948e10f02c45d7f274f045372fa
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.test.ts
@@ -0,0 +1,158 @@
+/* eslint-disable no-magic-numbers */
+import { MAP_INITIAL_STATE } from '@/redux/map/map.constants';
+import { RootState, store } from '@/redux/store';
+import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
+
+import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
+import { getVisibleBioEntitiesPolygonCoordinates } from './getVisibleBioEntitiesPolygonCoordinates';
+
+jest.mock('../../../../redux/store');
+
+const getStateSpy = jest.spyOn(store, 'getState');
+
+describe('getVisibleBioEntitiesPolygonCoordinates', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should return undefined if received array does not contain bioEntities with current map id', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
+          map: {
+            ...MAP_INITIAL_STATE,
+            data: {
+              ...MAP_INITIAL_STATE.data,
+              modelId: 5052,
+              size: {
+                width: 256,
+                height: 256,
+                tileSize: 256,
+                minZoom: 1,
+                maxZoom: 1,
+              },
+            },
+          },
+          bioEntity: {
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+          },
+          drugs: {
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+          },
+          drawer: {
+            ...DRAWER_INITIAL_STATE,
+            bioEntityDrawerState: {
+              bioentityId: undefined,
+              drugs: {},
+              chemicals: {},
+            },
+          },
+        }) as RootState,
+    );
+
+    expect(getVisibleBioEntitiesPolygonCoordinates()).toBe(undefined);
+  });
+  it('should return coordinates, max zoom, and map instance if received array contain bioEntities with current map id and max zoom', () => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
+          map: {
+            ...MAP_INITIAL_STATE,
+            data: {
+              ...MAP_INITIAL_STATE.data,
+              modelId: 52,
+              size: {
+                width: 256,
+                height: 256,
+                tileSize: 256,
+                minZoom: 1,
+                maxZoom: 5,
+              },
+            },
+          },
+          bioEntity: {
+            data: [
+              {
+                searchQueryElement: bioEntityContentFixture.bioEntity.name,
+                loading: 'succeeded',
+                error: { name: '', message: '' },
+                data: [
+                  {
+                    ...bioEntityContentFixture,
+                    bioEntity: {
+                      ...bioEntityContentFixture.bioEntity,
+                      model: 52,
+                      x: 97,
+                      y: 53,
+                      z: 1,
+                    },
+                  },
+                  {
+                    ...bioEntityContentFixture,
+                    bioEntity: {
+                      ...bioEntityContentFixture.bioEntity,
+                      model: 52,
+                      x: 12,
+                      y: 25,
+                      z: 1,
+                    },
+                  },
+                ],
+              },
+            ],
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+          },
+          drugs: {
+            data: [
+              {
+                searchQueryElement: '',
+                loading: 'succeeded',
+                error: { name: '', message: '' },
+                data: undefined,
+              },
+            ],
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+          },
+          chemicals: {
+            data: [
+              {
+                searchQueryElement: '',
+                loading: 'succeeded',
+                error: { name: '', message: '' },
+                data: undefined,
+              },
+            ],
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+          },
+          drawer: {
+            ...DRAWER_INITIAL_STATE,
+            bioEntityDrawerState: {
+              bioentityId: undefined,
+              drugs: {},
+              chemicals: {},
+            },
+            searchDrawerState: {
+              ...DRAWER_INITIAL_STATE.searchDrawerState,
+              selectedSearchElement: bioEntityContentFixture.bioEntity.name,
+            },
+          },
+        }) as RootState,
+    );
+
+    expect(getVisibleBioEntitiesPolygonCoordinates()).toEqual({
+      mapInstance: null,
+      maxZoom: 5,
+      polygonCoordinates: [
+        [-18158992, 11740728],
+        [-4852834, 16123932],
+      ],
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c1fba3388515df219601634b120ec9efef05e264
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/getVisibleBioEntitiesPolygonCoordinates.ts
@@ -0,0 +1,33 @@
+import { store } from '@/redux/store';
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { MapInstance } from '@/types/map';
+import { MapManager } from '../mapManager';
+import { pointToProjection } from '../fitBounds/fitBounds.utils';
+import { getPolygonPoints } from './getPolygonPoints';
+
+const VALID_POLYGON_COORDINATES_LENGTH = 2;
+
+export const getVisibleBioEntitiesPolygonCoordinates = ():
+  | {
+      polygonCoordinates: number[][];
+      maxZoom: number;
+      mapInstance: MapInstance | null;
+    }
+  | undefined => {
+  const mapSize = mapDataSizeSelector(store.getState());
+  const { maxZoom } = mapDataSizeSelector(store.getState());
+
+  const polygonPoints = getPolygonPoints();
+
+  const polygonCoordinates = polygonPoints.map(point => pointToProjection(point, mapSize));
+
+  if (polygonCoordinates.length !== VALID_POLYGON_COORDINATES_LENGTH) {
+    return undefined;
+  }
+
+  return {
+    polygonCoordinates,
+    maxZoom,
+    mapInstance: MapManager.getMapInstance(),
+  };
+};
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
index bdd6a539ddbfc7eca3934a8700061acc54d8de24..c04dfaace49cb9e95f3d76640e407b80a11c3ff1 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
@@ -8,6 +8,8 @@ import { Coordinates } from './triggerSearch.types';
 export const searchByCoordinates = async (
   coordinates: Coordinates,
   modelId: number,
+  hasFitBounds?: boolean,
+  fitBoundsZoom?: number,
 ): Promise<void> => {
   const { dispatch } = store;
   // side-effect below is to prevent complications with data update - old data may conflict with new data
@@ -23,5 +25,5 @@ export const searchByCoordinates = async (
     return;
   }
 
-  handleSearchResultAction({ searchResults, dispatch });
+  handleSearchResultAction({ searchResults, dispatch, hasFitBounds, fitBoundsZoom });
 };
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts b/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e804a294f9ad4f681661e008f15897543d1db1d9
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/searchByQuery.test.ts
@@ -0,0 +1,119 @@
+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 { 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 { waitFor } from '@testing-library/react';
+import { searchByQuery } from './searchByQuery';
+import { searchFitBounds } from './searchFitBounds';
+
+const MOCK_SEARCH_BY_QUERY_STORE = {
+  models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
+  map: {
+    ...MAP_INITIAL_STATE,
+    data: {
+      ...MAP_INITIAL_STATE.data,
+      modelId: 5052,
+      size: {
+        width: 256,
+        height: 256,
+        tileSize: 256,
+        minZoom: 1,
+        maxZoom: 1,
+      },
+    },
+  },
+  bioEntity: {
+    loading: 'succeeded',
+    error: { message: '', name: '' },
+  },
+  drugs: {
+    loading: 'succeeded',
+    error: { message: '', name: '' },
+  },
+  drawer: {
+    ...DRAWER_INITIAL_STATE,
+    bioEntityDrawerState: {
+      bioentityId: undefined,
+      drugs: {},
+      chemicals: {},
+    },
+  },
+};
+
+const mockedAxiosClient = mockNetworkNewAPIResponse();
+const SEARCH_QUERY = 'park7';
+
+jest.mock('./searchFitBounds');
+jest.mock('../../../../redux/store');
+const dispatchSpy = jest.spyOn(store, 'dispatch');
+const getStateSpy = jest.spyOn(store, 'getState');
+
+describe('searchByQuery', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should fit bounds after search if hasFitBounds param is true', 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);
+
+    await waitFor(() => {
+      expect(searchFitBounds).toHaveBeenCalled();
+    });
+  });
+  it('should not fit bounds after search if hasFitBounds param is false', 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, false);
+
+    await waitFor(() => {
+      expect(searchFitBounds).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts b/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
index 01d0603743171cebd905be553cf7d61aa6824fc1..e29c50f97532b73a77fe158f1d4e2571ff58bb4b 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByQuery.ts
@@ -2,13 +2,24 @@ import { getSearchValuesArrayAndTrimToSeven } from '@/components/FunctionalArea/
 import { getSearchData } from '@/redux/search/search.thunks';
 import { store } from '@/redux/store';
 import { displaySearchDrawerWithSelectedDefaultTab } from './displaySearchDrawerWithSelectedDefaultTab';
+import { searchFitBounds } from './searchFitBounds';
 
-export const searchByQuery = (query: string, perfectSearch: boolean | undefined): void => {
+export const searchByQuery = (
+  query: string,
+  perfectSearch: boolean | undefined,
+  hasFitBounds?: boolean,
+): void => {
   const { dispatch } = store;
   const searchValues = getSearchValuesArrayAndTrimToSeven(query);
   const isPerfectMatch = !!perfectSearch;
 
-  dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch }));
+  dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch }))
+    ?.unwrap()
+    .then(() => {
+      if (hasFitBounds) {
+        searchFitBounds();
+      }
+    });
 
   displaySearchDrawerWithSelectedDefaultTab(searchValues);
 };
diff --git a/src/services/pluginsManager/map/triggerSearch/searchFitBounds.test.ts b/src/services/pluginsManager/map/triggerSearch/searchFitBounds.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25847a33e9a61f93189d4d8882e92d6a81d43eac
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/searchFitBounds.test.ts
@@ -0,0 +1,105 @@
+/* eslint-disable no-magic-numbers */
+import { handleSetBounds } from '@/utils/map/useSetBounds';
+import { Map } from 'ol';
+import * as getVisibleBioEntitiesPolygonCoordinates from './getVisibleBioEntitiesPolygonCoordinates';
+import { searchFitBounds } from './searchFitBounds';
+
+jest.mock('../../../../utils/map/useSetBounds');
+
+jest.mock('./getVisibleBioEntitiesPolygonCoordinates', () => ({
+  __esModule: true,
+  ...jest.requireActual('./getVisibleBioEntitiesPolygonCoordinates'),
+}));
+
+const getVisibleBioEntitiesPolygonCoordinatesSpy = jest.spyOn(
+  getVisibleBioEntitiesPolygonCoordinates,
+  'getVisibleBioEntitiesPolygonCoordinates',
+);
+
+describe('searchFitBounds', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should not handle set bounds if data is not valid', () => {
+    getVisibleBioEntitiesPolygonCoordinatesSpy.mockImplementation(() => undefined);
+
+    searchFitBounds();
+
+    expect(handleSetBounds).not.toHaveBeenCalled();
+  });
+  it('should not handle set bounds if map instance is not valid', () => {
+    getVisibleBioEntitiesPolygonCoordinatesSpy.mockImplementation(() => ({
+      mapInstance: null,
+      maxZoom: 5,
+      polygonCoordinates: [
+        [231, 231],
+        [842, 271],
+      ],
+    }));
+
+    searchFitBounds();
+
+    expect(handleSetBounds).not.toHaveBeenCalled();
+  });
+  it('should handle set bounds if provided data is valid', () => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({ target: dummyElement });
+    const maxZoom = 5;
+    const polygonCoordinates = [
+      [231, 231],
+      [842, 271],
+    ];
+
+    getVisibleBioEntitiesPolygonCoordinatesSpy.mockImplementation(() => ({
+      mapInstance,
+      maxZoom,
+      polygonCoordinates,
+    }));
+
+    searchFitBounds();
+
+    expect(handleSetBounds).toHaveBeenCalled();
+    expect(handleSetBounds).toHaveBeenCalledWith(mapInstance, maxZoom, polygonCoordinates);
+  });
+  it('should handle set bounds with max zoom if zoom is not provided in argument', () => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({ target: dummyElement });
+    const maxZoom = 23;
+    const polygonCoordinates = [
+      [231, 231],
+      [842, 271],
+    ];
+
+    getVisibleBioEntitiesPolygonCoordinatesSpy.mockImplementation(() => ({
+      mapInstance,
+      maxZoom,
+      polygonCoordinates,
+    }));
+
+    searchFitBounds();
+
+    expect(handleSetBounds).toHaveBeenCalled();
+    expect(handleSetBounds).toHaveBeenCalledWith(mapInstance, maxZoom, polygonCoordinates);
+  });
+  it('should handle set bounds with zoom provided in argument instead of max zoom', () => {
+    const zoom = 12;
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({ target: dummyElement });
+    const maxZoom = 23;
+    const polygonCoordinates = [
+      [231, 231],
+      [842, 271],
+    ];
+
+    getVisibleBioEntitiesPolygonCoordinatesSpy.mockImplementation(() => ({
+      mapInstance,
+      maxZoom,
+      polygonCoordinates,
+    }));
+
+    searchFitBounds(zoom);
+
+    expect(handleSetBounds).toHaveBeenCalled();
+    expect(handleSetBounds).toHaveBeenCalledWith(mapInstance, zoom, polygonCoordinates);
+  });
+});
diff --git a/src/services/pluginsManager/map/triggerSearch/searchFitBounds.ts b/src/services/pluginsManager/map/triggerSearch/searchFitBounds.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9c2954ebe5525040cb3550b085613b0b0b781153
--- /dev/null
+++ b/src/services/pluginsManager/map/triggerSearch/searchFitBounds.ts
@@ -0,0 +1,15 @@
+import { handleSetBounds } from '@/utils/map/useSetBounds';
+import { getVisibleBioEntitiesPolygonCoordinates } from './getVisibleBioEntitiesPolygonCoordinates';
+
+export const searchFitBounds = (zoom?: number): void => {
+  const data = getVisibleBioEntitiesPolygonCoordinates();
+
+  if (data) {
+    const { polygonCoordinates, maxZoom, mapInstance } = data;
+
+    if (!mapInstance) return;
+
+    const setBoundsZoom = zoom || maxZoom;
+    handleSetBounds(mapInstance, setBoundsZoom, polygonCoordinates);
+  }
+};
diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
index 3c7408ebf99514fc11abc6bd75118bae26192724..1b522bb065723a9ef78cf2d24e01869960c3c00a 100644
--- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
+++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
@@ -10,6 +10,7 @@ import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFix
 import { waitFor } from '@testing-library/react';
 import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction';
 import { triggerSearch } from './triggerSearch';
+import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 const mockedAxiosOldClient = mockNetworkResponse();
@@ -148,9 +149,7 @@ describe('triggerSearch', () => {
         modelId: '53' as any,
       };
 
-      await expect(triggerSearch(invalidParams)).rejects.toThrowError(
-        'Invalid model id type. The model should be of number type',
-      );
+      await expect(triggerSearch(invalidParams)).rejects.toThrowError(ERROR_INVALID_MODEL_ID_TYPE);
     });
     it('should search result with proper data', async () => {
       mockedAxiosOldClient
diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts
index 8f1b42fe3f895d34ee15ea2beb690b16a8d2f202..8a69f07b6570907182960db7bd2e770d5b6e6644 100644
--- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts
+++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts
@@ -12,7 +12,7 @@ export async function triggerSearch(params: SearchParams): Promise<void> {
     if (typeof params.query !== 'string') {
       throw new Error(ERROR_INVALID_QUERY_TYPE);
     }
-    searchByQuery(params.query, params.perfectSearch);
+    searchByQuery(params.query, params.perfectSearch, params.fitBounds);
   } else {
     const areCoordinatesInvalidType =
       typeof params.coordinates !== 'object' || params.coordinates === null;
@@ -28,6 +28,6 @@ export async function triggerSearch(params: SearchParams): Promise<void> {
       throw new Error(ERROR_INVALID_MODEL_ID_TYPE);
     }
 
-    searchByCoordinates(params.coordinates, params.modelId);
+    searchByCoordinates(params.coordinates, params.modelId, params.fitBounds, params.zoom);
   }
 }
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
index 5fd9ea0a98a72dbfe3eacd96d8d708f81c3058bd..2969f8ab7be3e86ef9f44f871c4eace00d0e97a4 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
@@ -14,6 +14,8 @@ const PLUGINS_EVENTS = {
     onZoomChanged: 'onZoomChanged',
     onCenterChanged: 'onCenterChanged',
     onBioEntityClick: 'onBioEntityClick',
+    onPinIconClick: 'onPinIconClick',
+    onSurfaceClick: 'onSurfaceClick',
   },
   search: {
     onSearch: 'onSearch',
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
index 5227eabbdc6e6ea1f43b84d809fe6718af2fcc4c..66d4ab43fd2d6dd73422f714ada6c8a897592d0e 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
@@ -4,6 +4,8 @@ import { ALLOWED_PLUGINS_EVENTS, LISTENER_NOT_FOUND } from './pluginsEventBus.co
 import type {
   CenteredCoordinates,
   ClickedBioEntity,
+  ClickedPinIcon,
+  ClickedSurfaceOverlay,
   Events,
   EventsData,
   PluginsEventBusType,
@@ -21,6 +23,8 @@ export function dispatchEvent(type: 'onSubmapClose', submapId: number): void;
 export function dispatchEvent(type: 'onZoomChanged', data: ZoomChanged): void;
 export function dispatchEvent(type: 'onCenterChanged', data: CenteredCoordinates): void;
 export function dispatchEvent(type: 'onBioEntityClick', data: ClickedBioEntity): void;
+export function dispatchEvent(type: 'onPinIconClick', data: ClickedPinIcon): void;
+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}`);
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
index 7679bb0af514be8208c15a0a23c55c3de096aafe..0fdec91300559d50089301f264031c2d069f5fc4 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
@@ -1,4 +1,11 @@
-import { BioEntityContent, Chemical, CreatedOverlay, Drug, MapOverlay } from '@/types/models';
+import {
+  BioEntityContent,
+  Chemical,
+  CreatedOverlay,
+  Drug,
+  ElementSearchResult,
+  MapOverlay,
+} from '@/types/models';
 import { dispatchEvent } from './pluginsEventBus';
 
 export type BackgroundEvents = 'onBackgroundOverlayChange';
@@ -6,13 +13,15 @@ export type OverlayEvents =
   | 'onAddDataOverlay'
   | 'onRemoveDataOverlay'
   | 'onShowOverlay'
-  | 'onHideOverlay';
+  | 'onHideOverlay'
+  | 'onSurfaceClick';
 export type SubmapEvents =
   | 'onSubmapOpen'
   | 'onSubmapClose'
   | 'onZoomChanged'
   | 'onCenterChanged'
-  | 'onBioEntityClick';
+  | 'onBioEntityClick'
+  | 'onPinIconClick';
 export type SearchEvents = 'onSearch';
 
 export type Events = OverlayEvents | BackgroundEvents | SubmapEvents | SearchEvents;
@@ -34,9 +43,17 @@ export type ClickedBioEntity = {
   modelId: number;
 };
 
+export type ClickedPinIcon = {
+  id: number | string;
+};
+
+export type ClickedSurfaceOverlay = {
+  id: number | string;
+};
+
 export type SearchDataBioEntity = {
   type: 'bioEntity';
-  searchValues: string[];
+  searchValues: string[] | ElementSearchResult[];
   results: BioEntityContent[][];
 };
 
@@ -61,6 +78,8 @@ export type EventsData =
   | ZoomChanged
   | CenteredCoordinates
   | ClickedBioEntity
+  | ClickedPinIcon
+  | ClickedSurfaceOverlay
   | SearchData;
 
 export type PluginsEventBusType = {
diff --git a/src/services/pluginsManager/pluginsManager.ts b/src/services/pluginsManager/pluginsManager.ts
index 813274a16b43d74fea00d7250e3424adbd2fe1ce..f432527ab6e8e1f97755f1864745abb1819a687a 100644
--- a/src/services/pluginsManager/pluginsManager.ts
+++ b/src/services/pluginsManager/pluginsManager.ts
@@ -24,6 +24,10 @@ 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';
+
 export const PluginsManager: PluginsManagerType = {
   hashedPlugins: {},
   setHashedPlugin({ pluginUrl, pluginScript }) {
@@ -43,8 +47,11 @@ export const PluginsManager: PluginsManagerType = {
       },
       map: {
         data: {
+          getBounds,
+          getOpenMapId,
           getModels,
         },
+        fitBounds,
         openMap,
         triggerSearch,
         getZoom,
diff --git a/src/shared/Toast/Toast.component.test.tsx b/src/shared/Toast/Toast.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..610df96f168afee935ae509c76a42019302a38c9
--- /dev/null
+++ b/src/shared/Toast/Toast.component.test.tsx
@@ -0,0 +1,29 @@
+/* eslint-disable no-magic-numbers */
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Toast } from './Toast.component';
+
+describe('Toast component', () => {
+  it('renders success message correctly', () => {
+    const message = 'Success message';
+    render(<Toast type="success" message={message} onDismiss={() => {}} />);
+    const toastElement = screen.getByText(message);
+    expect(toastElement).toBeInTheDocument();
+    expect(toastElement).toHaveClass('text-green-500');
+  });
+
+  it('renders error message correctly', () => {
+    const message = 'Error message';
+    render(<Toast type="error" message={message} onDismiss={() => {}} />);
+    const toastElement = screen.getByText(message);
+    expect(toastElement).toBeInTheDocument();
+    expect(toastElement).toHaveClass('text-red-500');
+  });
+
+  it('calls onDismiss when close button is clicked', () => {
+    const mockOnDismiss = jest.fn();
+    render(<Toast type="success" message="Success message" onDismiss={mockOnDismiss} />);
+    const closeButton = screen.getByRole('button');
+    fireEvent.click(closeButton);
+    expect(mockOnDismiss).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/src/shared/Toast/Toast.component.tsx b/src/shared/Toast/Toast.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..02e1d965aa9bfc79143337d7ac11254cc6059f8c
--- /dev/null
+++ b/src/shared/Toast/Toast.component.tsx
@@ -0,0 +1,30 @@
+import { twMerge } from 'tailwind-merge';
+import { Icon } from '../Icon';
+
+type ToastArgs = {
+  type: 'success' | 'error';
+  message: string;
+  onDismiss: () => void;
+};
+
+export const Toast = ({ type, message, onDismiss }: ToastArgs): React.ReactNode => (
+  <div
+    className={twMerge(
+      'flex h-[76px] w-[700px] items-center rounded-l rounded-r-lg bg-white p-4 drop-shadow before:absolute before:inset-y-0 before:left-0 before:block before:w-1 before:rounded-l-lg before:content-[""]',
+      type === 'error' ? 'before:bg-red-500' : 'before:bg-green-500',
+    )}
+  >
+    <p
+      className={twMerge(
+        'text-base font-bold ',
+        type === 'error' ? 'text-red-500' : 'text-green-500',
+      )}
+    >
+      {message}
+    </p>
+
+    <button type="button" onClick={onDismiss} className="ml-auto flex-none">
+      <Icon name="close" className="ml-3 h-7 w-7 fill-font-500" />
+    </button>
+  </div>
+);
diff --git a/src/shared/Toast/index.ts b/src/shared/Toast/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c28ae1894e90798a5f9852ee719937588b8b0814
--- /dev/null
+++ b/src/shared/Toast/index.ts
@@ -0,0 +1 @@
+export { Toast } from './Toast.component';
diff --git a/src/types/store.ts b/src/types/store.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c513f8db4485126f755cd2fa6346e55f126f8fa1
--- /dev/null
+++ b/src/types/store.ts
@@ -0,0 +1,3 @@
+export type ThunkConfig = {
+  rejectValue: string;
+};
diff --git a/src/utils/context/mapInstanceContext.tsx b/src/utils/context/mapInstanceContext.tsx
index 1c0982d8e55a4bcbafdf1416b473b43afcf9f9b9..85fe8fc8f6f02db14da4a38b6933a197c1fddee9 100644
--- a/src/utils/context/mapInstanceContext.tsx
+++ b/src/utils/context/mapInstanceContext.tsx
@@ -1,14 +1,15 @@
+import { MapManager } from '@/services/pluginsManager/map/mapManager';
 import { MapInstance } from '@/types/map';
-import { Dispatch, SetStateAction, createContext, useContext, useMemo, useState } from 'react';
+import { createContext, useCallback, useContext, useMemo, useState } from 'react';
 
 export interface MapInstanceContext {
   mapInstance: MapInstance;
-  setMapInstance: Dispatch<SetStateAction<MapInstance>>;
+  handleSetMapInstance: (mapInstance: MapInstance) => void;
 }
 
 export const MapInstanceContext = createContext<MapInstanceContext>({
   mapInstance: undefined,
-  setMapInstance: () => {},
+  handleSetMapInstance: () => {},
 });
 
 export const useMapInstance = (): MapInstanceContext => useContext(MapInstanceContext);
@@ -24,12 +25,22 @@ export const MapInstanceProvider = ({
 }: MapInstanceProviderProps): JSX.Element => {
   const [mapInstance, setMapInstance] = useState<MapInstance>(initialValue?.mapInstance);
 
+  const handleSetMapInstance = useCallback(
+    (map: MapInstance) => {
+      if (!mapInstance) {
+        setMapInstance(map);
+        MapManager.setMapInstance(map);
+      }
+    },
+    [mapInstance],
+  );
+
   const mapInstanceContextValue = useMemo(
     () => ({
       mapInstance,
-      setMapInstance,
+      handleSetMapInstance,
     }),
-    [mapInstance],
+    [mapInstance, handleSetMapInstance],
   );
 
   return (
diff --git a/src/utils/getErrorMessage/getErrorMessage.constants.ts b/src/utils/getErrorMessage/getErrorMessage.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..00b84d7087cf177ecf1a129633867153ce228e48
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.constants.ts
@@ -0,0 +1,11 @@
+export const UNKNOWN_ERROR = 'An unknown error occurred. Please try again later.';
+
+export const HTTP_ERROR_MESSAGES = {
+  400: "The server couldn't understand your request. Please check your input and try again.",
+  401: "You're not authorized to access this resource. Please log in or check your credentials.",
+  403: "Access Forbidden! You don't have permission to access this resource.",
+  404: "The page you're looking for doesn't exist. Please verify the URL and try again.",
+  500: 'Unexpected server error. Please try again later or contact support.',
+  501: 'Sorry, this feature is not yet implemented. Please try again later.',
+  503: 'Service Unavailable! The server is currently down for maintenance. Please try again later.',
+};
diff --git a/src/utils/getErrorMessage/getErrorMessage.test.ts b/src/utils/getErrorMessage/getErrorMessage.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f9f2b7cd101c4ea2dccfcf5643190f1b9b25cf2
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.test.ts
@@ -0,0 +1,34 @@
+/* eslint-disable no-magic-numbers */
+import { getErrorMessage } from './getErrorMessage';
+import { mockAxiosError } from './getErrorMessage.test.utils';
+
+describe('getErrorMessage function', () => {
+  it('should return custom message if provided', () => {
+    const error = new Error('Custom Error');
+    const errorMessage = getErrorMessage({ error, message: 'This is a custom message' });
+    expect(errorMessage).toBe('This is a custom message');
+  });
+
+  it('should return extracted Axios error message', () => {
+    const error = mockAxiosError(401, 'Unauthorized');
+    const errorMessage = getErrorMessage({ error });
+    expect(errorMessage).toBe('Unauthorized');
+  });
+
+  it('should return error message from Error instance', () => {
+    const error = new Error('Network Error');
+    const errorMessage = getErrorMessage({ error });
+    expect(errorMessage).toBe('Network Error');
+  });
+
+  it('should return default error message if error is of unknown type', () => {
+    const errorMessage = getErrorMessage({ error: {} });
+    expect(errorMessage).toBe('An unknown error occurred. Please try again later.');
+  });
+
+  it('should prepend prefix to error message', () => {
+    const error = new Error('Server Error');
+    const errorMessage = getErrorMessage({ error, prefix: 'Error occurred' });
+    expect(errorMessage).toBe('Error occurred: Server Error');
+  });
+});
diff --git a/src/utils/getErrorMessage/getErrorMessage.test.utils.ts b/src/utils/getErrorMessage/getErrorMessage.test.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb7983e53d39dbb032410d196c3ab37345042e48
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.test.utils.ts
@@ -0,0 +1,15 @@
+import { AxiosError } from 'axios';
+
+type MockAxiosError = AxiosError<{ error: string; reason: string }>;
+
+export const mockAxiosError = (status: number, reason: string | null): MockAxiosError =>
+  ({
+    isAxiosError: true,
+    response: {
+      status,
+      data: {
+        reason,
+        error: reason,
+      },
+    },
+  }) as MockAxiosError;
diff --git a/src/utils/getErrorMessage/getErrorMessage.ts b/src/utils/getErrorMessage/getErrorMessage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20073f02e7d75ea19cbcbe1e4e8573031f25588f
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.ts
@@ -0,0 +1,29 @@
+import axios from 'axios';
+import { UNKNOWN_ERROR } from './getErrorMessage.constants';
+import { extractAxiosErrorMessage } from './getErrorMessage.utils';
+
+type GetErrorMessageConfig = {
+  error: unknown;
+  message?: string;
+  prefix?: string;
+};
+
+export const getErrorMessage = ({ error, message, prefix }: GetErrorMessageConfig): string => {
+  let errorMessage: string;
+
+  switch (true) {
+    case !!message:
+      errorMessage = message;
+      break;
+    case axios.isAxiosError(error):
+      errorMessage = extractAxiosErrorMessage(error);
+      break;
+    case error instanceof Error:
+      errorMessage = error.message;
+      break;
+    default:
+      errorMessage = UNKNOWN_ERROR;
+  }
+
+  return prefix ? `${prefix}: ${errorMessage}` : errorMessage;
+};
diff --git a/src/utils/getErrorMessage/getErrorMessage.types.ts b/src/utils/getErrorMessage/getErrorMessage.types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33e843551e3bafc782ab11cc537a5565c9d5242d
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.types.ts
@@ -0,0 +1,3 @@
+import { HTTP_ERROR_MESSAGES } from './getErrorMessage.constants';
+
+export type HttpStatuses = keyof typeof HTTP_ERROR_MESSAGES;
diff --git a/src/utils/getErrorMessage/getErrorMessage.utils.test.ts b/src/utils/getErrorMessage/getErrorMessage.utils.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8406dcaf7f0d7751d6c85d3d7e92b2028495e30c
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.utils.test.ts
@@ -0,0 +1,22 @@
+/* eslint-disable no-magic-numbers */
+import { mockAxiosError } from './getErrorMessage.test.utils';
+import { extractAxiosErrorMessage } from './getErrorMessage.utils';
+
+describe('extractAxiosErrorMessage', () => {
+  it('should return the error message from Axios error response if exist', () => {
+    const error = mockAxiosError(404, 'Not Found');
+    expect(extractAxiosErrorMessage(error)).toBe('Not Found');
+  });
+  it('should return error message defined by response status if error response does not exist', () => {
+    const error = mockAxiosError(500, null);
+    expect(extractAxiosErrorMessage(error)).toBe(
+      'Unexpected server error. Please try again later or contact support.',
+    );
+  });
+  it('should return the default error message if status code is not defined in predefined error messages list and error response does not exist', () => {
+    const error = mockAxiosError(418, null);
+    expect(extractAxiosErrorMessage(error)).toBe(
+      'An unknown error occurred. Please try again later.',
+    );
+  });
+});
diff --git a/src/utils/getErrorMessage/getErrorMessage.utils.ts b/src/utils/getErrorMessage/getErrorMessage.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46a46ee43c3c8b694a5100c4d3300c42d9a39e29
--- /dev/null
+++ b/src/utils/getErrorMessage/getErrorMessage.utils.ts
@@ -0,0 +1,14 @@
+import { AxiosError } from 'axios';
+import { HTTP_ERROR_MESSAGES, UNKNOWN_ERROR } from './getErrorMessage.constants';
+import { HttpStatuses } from './getErrorMessage.types';
+
+type Error = { error: string; reason: string };
+
+export const extractAxiosErrorMessage = (error: AxiosError<Error>): string => {
+  if (error.response?.data?.reason) {
+    return error.response.data.reason;
+  }
+
+  const status = error.response?.status as HttpStatuses;
+  return HTTP_ERROR_MESSAGES[status] || UNKNOWN_ERROR;
+};
diff --git a/src/utils/getErrorMessage/index.ts b/src/utils/getErrorMessage/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..50ca645d9391ddb5593acfcd24d296412d18a731
--- /dev/null
+++ b/src/utils/getErrorMessage/index.ts
@@ -0,0 +1 @@
+export { getErrorMessage } from './getErrorMessage';
diff --git a/src/utils/map/useSetBounds.test.ts b/src/utils/map/useSetBounds.test.ts
index 4e00469d4c7df59c3b33135a5c6da8182b1e0db5..71ee12602b0c344b7276446cb952b1dbb1d687d9 100644
--- a/src/utils/map/useSetBounds.test.ts
+++ b/src/utils/map/useSetBounds.test.ts
@@ -39,7 +39,7 @@ describe('useSetBounds - hook', () => {
         {
           mapInstanceContextValue: {
             mapInstance: undefined,
-            setMapInstance: () => {},
+            handleSetMapInstance: () => {},
           },
         },
       );
@@ -84,7 +84,7 @@ describe('useSetBounds - hook', () => {
         {
           mapInstanceContextValue: {
             mapInstance,
-            setMapInstance: () => {},
+            handleSetMapInstance: () => {},
           },
         },
       );
diff --git a/src/utils/showToast.test.tsx b/src/utils/showToast.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..01f65adebfafe68142553d337ef699c66b9a75b5
--- /dev/null
+++ b/src/utils/showToast.test.tsx
@@ -0,0 +1,21 @@
+/* eslint-disable no-magic-numbers */
+import { toast } from 'sonner';
+import { showToast } from './showToast';
+
+jest.mock('sonner', () => ({
+  toast: {
+    custom: jest.fn(),
+    dismiss: jest.fn(),
+  },
+}));
+
+describe('showToast', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should call toast.custom on showToast call', () => {
+    showToast({ type: 'success', message: 'Success message' });
+    expect(toast.custom).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/src/utils/showToast.tsx b/src/utils/showToast.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c7ea4329137a508aec4b93357e63442ef6c3c795
--- /dev/null
+++ b/src/utils/showToast.tsx
@@ -0,0 +1,13 @@
+import { toast } from 'sonner';
+import { Toast } from '@/shared/Toast';
+
+type ShowToastArgs = {
+  type: 'success' | 'error';
+  message: string;
+};
+
+export const showToast = (args: ShowToastArgs): void => {
+  toast.custom(t => (
+    <Toast message={args.message} onDismiss={() => toast.dismiss(t)} type={args.type} />
+  ));
+};