diff --git a/.eslintrc.json b/.eslintrc.json
index 3ed8a0dac26c9f37e4753346fdf8856d5351cabe..8712576ff44b991298e686588f6cb343c1bf9ab6 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -89,7 +89,14 @@
         "config": "./tailwind.config.ts"
       }
     ],
-    "prettier/prettier": "error"
+    "prettier/prettier": "error",
+    "jsx-a11y/label-has-associated-control": [
+      2,
+      {
+        "controlComponents": ["Input"],
+        "depth": 3
+      }
+    ]
   },
   "overrides": [
     {
diff --git a/package-lock.json b/package-lock.json
index 268c6f7edcedbd8e8de2ffda5d8b728c8abbd8d2..4fb5b060db68de2f2175f44a2f25267d63c091dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
         "react": "18.2.0",
         "react-accessible-accordion": "^5.0.0",
         "react-dom": "18.2.0",
+        "react-dropzone": "^14.2.3",
         "react-redux": "^8.1.2",
         "tailwind-merge": "^1.14.0",
         "tailwindcss": "3.3.3",
@@ -3050,6 +3051,14 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.15",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
@@ -6349,6 +6358,17 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
+    "node_modules/file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -11359,6 +11379,22 @@
         "react": "^18.2.0"
       }
     },
+    "node_modules/react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "dependencies": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8 || 18.0.0"
+      }
+    },
     "node_modules/react-is": {
       "version": "18.2.0",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -15894,6 +15930,11 @@
       "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
       "dev": true
     },
+    "attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+    },
     "autoprefixer": {
       "version": "10.4.15",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
@@ -18287,6 +18328,14 @@
         "flat-cache": "^3.0.4"
       }
     },
+    "file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "requires": {
+        "tslib": "^2.4.0"
+      }
+    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -21770,6 +21819,16 @@
         "scheduler": "^0.23.0"
       }
     },
+    "react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "requires": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      }
+    },
     "react-is": {
       "version": "18.2.0",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
diff --git a/package.json b/package.json
index deee43c5eda2c8e1f543cd52b919e81619ff3bf2..081b8f16e154a0fbdc12837c212cf97441ded897 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "react": "18.2.0",
     "react-accessible-accordion": "^5.0.0",
     "react-dom": "18.2.0",
+    "react-dropzone": "^14.2.3",
     "react-redux": "^8.1.2",
     "tailwind-merge": "^1.14.0",
     "tailwindcss": "3.3.3",
diff --git a/setupTests.ts b/setupTests.ts
index e8c65391c7967eb34c7359010aaef4e93a7dd653..1d944c81f3e4f47f19d599c4f702725886518791 100644
--- a/setupTests.ts
+++ b/setupTests.ts
@@ -10,6 +10,10 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({
 
 jest.mock('next/router', () => require('next-router-mock'));
 
+global.TextEncoder = jest.fn().mockImplementation(() => ({
+  encode: jest.fn(),
+}));
+
 const localStorageMock = (() => {
   let store: {
     [key: PropertyKey]: string;
diff --git a/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx
index 354b53377e7fa13900bc8f38b872099caf622514..40ac94361133d1a08a9d31260dd277a28c7f35d6 100644
--- a/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx
+++ b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx
@@ -3,6 +3,7 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { loadingUserSelector } from '@/redux/user/user.selectors';
 import { login } from '@/redux/user/user.thunks';
 import { Button } from '@/shared/Button';
+import { Input } from '@/shared/Input';
 import Link from 'next/link';
 import React from 'react';
 
@@ -27,26 +28,26 @@ export const LoginModal: React.FC = () => {
       <form onSubmit={handleSubmit}>
         <label className="mb-5 block text-sm font-semibold" htmlFor="login">
           Login:
-          <input
+          <Input
             type="text"
             name="login"
             id="login"
             placeholder="Your login here.."
             value={credentials.login}
             onChange={handleChange}
-            className="mt-2.5 h-10 w-full rounded-s border border-transparent bg-cultured px-2 py-2.5 text-sm font-medium text-font-400 outline-none  hover:border-greyscale-600 focus:border-greyscale-600"
+            className="mt-2.5 text-sm font-medium text-font-400"
           />
         </label>
         <label className="text-sm font-semibold" htmlFor="password">
           Password:
-          <input
+          <Input
             type="password"
             name="password"
             id="password"
             placeholder="Your password here.."
             value={credentials.password}
             onChange={handleChange}
-            className="mt-2.5 h-10 w-full rounded-s border border-transparent bg-cultured px-2 py-2.5 text-sm font-medium text-font-400 outline-none  hover:border-greyscale-600 focus:border-greyscale-600"
+            className="mt-2.5 text-sm font-medium text-font-400"
           />
         </label>
         <div className="mb-10 text-right">
diff --git a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
index c64ecf7408b301eb3f5b77ed21ee963d748d5fb3..15b84a160d4112d107fbdee6027183c6bf375fdb 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
@@ -1,15 +1,26 @@
 import { DrawerHeading } from '@/shared/DrawerHeading';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { currentStepOverlayDrawerStateSelector } from '@/redux/drawer/drawer.selectors';
+import { STEP } from '@/constants/searchDrawer';
 import { GeneralOverlays } from './GeneralOverlays';
 import { UserOverlays } from './UserOverlays';
+import { UserOverlayForm } from './UserOverlayForm';
 
 export const OverlaysDrawer = (): JSX.Element => {
+  const currentStep = useAppSelector(currentStepOverlayDrawerStateSelector);
+
   return (
     <div data-testid="overlays-drawer" className="h-full max-h-full">
-      <DrawerHeading title="Overlays" />
-      <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto">
-        <GeneralOverlays />
-        <UserOverlays />
-      </div>
+      {currentStep === STEP.FIRST && (
+        <>
+          <DrawerHeading title="Overlays" />
+          <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto">
+            <GeneralOverlays />
+            <UserOverlays />
+          </div>
+        </>
+      )}
+      {currentStep === STEP.SECOND && <UserOverlayForm />}
     </div>
   );
 };
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..83a68bfa1714e773b550a7c46552e14963b77832
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { FileUpload } from './FileUpload.component';
+
+describe('FileUpload component', () => {
+  const handleChangeFile = jest.fn();
+  const handleChangeOverlayContent = jest.fn();
+  const handleOverlayChange = jest.fn();
+  const uploadedFile = new File(['file content'], 'test.txt', {
+    type: 'text/plain',
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('renders correctly with default state', () => {
+    render(
+      <FileUpload
+        handleChangeFile={handleChangeFile}
+        handleChangeOverlayContent={handleChangeOverlayContent}
+        updateUserOverlayForm={handleOverlayChange}
+        uploadedFile={null}
+      />,
+    );
+
+    expect(screen.getByText(/drag and drop here or/i)).toBeInTheDocument();
+  });
+
+  it('renders filename when file type is correct', () => {
+    render(
+      <FileUpload
+        handleChangeFile={handleChangeFile}
+        handleChangeOverlayContent={handleChangeOverlayContent}
+        updateUserOverlayForm={handleOverlayChange}
+        uploadedFile={uploadedFile}
+      />,
+    );
+
+    expect(screen.getByText(/test.txt/i)).toBeInTheDocument();
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4370a451657d8e912a245669f31b93877346e7d2
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/FileUpload.component.tsx
@@ -0,0 +1,62 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { useDropzone } from 'react-dropzone';
+import { processOverlayContentChange } from '../UserOverlayForm.utils';
+
+type FileUploadProps = {
+  updateUserOverlayForm: (nameType: string, value: string) => void;
+  handleChangeOverlayContent: (value: string) => void;
+  handleChangeFile: (value: File) => void;
+  uploadedFile: File | null;
+};
+
+export const FileUpload = ({
+  handleChangeFile,
+  handleChangeOverlayContent,
+  updateUserOverlayForm,
+  uploadedFile,
+}: FileUploadProps): React.ReactNode => {
+  const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
+    accept: {
+      'text/plain': ['.txt'],
+    },
+    onDrop: acceptedFiles => {
+      handleChangeFile(acceptedFiles[0]);
+
+      const file = acceptedFiles[0];
+      if (file) {
+        const reader = new FileReader();
+        reader.readAsText(file);
+        reader.onload = (e): void => {
+          if (e.target) {
+            const content = e.target?.result as string;
+            handleChangeOverlayContent(content);
+            processOverlayContentChange(content, updateUserOverlayForm);
+          }
+        };
+      }
+    },
+  });
+
+  return (
+    <div
+      {...getRootProps()}
+      className="flex h-16 items-center justify-center rounded-lg bg-cultured"
+      data-testid="dropzone"
+    >
+      <input {...getInputProps()} data-testid="dropzone-input" />
+      <p className="text-xs font-semibold">
+        {uploadedFile && uploadedFile.name}
+
+        {isDragActive && !isDragReject && 'Drop the file here ...'}
+
+        {!isDragActive && !uploadedFile && (
+          <>
+            Drag and drop here or <span className="text-[#004DE2]">browse</span>
+          </>
+        )}
+        {isDragReject && 'Invalid file type. Please choose a supported format .txt'}
+      </p>
+    </div>
+  );
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/index.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07a87a8db76e41abacdb0f0b31bdd5f2402de7a2
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/FileUpload/index.ts
@@ -0,0 +1 @@
+export { FileUpload } from './FileUpload.component';
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..acc140ebd165aa26853145692ff6bdaa17eced0f
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.test.tsx
@@ -0,0 +1,48 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { OverlaySelector } from './OverlaySelector.component';
+import { SelectorItem } from '../UserOverlayForm.types';
+
+const items: SelectorItem[] = [
+  { id: '1', label: 'Item 1' },
+  { id: '2', label: 'Item 2' },
+  { id: '3', label: 'Item 3' },
+];
+
+const onChangeMock = jest.fn();
+
+describe('OverlaySelector component', () => {
+  it('renders the component with initial values', () => {
+    const label = 'Select an item';
+    const value = items[0];
+
+    render(<OverlaySelector items={items} value={value} onChange={onChangeMock} label={label} />);
+
+    expect(screen.getByText(label)).toBeInTheDocument();
+
+    expect(screen.getByTestId('selector-dropdown-button-name')).toHaveTextContent(value.label);
+  });
+
+  it('opens the dropdown and selects an item', () => {
+    const label = 'Select an item';
+    const value = items[0];
+
+    render(<OverlaySelector items={items} value={value} onChange={onChangeMock} label={label} />);
+
+    fireEvent.click(screen.getByTestId('selector-dropdown-button-name'));
+
+    expect(screen.getByRole('listbox')).toBeInTheDocument();
+
+    const selectedItem = items[1];
+    const firstItem = screen.getByText(selectedItem.label);
+
+    fireEvent.click(firstItem);
+
+    waitFor(() => {
+      expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
+    });
+
+    expect(onChangeMock).toHaveBeenCalledWith(selectedItem);
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0e29ed5fdd9870377c00ac323caf421e0fc597d0
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlaySelector.component.tsx
@@ -0,0 +1,84 @@
+/* eslint-disable no-magic-numbers */
+import { useSelect } from 'downshift';
+
+import { twMerge } from 'tailwind-merge';
+import { Icon } from '@/shared/Icon';
+import { SelectorItem } from '../UserOverlayForm.types';
+
+type OverlaySelectorProps = {
+  items: SelectorItem[];
+  value: SelectorItem;
+  onChange: (item: SelectorItem) => void;
+  label: string;
+};
+
+export const OverlaySelector = ({
+  items,
+  value,
+  onChange,
+  label,
+}: OverlaySelectorProps): JSX.Element => {
+  const onItemSelect = (item: SelectorItem | undefined | null): void => {
+    if (item) {
+      onChange(item);
+    }
+  };
+
+  const {
+    isOpen,
+    getToggleButtonProps,
+    getMenuProps,
+    highlightedIndex,
+    getItemProps,
+    selectedItem,
+  } = useSelect({
+    items,
+    defaultSelectedItem: items[0],
+    selectedItem: value,
+    onSelectedItemChange: ({ selectedItem: newSelectedItem }) => onItemSelect(newSelectedItem),
+  });
+
+  return (
+    <div className="mb-2.5">
+      <p className="my-2.5 text-sm">{label}</p>
+
+      <div className={twMerge('relative rounded-t bg-cultured text-xs', !isOpen && 'rounded-b')}>
+        <div className={twMerge('flex w-full flex-col rounded-t py-2 pl-4 pr-3')}>
+          <div
+            {...getToggleButtonProps()}
+            className="flex cursor-pointer flex-row items-center justify-between bg-cultured"
+          >
+            <span data-testid="selector-dropdown-button-name" className="font-medium">
+              {selectedItem?.label}
+            </span>
+            <Icon
+              name="chevron-down"
+              className={twMerge('arrow-button h-6 w-6 fill-primary-500', isOpen && 'rotate-180')}
+            />
+          </div>
+        </div>
+        <ul
+          {...getMenuProps()}
+          className={`absolute inset-x-0 z-10 max-h-80 w-full overflow-scroll rounded-b bg-cultured p-0 ${
+            !isOpen && 'hidden'
+          }`}
+        >
+          {isOpen &&
+            items.map((item, index) => (
+              <li
+                className={twMerge(
+                  'border-t',
+                  highlightedIndex === index && 'text-primary-500',
+                  'flex flex-col px-4 py-2',
+                )}
+                key={item.id}
+                {...getItemProps({ item, index })}
+              >
+                <span>{item.label}</span>
+              </li>
+            ))}
+        </ul>
+      </div>
+    </div>
+  );
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/index.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..147e48942ee3dbd6730acc1ad69fe13bc1f5057a
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/index.ts
@@ -0,0 +1 @@
+export { OverlaySelector } from './OverlaySelector.component';
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b7c754c8f76d782d67e55a0beec1f0c2f1a3021c
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
@@ -0,0 +1,240 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { projectFixture } from '@/models/fixtures/projectFixture';
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { AppDispatch, RootState, StoreType } from '@/redux/store';
+import { DEFAULT_ERROR } from '@/constants/errors';
+import { drawerOverlaysStepOneFixture } from '@/redux/drawer/drawerFixture';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { apiPath } from '@/redux/apiPath';
+import {
+  createdOverlayFileFixture,
+  createdOverlayFixture,
+  uploadedOverlayFileContentFixture,
+} from '@/models/fixtures/overlaysFixture';
+import { UserOverlayForm } from './UserOverlayForm.component';
+
+const mockedAxiosClient = mockNetworkResponse();
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <UserOverlayForm />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const renderComponentWithActionListener = (
+  initialStoreState: InitialStoreState = {},
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStoreState);
+
+  return (
+    render(
+      <Wrapper>
+        <UserOverlayForm />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('UserOverlayForm - Component', () => {
+  it('renders the UserOverlayForm component', () => {
+    renderComponent();
+
+    expect(screen.getByTestId('overlay-name')).toBeInTheDocument();
+    expect(screen.getByLabelText('upload overlay')).toBeInTheDocument();
+  });
+
+  it('should submit the form with elements list when upload button is clicked', async () => {
+    mockedAxiosClient
+      .onPost(apiPath.createOverlayFile())
+      .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(123))
+      .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.createOverlay('pd'))
+      .reply(HttpStatusCode.Ok, createdOverlayFixture);
+
+    renderComponent({
+      project: {
+        data: projectFixture,
+        loading: 'succeeded',
+        error: { message: '', name: '' },
+      },
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+
+    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'));
+
+    expect(screen.getByLabelText('upload overlay')).toBeDisabled();
+  });
+
+  it('should create correct name for file which contains elements list as content', async () => {
+    const { store } = renderComponentWithActionListener({
+      project: {
+        data: projectFixture,
+        loading: 'succeeded',
+        error: { message: '', name: '' },
+      },
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+
+    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' },
+    });
+
+    const actions = store.getActions();
+    fireEvent.click(screen.getByLabelText('upload overlay'));
+
+    expect(actions[0].meta.arg.filename).toBe('unknown.txt');
+  });
+
+  it('should update the form inputs based on overlay content provided by elements list', async () => {
+    renderComponent({
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+
+    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: '#NAME = John\n# DESCRIPTION = Some description\n# TYPE = GENETIC_VARIANT\n',
+      },
+    });
+
+    expect(screen.getByTestId('overlay-name')).toHaveValue('John');
+    expect(screen.getByTestId('overlay-description')).toHaveValue('Some description');
+    expect(screen.getByText('GENETIC_VARIANT')).toBeVisible();
+  });
+
+  it('should display correct filename', async () => {
+    const uploadedFile = new File(['file content'], 'test.txt', {
+      type: 'text/plain',
+    });
+    renderComponent({
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+
+    fireEvent.change(screen.getByTestId('dropzone-input'), {
+      target: { files: [uploadedFile] },
+    });
+
+    const dropzone: HTMLInputElement = screen.getByTestId('dropzone-input');
+    expect(dropzone?.files?.[0].name).toBe('test.txt');
+  });
+
+  it('should not submit when form is not filled', async () => {
+    renderComponent({
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+    expect(screen.getByTestId('overlay-description')).toHaveValue('');
+    fireEvent.click(screen.getByLabelText('upload overlay'));
+    expect(screen.getByLabelText('upload overlay')).not.toBeDisabled();
+  });
+  it('should navigate to overlays after clicking backward button', async () => {
+    const { store } = renderComponent({
+      drawer: drawerOverlaysStepOneFixture,
+      project: {
+        data: projectFixture,
+        loading: 'succeeded',
+        error: { message: '', name: '' },
+      },
+      overlays: {
+        data: [],
+        loading: 'idle',
+        error: DEFAULT_ERROR,
+        addOverlay: {
+          loading: 'idle',
+          error: DEFAULT_ERROR,
+        },
+      },
+    });
+
+    const backButton = screen.getByRole('back-button');
+
+    backButton.click();
+
+    const {
+      drawer: {
+        overlayDrawerState: { currentStep },
+      },
+    } = store.getState();
+
+    expect(currentStep).toBe(1);
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ed8f04b70e8c38a2107665c1a3957c9c862aa83c
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.tsx
@@ -0,0 +1,121 @@
+/* eslint-disable no-magic-numbers */
+import { DrawerHeadingBackwardButton } from '@/shared/DrawerHeadingBackwardButton';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { openOverlaysDrawer } from '@/redux/drawer/drawer.slice';
+import { Button } from '@/shared/Button';
+import { Input } from '@/shared/Input';
+import { Textarea } from '@/shared/Textarea';
+import { OverlaySelector } from './OverlaySelector';
+import { OVERLAY_GROUPS, OVERLAY_TYPES } from './UserOverlayForm.constants';
+import { FileUpload } from './FileUpload';
+import { useUserOverlayForm } from './hooks/useUserOverlayForm';
+
+export const UserOverlayForm = (): React.ReactNode => {
+  const dispatch = useAppDispatch();
+  const {
+    name,
+    type,
+    group,
+    description,
+    uploadedFile,
+    elementsList,
+    isPending,
+    handleChangeName,
+    handleChangeDescription,
+    handleChangeType,
+    handleChangeGroup,
+    handleChangeElementsList,
+    handleSubmit,
+    updateUserOverlayForm,
+    handleChangeUploadedFile,
+    handleChangeOverlayContent,
+  } = useUserOverlayForm();
+
+  const navigateToOverlays = (): void => {
+    dispatch(openOverlaysDrawer());
+  };
+
+  return (
+    <>
+      <DrawerHeadingBackwardButton backwardFunction={navigateToOverlays}>
+        Add overlay
+      </DrawerHeadingBackwardButton>
+      <form className="flex h-[calc(100%-93px)] max-h-[calc(100%-93px)] flex-col overflow-y-auto p-6">
+        <div className="mb-2.5">
+          <p className="mb-2.5 text-sm">Upload file</p>
+          <FileUpload
+            uploadedFile={uploadedFile}
+            updateUserOverlayForm={updateUserOverlayForm}
+            handleChangeFile={handleChangeUploadedFile}
+            handleChangeOverlayContent={handleChangeOverlayContent}
+          />
+          <p className="my-5 text-center">or</p>
+          <label className="text-sm" htmlFor="elementsList">
+            Provide list of elements here
+            <Textarea
+              id="elementsList"
+              name="elementsList"
+              data-testid="overlay-elements-list"
+              value={elementsList}
+              onChange={handleChangeElementsList}
+              rows={6}
+              placeholder="Type here"
+              className="mt-2.5"
+            />
+          </label>
+        </div>
+
+        <label className="mb-2.5 text-sm" htmlFor="name">
+          Name
+          <Input
+            type="text"
+            name="name"
+            id="name"
+            data-testid="overlay-name"
+            value={name}
+            onChange={handleChangeName}
+            placeholder="Overlays 11/07/2022"
+            sizeVariant="medium"
+            className="mt-2.5 text-xs"
+          />
+        </label>
+
+        <OverlaySelector
+          value={type}
+          onChange={handleChangeType}
+          items={OVERLAY_TYPES}
+          label="Type"
+        />
+
+        <OverlaySelector
+          value={group}
+          onChange={handleChangeGroup}
+          items={OVERLAY_GROUPS}
+          label="Select group"
+        />
+
+        <label className="mt-2.5 text-sm" htmlFor="description">
+          Description
+          <Textarea
+            id="description"
+            name="description"
+            value={description}
+            data-testid="overlay-description"
+            onChange={handleChangeDescription}
+            rows={4}
+            placeholder="Type Description"
+            className="mt-2.5"
+          />
+        </label>
+        <Button
+          className="mt-2.5 items-center justify-center self-start"
+          onClick={handleSubmit}
+          disabled={isPending}
+          aria-label="upload overlay"
+        >
+          Upload
+        </Button>
+      </form>
+    </>
+  );
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.constants.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b4622dbcb86e2a818d40f7c2a29cf3556f19fa6
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.constants.ts
@@ -0,0 +1,22 @@
+/* eslint-disable no-magic-numbers */
+export const OVERLAY_TYPES = [
+  {
+    id: 'GENERIC',
+    label: 'GENERIC',
+  },
+  {
+    id: 'GENETIC_VARIANT',
+    label: 'GENETIC_VARIANT',
+  },
+];
+
+export const OVERLAY_GROUPS = [
+  {
+    id: 'WITHOUT_GROUP',
+    label: 'Without group',
+  },
+];
+
+export const DEFAULT_GROUP = OVERLAY_GROUPS[0];
+
+export const DEFAULT_TYPE = OVERLAY_TYPES[0];
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.types.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..531a4e204a62d2bdb9742ce950ee84041c9cd92f
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.types.ts
@@ -0,0 +1 @@
+export type SelectorItem = { id: string; label: string };
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.test.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f3baf2ac99c9e6a9cca8831cbe4ce35bdbf2ac8
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.test.ts
@@ -0,0 +1,29 @@
+/* eslint-disable no-magic-numbers */
+import { processOverlayContentChange } from './UserOverlayForm.utils';
+
+const handleOverlayChange = jest.fn();
+
+describe('processOverlayContentChange', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should parse overlay file content and invoke the handleOverlayChange callback for valid lines', () => {
+    const fileContent = `#NAME = John\n# DESCRIPTION = Some description\n# TYPE = Type1\n`;
+
+    processOverlayContentChange(fileContent, handleOverlayChange);
+
+    expect(handleOverlayChange).toHaveBeenCalledTimes(3);
+    expect(handleOverlayChange).toHaveBeenCalledWith('NAME', 'John');
+    expect(handleOverlayChange).toHaveBeenCalledWith('DESCRIPTION', 'Some description');
+    expect(handleOverlayChange).toHaveBeenCalledWith('TYPE', 'Type1');
+  });
+
+  it('should handle lines with invalid format without calling handleOverlayChange', () => {
+    const fileContent = `InvalidLine1\n#InvalidLine2\n=InvalidLine3\n`;
+
+    processOverlayContentChange(fileContent, handleOverlayChange);
+
+    expect(handleOverlayChange).not.toHaveBeenCalled();
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3c499996a57dc078523f9177b6a58799d2198ac
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.utils.ts
@@ -0,0 +1,27 @@
+/* eslint-disable no-magic-numbers */
+
+type OverlayDataCallback = {
+  (nameType: string, value: string): void;
+};
+
+const OVERLAY_INFO_INDICATOR = '#';
+const ASSIGNMENT_OPERATOR = '=';
+
+export const processOverlayContentChange = (
+  fileContent: string,
+  callback: OverlayDataCallback,
+): void => {
+  const content = fileContent.trim();
+  const lines = content.split('\n');
+
+  lines.forEach(line => {
+    const isOverlayInfoLine = line.indexOf(OVERLAY_INFO_INDICATOR) === 0;
+    const hasAssignment = line.indexOf(ASSIGNMENT_OPERATOR) > 0;
+
+    if (isOverlayInfoLine && hasAssignment) {
+      const nameType = line.substring(1, line.indexOf(ASSIGNMENT_OPERATOR)).trim();
+      const value = line.substring(line.indexOf(ASSIGNMENT_OPERATOR) + 1).trim();
+      callback(nameType, value);
+    }
+  });
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.test.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..988e9ea0989902028959116c91971b6dcf26e984
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.test.ts
@@ -0,0 +1,59 @@
+import { renderHook } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { ChangeEvent } from 'react';
+import { useUserOverlayForm } from './useUserOverlayForm';
+
+describe('useUserOverlayForm', () => {
+  it('should update state when form fields are changed', () => {
+    const { Wrapper } = getReduxWrapperWithStore();
+    const { result } = renderHook(() => useUserOverlayForm(), { wrapper: Wrapper });
+
+    act(() => {
+      result.current.handleChangeType({ id: '1', label: 'Test Type' });
+      result.current.handleChangeGroup({ id: '1', label: 'Test Group' });
+    });
+
+    expect(result.current.type).toEqual({ id: '1', label: 'Test Type' });
+    expect(result.current.group).toEqual({ id: '1', label: 'Test Group' });
+  });
+
+  it('should update overlayContent when handleChangeOverlayContent is called', () => {
+    const { Wrapper } = getReduxWrapperWithStore();
+    const { result } = renderHook(() => useUserOverlayForm(), { wrapper: Wrapper });
+
+    act(() => {
+      result.current.handleChangeOverlayContent('Test Overlay Content');
+    });
+
+    expect(result.current.overlayContent).toBe('Test Overlay Content');
+  });
+  it('should update elementsList and overlayContent when handleChangeElementsList is called', () => {
+    const { Wrapper } = getReduxWrapperWithStore();
+    const { result } = renderHook(() => useUserOverlayForm(), { wrapper: Wrapper });
+
+    act(() => {
+      result.current.handleChangeElementsList({
+        target: { value: 'Test Elements List' },
+      } as ChangeEvent<HTMLTextAreaElement>);
+    });
+
+    expect(result.current.elementsList).toBe('Test Elements List');
+    expect(result.current.overlayContent).toBe('Test Elements List');
+  });
+  it('should update state variables based on updateUserOverlayForm', () => {
+    const { Wrapper } = getReduxWrapperWithStore();
+    const { result } = renderHook(() => useUserOverlayForm(), { wrapper: Wrapper });
+
+    act(() => {
+      result.current.updateUserOverlayForm('NAME', 'Test Name');
+    });
+
+    expect(result.current.name).toBe('Test Name');
+
+    act(() => {
+      result.current.updateUserOverlayForm('DESCRIPTION', 'Test Description');
+    });
+    expect(result.current.description).toBe('Test Description');
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dda281bf7dc92145f1902d283f40a7521a34ee25
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts
@@ -0,0 +1,143 @@
+import { useState, ChangeEvent } from 'react';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { projectIdSelector } from '@/redux/project/project.selectors';
+import { addOverlay } from '@/redux/overlays/overlays.thunks';
+import { loadingAddOverlay } from '@/redux/overlays/overlays.selectors';
+import { DEFAULT_GROUP, DEFAULT_TYPE, OVERLAY_TYPES } from '../UserOverlayForm.constants';
+import { SelectorItem } from '../UserOverlayForm.types';
+import { processOverlayContentChange } from '../UserOverlayForm.utils';
+
+type ReturnType = {
+  name: string;
+  type: SelectorItem;
+  group: SelectorItem;
+  description: string;
+  uploadedFile: File | null;
+  elementsList: string;
+  overlayContent: string;
+  projectId?: string;
+  isPending: boolean;
+  handleChangeName: (e: ChangeEvent<HTMLInputElement>) => void;
+  handleChangeDescription: (e: ChangeEvent<HTMLTextAreaElement>) => void;
+  handleChangeType: (value: SelectorItem) => void;
+  handleChangeGroup: (value: SelectorItem) => void;
+  handleChangeUploadedFile: (value: File) => void;
+  handleChangeOverlayContent: (value: string) => void;
+  handleChangeElementsList: (e: ChangeEvent<HTMLTextAreaElement>) => void;
+  handleSubmit: () => Promise<void>;
+  updateUserOverlayForm: (nameType: string, value: string) => void;
+};
+
+export const useUserOverlayForm = (): ReturnType => {
+  const dispatch = useAppDispatch();
+  const projectId = useAppSelector(projectIdSelector);
+  const loadingAddOverlayStatus = useAppSelector(loadingAddOverlay);
+  const isPending = loadingAddOverlayStatus === 'pending';
+
+  const [name, setName] = useState('');
+  const [type, setType] = useState<SelectorItem>(DEFAULT_TYPE);
+  const [group, setGroup] = useState<SelectorItem>(DEFAULT_GROUP);
+  const [description, setDescription] = useState('');
+  const [uploadedFile, setUploadedFile] = useState<File | null>(null);
+  const [elementsList, setElementsList] = useState('');
+  const [overlayContent, setOverlayContent] = useState('');
+
+  const handleChangeName = (e: ChangeEvent<HTMLInputElement>): void => {
+    setName(e.target.value);
+  };
+
+  const handleChangeDescription = (e: ChangeEvent<HTMLTextAreaElement>): void => {
+    setDescription(e.target.value);
+  };
+
+  const handleChangeType = (value: SelectorItem): void => {
+    setType(value);
+  };
+
+  const handleChangeGroup = (value: SelectorItem): void => {
+    setGroup(value);
+  };
+
+  const handleChangeUploadedFile = (value: File): void => {
+    setUploadedFile(value);
+  };
+
+  const handleChangeOverlayContent = (value: string): void => {
+    setOverlayContent(value);
+  };
+
+  const updateUserOverlayForm = (nameType: string, value: string): void => {
+    switch (nameType) {
+      case 'NAME':
+        setName(value);
+        break;
+      case 'DESCRIPTION':
+        setDescription(value);
+        break;
+      case 'TYPE': {
+        const foundType = OVERLAY_TYPES.find(el => el.id === value);
+        if (foundType) {
+          setType(foundType);
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  };
+
+  const handleChangeElementsList = (e: ChangeEvent<HTMLTextAreaElement>): void => {
+    processOverlayContentChange(e.target.value, updateUserOverlayForm); // When user change elements list we have to analyze content. If it contains overlay info like e.g NAME we need to update field NAME in form
+    setOverlayContent(e.target.value);
+    setElementsList(e.target.value);
+  };
+
+  const handleSubmit = async (): Promise<void> => {
+    let filename = uploadedFile?.name;
+
+    if (!filename) {
+      filename = 'unknown.txt'; // Elements list is sent to the backend as a file, so we need to create a filename for the elements list.
+    }
+
+    if (!overlayContent || !projectId || !description || !name) return;
+
+    dispatch(
+      addOverlay({
+        content: overlayContent,
+        description,
+        filename,
+        name,
+        projectId,
+        type: type.id,
+      }),
+    );
+
+    setName('');
+    setDescription('');
+    setElementsList('');
+    setOverlayContent('');
+    setUploadedFile(null);
+  };
+
+  return {
+    name,
+    type,
+    group,
+    description,
+    uploadedFile,
+    elementsList,
+    overlayContent,
+    projectId,
+    isPending,
+    handleChangeName,
+    handleChangeDescription,
+    handleChangeType,
+    handleChangeGroup,
+    handleChangeElementsList,
+    handleSubmit,
+    updateUserOverlayForm,
+    handleChangeUploadedFile,
+    handleChangeOverlayContent,
+  };
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/index.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e51db0f06b59bd7e0dec1b5460263e3f47e5efc2
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/index.ts
@@ -0,0 +1 @@
+export { UserOverlayForm } from './UserOverlayForm.component';
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fbf7dc2eadb067a97bc4d4ecf389cf5389ee3dbb
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.test.tsx
@@ -0,0 +1,83 @@
+import { StoreType } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { render, screen } from '@testing-library/react';
+import { UserOverlays } from './UserOverlays.component';
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <UserOverlays />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('UserOverlays component', () => {
+  it('renders loading message when user is loading', () => {
+    renderComponent({
+      user: {
+        loading: 'pending',
+        authenticated: false,
+        error: { name: '', message: '' },
+        login: null,
+      },
+    });
+
+    expect(screen.getByText('Loading')).toBeInTheDocument();
+  });
+
+  it('renders login button when user is not authenticated', () => {
+    renderComponent({
+      user: {
+        loading: 'failed',
+        authenticated: false,
+        error: { name: '', message: '' },
+        login: null,
+      },
+    });
+
+    expect(screen.getByLabelText('login button')).toBeInTheDocument();
+  });
+
+  it('dispatches openLoginModal action when Login button is clicked', () => {
+    const { store } = renderComponent({
+      user: {
+        loading: 'failed',
+        authenticated: false,
+        error: { name: '', message: '' },
+        login: null,
+      },
+      modal: {
+        isOpen: false,
+        modalName: 'none',
+        modalTitle: '',
+        overviewImagesState: {},
+      },
+    });
+    screen.getByLabelText('login button').click();
+    const state = store.getState().modal;
+    expect(state.isOpen).toEqual(true);
+    expect(state.modalName).toEqual('login');
+  });
+
+  it('renders add overlay button when user is authenticated', () => {
+    renderComponent({
+      user: {
+        loading: 'succeeded',
+        authenticated: true,
+        error: { name: '', message: '' },
+        login: 'test',
+      },
+    });
+
+    expect(screen.getByLabelText('add overlay button')).toBeInTheDocument();
+  });
+});
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx
index d594f01698bd6bc04fc19d67b02dc196a7e22221..2db18b4201bb8ce15a36ea59994a5238e011cb79 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx
@@ -1,3 +1,4 @@
+import { displayAddOverlaysDrawer } from '@/redux/drawer/drawer.slice';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { openLoginModal } from '@/redux/modal/modal.slice';
@@ -8,27 +9,40 @@ export const UserOverlays = (): JSX.Element => {
   const dispatch = useAppDispatch();
   const loadingUser = useAppSelector(loadingUserSelector);
   const authenticatedUser = useAppSelector(authenticatedUserSelector);
+  const isPending = loadingUser === 'pending';
 
   const handleLoginClick = (): void => {
     dispatch(openLoginModal());
   };
 
+  const handleAddOverlay = (): void => {
+    dispatch(displayAddOverlaysDrawer());
+  };
+
   return (
     <div className="p-6">
-      {loadingUser === 'pending' && <h1>Loading</h1>}
+      {isPending && <h1>Loading</h1>}
 
-      {loadingUser !== 'pending' && !authenticatedUser && (
+      {!isPending && !authenticatedUser && (
         <>
           <p className="mb-5 font-semibold">User provided overlays:</p>
           <p className="mb-5 text-sm">
             You are not logged in, please login to upload and view custom overlays
           </p>
-          <Button onClick={handleLoginClick}>Login</Button>
+          <Button onClick={handleLoginClick} aria-label="login button">
+            Login
+          </Button>
         </>
       )}
 
-      {/* TODO: Implement user overlays */}
-      {authenticatedUser && <h1>Authenticated</h1>}
+      {authenticatedUser && (
+        <div className="flex items-center justify-between">
+          <p>User provided overlays:</p>
+          <Button onClick={handleAddOverlay} aria-label="add overlay button">
+            Add overlay
+          </Button>
+        </div>
+      )}
     </div>
   );
 };
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
index a533a5b83bc7ca1eb96e6d1210df63868420d05d..94bc618d7d632fd681c45a9f96b2523fd6b8dffc 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
@@ -26,6 +26,9 @@ const INITIAL_STATE: InitialStoreState = {
     },
     reactionDrawerState: {},
     bioEntityDrawerState: {},
+    overlayDrawerState: {
+      currentStep: 0,
+    },
   },
   drugs: {
     data: [
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerWrapper.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerWrapper.component.test.tsx
index 9477d59d8d24cfd5d3af9bec571e13694a482409..c36eb3a97ab71c8c936439e223f01849b459a275 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerWrapper.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/SearchDrawerWrapper.component.test.tsx
@@ -45,6 +45,9 @@ describe('SearchDrawerWrapper - component', () => {
         },
         reactionDrawerState: {},
         bioEntityDrawerState: {},
+        overlayDrawerState: {
+          currentStep: 0,
+        },
       },
     });
 
@@ -65,6 +68,9 @@ describe('SearchDrawerWrapper - component', () => {
         },
         reactionDrawerState: {},
         bioEntityDrawerState: {},
+        overlayDrawerState: {
+          currentStep: 0,
+        },
       },
     });
 
diff --git a/src/models/fixtures/overlaysFixture.ts b/src/models/fixtures/overlaysFixture.ts
index c0a26efd4daccf2dbd062e7b37a67ef6e2d1033a..d981dba341420fbd50d17cd5d20b2d51af3189b5 100644
--- a/src/models/fixtures/overlaysFixture.ts
+++ b/src/models/fixtures/overlaysFixture.ts
@@ -2,9 +2,25 @@ import { ZOD_SEED } from '@/constants';
 import { z } from 'zod';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { createFixture } from 'zod-fixture';
-import { mapOverlay } from '../mapOverlay';
+import {
+  createdOverlayFileSchema,
+  createdOverlaySchema,
+  mapOverlay,
+  uploadedOverlayFileContentSchema,
+} from '../mapOverlay';
 
 export const overlaysFixture = createFixture(z.array(mapOverlay), {
   seed: ZOD_SEED,
   array: { min: 2, max: 2 },
 });
+
+export const createdOverlayFileFixture = createFixture(createdOverlayFileSchema, {
+  seed: ZOD_SEED,
+});
+
+export const uploadedOverlayFileContentFixture = createFixture(uploadedOverlayFileContentSchema, {
+  seed: ZOD_SEED,
+});
+export const createdOverlayFixture = createFixture(createdOverlaySchema, {
+  seed: ZOD_SEED,
+});
diff --git a/src/models/mapOverlay.ts b/src/models/mapOverlay.ts
index a22b65aa5eed7751bc3033c30998972dadadd9c5..16da571736fd815bb63a9c6bef833c5ab2102c3a 100644
--- a/src/models/mapOverlay.ts
+++ b/src/models/mapOverlay.ts
@@ -12,3 +12,26 @@ export const mapOverlay = z.object({
   type: z.string(),
   order: z.number(),
 });
+
+export const createdOverlayFileSchema = z.object({
+  id: z.number(),
+  filename: z.string(),
+  length: z.number(),
+  owner: z.string(),
+  uploadedDataLength: z.number(),
+});
+
+export const uploadedOverlayFileContentSchema = createdOverlayFileSchema.extend({});
+
+export const createdOverlaySchema = z.object({
+  name: z.string(),
+  googleLicenseConsent: z.boolean(),
+  creator: z.string(),
+  description: z.string(),
+  genomeType: z.string().nullable(),
+  genomeVersion: z.string().nullable(),
+  idObject: z.number(),
+  publicOverlay: z.boolean(),
+  type: z.string(),
+  order: z.number(),
+});
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index 283a420a57d9f49509d4f43785b9ac9dda4b6ad3..433b6c499e2a2cd01c02670873976f46dd40d3db 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -35,5 +35,8 @@ export const apiPath = {
   getConfiguration: (): string => 'configuration/',
   getOverlayBioEntity: ({ overlayId, modelId }: { overlayId: number; modelId: number }): string =>
     `projects/${PROJECT_ID}/overlays/${overlayId}/models/${modelId}/bioEntities/`,
+  createOverlay: (projectId: string): string => `projects/${projectId}/overlays/`,
+  createOverlayFile: (): string => `files/`,
+  uploadOverlayFileContent: (fileId: number): string => `files/${fileId}:uploadContent`,
   getStatisticsById: (projectId: string): string => `projects/${projectId}/statistics/`,
 };
diff --git a/src/redux/drawer/drawer.constants.ts b/src/redux/drawer/drawer.constants.ts
index c04c02963c448309b61fa0d854c93875012d8514..f1035f976654ddf96e73684f3e23181e1b2722e4 100644
--- a/src/redux/drawer/drawer.constants.ts
+++ b/src/redux/drawer/drawer.constants.ts
@@ -12,4 +12,7 @@ export const DRAWER_INITIAL_STATE: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts
index 8ecb83281c39220a3909308e0c902e518f68842b..8a60b60fab8b52fdd93845ff79470c2f480d794d 100644
--- a/src/redux/drawer/drawer.reducers.ts
+++ b/src/redux/drawer/drawer.reducers.ts
@@ -31,6 +31,13 @@ export const openSubmapsDrawerReducer = (state: DrawerState): void => {
 export const openOverlaysDrawerReducer = (state: DrawerState): void => {
   state.isOpen = true;
   state.drawerName = 'overlays';
+  state.overlayDrawerState.currentStep = STEP.FIRST;
+};
+
+export const displayAddOverlaysDrawerReducer = (state: DrawerState): void => {
+  state.isOpen = true;
+  state.drawerName = 'overlays';
+  state.overlayDrawerState.currentStep = STEP.SECOND;
 };
 
 export const selectTabReducer = (
diff --git a/src/redux/drawer/drawer.selectors.ts b/src/redux/drawer/drawer.selectors.ts
index 060a652a62b8ffb78d487d9cc81d0fc530169368..26e9a90b8f01ba8f2e353c8aa8310ff9917f9fcd 100644
--- a/src/redux/drawer/drawer.selectors.ts
+++ b/src/redux/drawer/drawer.selectors.ts
@@ -98,3 +98,13 @@ export const currentDrawerReactionIdSelector = createSelector(
   reactionDrawerStateSelector,
   state => state?.reactionId,
 );
+
+export const overlayDrawerStateSelector = createSelector(
+  drawerSelector,
+  state => state.overlayDrawerState,
+);
+
+export const currentStepOverlayDrawerStateSelector = createSelector(
+  overlayDrawerStateSelector,
+  state => state.currentStep,
+);
diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts
index 769b5cf0a23560b03b607ccfff67beff81e429f4..98e073aea596150c61886bba0af7636abb6af68e 100644
--- a/src/redux/drawer/drawer.slice.ts
+++ b/src/redux/drawer/drawer.slice.ts
@@ -13,6 +13,7 @@ import {
   openSearchDrawerWithSelectedTabReducer,
   openSubmapsDrawerReducer,
   selectTabReducer,
+  displayAddOverlaysDrawerReducer,
 } from './drawer.reducers';
 import { DRAWER_INITIAL_STATE } from './drawer.constants';
 
@@ -24,6 +25,7 @@ const drawerSlice = createSlice({
     openSearchDrawerWithSelectedTab: openSearchDrawerWithSelectedTabReducer,
     openSubmapsDrawer: openSubmapsDrawerReducer,
     openOverlaysDrawer: openOverlaysDrawerReducer,
+    displayAddOverlaysDrawer: displayAddOverlaysDrawerReducer,
     selectTab: selectTabReducer,
     closeDrawer: closeDrawerReducer,
     displayDrugsList: displayDrugsListReducer,
@@ -41,6 +43,7 @@ export const {
   openSearchDrawerWithSelectedTab,
   openSubmapsDrawer,
   openOverlaysDrawer,
+  displayAddOverlaysDrawer,
   selectTab,
   closeDrawer,
   displayDrugsList,
diff --git a/src/redux/drawer/drawer.types.ts b/src/redux/drawer/drawer.types.ts
index 44ed65164ace31db7a1ca07561382e8c6d61ffa7..075fcd1961a96c79e242b41a9a81273e4f56f9a5 100644
--- a/src/redux/drawer/drawer.types.ts
+++ b/src/redux/drawer/drawer.types.ts
@@ -10,6 +10,10 @@ export type SearchDrawerState = {
   selectedSearchElement: string;
 };
 
+export type OverlayDrawerState = {
+  currentStep: number;
+};
+
 export type ReactionDrawerState = {
   reactionId?: number;
 };
@@ -24,6 +28,7 @@ export type DrawerState = {
   searchDrawerState: SearchDrawerState;
   reactionDrawerState: ReactionDrawerState;
   bioEntityDrawerState: BioEntityDrawerState;
+  overlayDrawerState: OverlayDrawerState;
 };
 
 export type OpenSearchDrawerWithSelectedTabReducerPayload = string;
diff --git a/src/redux/drawer/drawerFixture.ts b/src/redux/drawer/drawerFixture.ts
index f96232938b74978fd097d7a13cc23ea4a4f89914..29b32d3aeb1d5f53ff6ee34abd86d325d6a71e8b 100644
--- a/src/redux/drawer/drawerFixture.ts
+++ b/src/redux/drawer/drawerFixture.ts
@@ -12,6 +12,9 @@ export const initialStateFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
 
 export const openedDrawerSubmapsFixture: DrawerState = {
@@ -26,6 +29,9 @@ export const openedDrawerSubmapsFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
 
 export const drawerSearchStepOneFixture: DrawerState = {
@@ -40,6 +46,9 @@ export const drawerSearchStepOneFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
 
 export const drawerSearchDrugsStepTwoFixture: DrawerState = {
@@ -54,6 +63,9 @@ export const drawerSearchDrugsStepTwoFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
 
 export const drawerSearchChemicalsStepTwoFixture: DrawerState = {
@@ -68,6 +80,26 @@ export const drawerSearchChemicalsStepTwoFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
+};
+
+export const drawerOverlaysStepOneFixture: DrawerState = {
+  isOpen: true,
+  drawerName: 'overlays',
+  searchDrawerState: {
+    currentStep: 0,
+    stepType: 'none',
+    selectedValue: undefined,
+    listOfBioEnitites: [],
+    selectedSearchElement: '',
+  },
+  reactionDrawerState: {},
+  bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 2,
+  },
 };
 
 export const openedExportDrawerFixture: DrawerState = {
@@ -82,4 +114,7 @@ export const openedExportDrawerFixture: DrawerState = {
   },
   reactionDrawerState: {},
   bioEntityDrawerState: {},
+  overlayDrawerState: {
+    currentStep: 0,
+  },
 };
diff --git a/src/redux/overlays/overlays.constants.ts b/src/redux/overlays/overlays.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dda564cda0098e449d5adadb219d86b8320378e4
--- /dev/null
+++ b/src/redux/overlays/overlays.constants.ts
@@ -0,0 +1,2 @@
+/* eslint-disable no-magic-numbers */
+export const CHUNK_SIZE = 65535 * 8;
diff --git a/src/redux/overlays/overlays.mock.ts b/src/redux/overlays/overlays.mock.ts
index cdb5593cba6857acbbcafbb9d1886f93457e33cf..3e1557ba4e7604dd58aa294eeba2bd13ebced06e 100644
--- a/src/redux/overlays/overlays.mock.ts
+++ b/src/redux/overlays/overlays.mock.ts
@@ -6,6 +6,10 @@ export const OVERLAYS_INITIAL_STATE_MOCK: OverlaysState = {
   data: [],
   loading: 'idle',
   error: DEFAULT_ERROR,
+  addOverlay: {
+    loading: 'idle',
+    error: DEFAULT_ERROR,
+  },
 };
 
 export const PUBLIC_OVERLAYS_MOCK: MapOverlay[] = [
@@ -77,4 +81,17 @@ export const OVERLAYS_PUBLIC_FETCHED_STATE_MOCK: OverlaysState = {
   data: PUBLIC_OVERLAYS_MOCK,
   loading: 'succeeded',
   error: DEFAULT_ERROR,
+  addOverlay: {
+    loading: 'idle',
+    error: DEFAULT_ERROR,
+  },
+};
+
+export const ADD_OVERLAY_MOCK = {
+  content: 'test',
+  description: 'test',
+  filename: 'unknown.txt',
+  name: 'test',
+  projectId: 'pd',
+  type: 'GENERIC',
 };
diff --git a/src/redux/overlays/overlays.reducers.test.ts b/src/redux/overlays/overlays.reducers.test.ts
index d0116134dfce094433f2fd9a9948ee98541cd1eb..2fe92673346f59194fc6f78a5a85443d31d0b273 100644
--- a/src/redux/overlays/overlays.reducers.test.ts
+++ b/src/redux/overlays/overlays.reducers.test.ts
@@ -1,15 +1,23 @@
+/* eslint-disable no-magic-numbers */
 import { PROJECT_ID } from '@/constants';
-import { overlaysFixture } from '@/models/fixtures/overlaysFixture';
+import {
+  createdOverlayFileFixture,
+  createdOverlayFixture,
+  overlaysFixture,
+  uploadedOverlayFileContentFixture,
+} from '@/models/fixtures/overlaysFixture';
 import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
 import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { HttpStatusCode } from 'axios';
+import { waitFor } from '@testing-library/react';
 import { apiPath } from '../apiPath';
 import overlaysReducer from './overlays.slice';
-import { getAllPublicOverlaysByProjectId } from './overlays.thunks';
+import { addOverlay, getAllPublicOverlaysByProjectId } from './overlays.thunks';
 import { OverlaysState } from './overlays.types';
+import { ADD_OVERLAY_MOCK } from './overlays.mock';
 
 const mockedAxiosClient = mockNetworkResponse();
 
@@ -17,6 +25,10 @@ const INITIAL_STATE: OverlaysState = {
   data: [],
   loading: 'idle',
   error: { name: '', message: '' },
+  addOverlay: {
+    loading: 'idle',
+    error: { name: '', message: '' },
+  },
 };
 
 describe('overlays reducer', () => {
@@ -30,7 +42,7 @@ describe('overlays reducer', () => {
 
     expect(overlaysReducer(undefined, action)).toEqual(INITIAL_STATE);
   });
-  it('should update store after succesfull getAllPublicOverlaysByProjectId query', async () => {
+  it('should update store after successful getAllPublicOverlaysByProjectId query', async () => {
     mockedAxiosClient
       .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true }))
       .reply(HttpStatusCode.Ok, overlaysFixture);
@@ -76,4 +88,58 @@ describe('overlays reducer', () => {
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
+  it('should update store when addOverlay is pending', async () => {
+    mockedAxiosClient
+      .onPost(apiPath.createOverlayFile())
+      .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(123))
+      .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.createOverlay('pd'))
+      .reply(HttpStatusCode.Ok, createdOverlayFixture);
+
+    await store.dispatch(addOverlay(ADD_OVERLAY_MOCK));
+    const { loading } = store.getState().overlays.addOverlay;
+
+    waitFor(() => {
+      expect(loading).toEqual('pending');
+    });
+  });
+
+  it('should update store after successful addOverlay', async () => {
+    mockedAxiosClient
+      .onPost(apiPath.createOverlayFile())
+      .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(123))
+      .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.createOverlay('pd'))
+      .reply(HttpStatusCode.Ok, createdOverlayFixture);
+
+    await store.dispatch(addOverlay(ADD_OVERLAY_MOCK));
+    const { loading, error } = store.getState().overlays.addOverlay;
+
+    expect(loading).toEqual('succeeded');
+    expect(error).toEqual({ message: '', name: '' });
+  });
+  it('should update store after failed addOverlay', async () => {
+    mockedAxiosClient.onPost(apiPath.createOverlayFile()).reply(HttpStatusCode.NotFound, undefined);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(123))
+      .reply(HttpStatusCode.NotFound, undefined);
+
+    mockedAxiosClient.onPost(apiPath.createOverlay('pd')).reply(HttpStatusCode.NotFound, undefined);
+
+    await store.dispatch(addOverlay(ADD_OVERLAY_MOCK));
+    const { loading } = store.getState().overlays.addOverlay;
+
+    expect(loading).toEqual('failed');
+  });
 });
diff --git a/src/redux/overlays/overlays.reducers.ts b/src/redux/overlays/overlays.reducers.ts
index 99e493ea4f4f7b6d9288b0c28cfb9234057e9bc9..d8f12eef16e426ef9c7aef040030fd20963a1842 100644
--- a/src/redux/overlays/overlays.reducers.ts
+++ b/src/redux/overlays/overlays.reducers.ts
@@ -1,5 +1,5 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
-import { getAllPublicOverlaysByProjectId } from './overlays.thunks';
+import { addOverlay, getAllPublicOverlaysByProjectId } from './overlays.thunks';
 import { OverlaysState } from './overlays.types';
 
 export const getAllPublicOverlaysByProjectIdReducer = (
@@ -17,3 +17,16 @@ export const getAllPublicOverlaysByProjectIdReducer = (
     // TODO to discuss manage state of failure
   });
 };
+
+export const addOverlayReducer = (builder: ActionReducerMapBuilder<OverlaysState>): void => {
+  builder.addCase(addOverlay.pending, state => {
+    state.addOverlay.loading = 'pending';
+  });
+  builder.addCase(addOverlay.fulfilled, state => {
+    state.addOverlay.loading = 'succeeded';
+  });
+  builder.addCase(addOverlay.rejected, state => {
+    state.addOverlay.loading = 'failed';
+    // TODO to discuss manage state of failure
+  });
+};
diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts
index f9d36cad0a63c93ae55565e795b897c6bbd4db3e..b1035c5c7e6a359fdfe44884adbcaf49568d12b8 100644
--- a/src/redux/overlays/overlays.selectors.ts
+++ b/src/redux/overlays/overlays.selectors.ts
@@ -7,3 +7,8 @@ export const overlaysDataSelector = createSelector(
   overlaysSelector,
   overlays => overlays?.data || [],
 );
+
+export const loadingAddOverlay = createSelector(
+  overlaysSelector,
+  state => state.addOverlay.loading,
+);
diff --git a/src/redux/overlays/overlays.slice.ts b/src/redux/overlays/overlays.slice.ts
index 8d259288d5d8eb69d15d48a8408d4e83d6342573..5f49156af3e1b54b2074c7ae9b653e80f7488027 100644
--- a/src/redux/overlays/overlays.slice.ts
+++ b/src/redux/overlays/overlays.slice.ts
@@ -1,11 +1,15 @@
 import { createSlice } from '@reduxjs/toolkit';
-import { getAllPublicOverlaysByProjectIdReducer } from './overlays.reducers';
+import { addOverlayReducer, getAllPublicOverlaysByProjectIdReducer } from './overlays.reducers';
 import { OverlaysState } from './overlays.types';
 
 const initialState: OverlaysState = {
   data: [],
   loading: 'idle',
   error: { name: '', message: '' },
+  addOverlay: {
+    loading: 'idle',
+    error: { name: '', message: '' },
+  },
 };
 
 const overlaysState = createSlice({
@@ -14,6 +18,7 @@ const overlaysState = createSlice({
   reducers: {},
   extraReducers: builder => {
     getAllPublicOverlaysByProjectIdReducer(builder);
+    addOverlayReducer(builder);
   },
 });
 
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
index 6a01933378559ff68685808752a45c4cc11ce9aa..330e5ee98aba5a26299f21b8d56c9b29e16d2d1e 100644
--- a/src/redux/overlays/overlays.thunks.ts
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -1,10 +1,17 @@
-import { mapOverlay } from '@/models/mapOverlay';
+/* eslint-disable no-magic-numbers */
+import {
+  createdOverlayFileSchema,
+  createdOverlaySchema,
+  mapOverlay,
+  uploadedOverlayFileContentSchema,
+} from '@/models/mapOverlay';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
-import { MapOverlay } from '@/types/models';
+import { CreatedOverlay, CreatedOverlayFile, MapOverlay } from '@/types/models';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { z } from 'zod';
 import { apiPath } from '../apiPath';
+import { CHUNK_SIZE } from './overlays.constants';
 
 export const getAllPublicOverlaysByProjectId = createAsyncThunk(
   'overlays/getAllPublicOverlaysByProjectId',
@@ -18,3 +25,136 @@ export const getAllPublicOverlaysByProjectId = createAsyncThunk(
     return isDataValid ? response.data : [];
   },
 );
+
+/** UTILS */
+
+type CreateFileArgs = {
+  filename: string;
+  content: string;
+};
+
+const createFile = async ({ filename, content }: CreateFileArgs): Promise<CreatedOverlayFile> => {
+  const fileParams = {
+    filename: `C:\\fakepath\\${filename}`,
+    length: content.length.toString(),
+  };
+
+  const response = await axiosInstance.post(
+    apiPath.createOverlayFile(),
+    new URLSearchParams(fileParams),
+    {
+      withCredentials: true,
+    },
+  );
+
+  const isDataValid = validateDataUsingZodSchema(response.data, createdOverlayFileSchema);
+  return isDataValid ? response.data : undefined;
+};
+
+type UploadContentArgs = {
+  createdFile: CreatedOverlayFile;
+  overlayContent: string;
+};
+const uploadContent = async ({ createdFile, overlayContent }: UploadContentArgs): Promise<void> => {
+  const data = new Uint8Array(new TextEncoder().encode(overlayContent));
+  let uploadedLength = 0;
+
+  const sendChunk = async (): Promise<void> => {
+    if (uploadedLength >= data.length) {
+      return;
+    }
+
+    const chunk = data.slice(uploadedLength, uploadedLength + CHUNK_SIZE);
+
+    const responeJSON = await fetch(
+      `${process.env.NEXT_PUBLIC_BASE_API_URL}/${apiPath.uploadOverlayFileContent(createdFile.id)}`,
+      {
+        method: 'POST',
+        credentials: 'include',
+        body: chunk,
+      },
+    );
+
+    const response = await responeJSON.json();
+    validateDataUsingZodSchema(response, uploadedOverlayFileContentSchema);
+
+    uploadedLength += chunk.length;
+    sendChunk();
+  };
+
+  await sendChunk();
+};
+
+type CreatedOverlayArgs = {
+  createdFile: CreatedOverlayFile;
+  description: string;
+  name: string;
+  type: string;
+  projectId: string;
+};
+
+const creteOverlay = async ({
+  createdFile,
+  description,
+  type,
+  name,
+  projectId,
+}: CreatedOverlayArgs): Promise<CreatedOverlay> => {
+  const data = {
+    name,
+    description,
+    filename: createdFile.filename,
+    googleLicenseConsent: false.toString(),
+    type,
+    fileId: createdFile.id.toString(),
+  };
+
+  const overlay = new URLSearchParams(data);
+
+  const response = await axiosInstance.post(apiPath.createOverlay(projectId), overlay, {
+    withCredentials: true,
+  });
+
+  const isDataValid = validateDataUsingZodSchema(response.data, createdOverlaySchema);
+
+  return isDataValid ? response.data : undefined;
+};
+
+type AddOverlayArgs = {
+  filename: string;
+  content: string;
+  description: string;
+  type: string;
+  name: string;
+  projectId: string;
+};
+
+export const addOverlay = createAsyncThunk(
+  'overlays/addOverlay',
+  async ({
+    filename,
+    content,
+    description,
+    name,
+    type,
+    projectId,
+  }: AddOverlayArgs): Promise<void> => {
+    const createdFile = await createFile({
+      filename,
+      content,
+    });
+
+    await uploadContent({
+      createdFile,
+      overlayContent: content,
+    });
+
+    await creteOverlay({
+      createdFile,
+      description,
+      name,
+      type,
+      projectId,
+    });
+  },
+);
diff --git a/src/redux/overlays/overlays.types.ts b/src/redux/overlays/overlays.types.ts
index ee00e94527ebcf6d68a453c26e0808f94874b1fa..15d4d813a5879a8e5986e1daf411eceda9f5ef55 100644
--- a/src/redux/overlays/overlays.types.ts
+++ b/src/redux/overlays/overlays.types.ts
@@ -1,4 +1,12 @@
 import { FetchDataState } from '@/types/fetchDataState';
+import { Loading } from '@/types/loadingState';
 import { MapOverlay } from '@/types/models';
 
-export type OverlaysState = FetchDataState<MapOverlay[] | []>;
+export type AddOverlayState = {
+  addOverlay: {
+    loading: Loading;
+    error: Error;
+  };
+};
+
+export type OverlaysState = FetchDataState<MapOverlay[] | []> & AddOverlayState;
diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts
index 610a6cce94495eb86214761dc84c5bc185a56609..c5ac340314f157036c59a0cc3cd648ae701582bd 100644
--- a/src/redux/project/project.selectors.ts
+++ b/src/redux/project/project.selectors.ts
@@ -31,3 +31,8 @@ export const projectDirectorySelector = createSelector(
   projectDataSelector,
   projectData => projectData?.directory,
 );
+
+export const projectIdSelector = createSelector(
+  projectDataSelector,
+  projectData => projectData?.projectId,
+);
diff --git a/src/shared/Input/Input.component.test.tsx b/src/shared/Input/Input.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a26f46fcb939eb0c8e25756218715db9b95d4af8
--- /dev/null
+++ b/src/shared/Input/Input.component.test.tsx
@@ -0,0 +1,70 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { Input } from './Input.component';
+
+describe('Input - component', () => {
+  it('should render with proper testid', () => {
+    render(<Input data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toBeInTheDocument();
+  });
+
+  it('should apply the default style and size variants when none are provided', () => {
+    render(<Input data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveClass('bg-cultured');
+    expect(inputElement).toHaveClass('rounded-s');
+  });
+
+  it('should apply the specified style variant', () => {
+    render(<Input styleVariant="primary" data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveClass('bg-cultured');
+  });
+
+  it('should apply the specified size variant', () => {
+    render(<Input sizeVariant="medium" data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveClass('rounded-lg');
+    expect(inputElement).toHaveClass('h-12');
+    expect(inputElement).toHaveClass('text-sm');
+  });
+
+  it('should merge custom class with style and size variant classes', () => {
+    render(
+      <Input
+        className="text-red-500"
+        styleVariant="primary"
+        sizeVariant="medium"
+        data-testid="input"
+      />,
+    );
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveClass('text-red-500');
+    expect(inputElement).toHaveClass('h-12');
+    expect(inputElement).toHaveClass('bg-cultured');
+  });
+
+  it(' should handle onChange event', () => {
+    const handleChange = jest.fn();
+    render(<Input onChange={handleChange} data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    fireEvent.change(inputElement, { target: { value: 'Hello, World!' } });
+    expect(handleChange).toHaveBeenCalledTimes(1);
+  });
+
+  it('should render with a placeholder', () => {
+    const placeholderText = 'Type here...';
+    render(<Input placeholder={placeholderText} data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveAttribute('placeholder', placeholderText);
+  });
+
+  it('should render with a default value', () => {
+    const defaultValue = 'Initial value';
+    render(<Input defaultValue={defaultValue} data-testid="input" />);
+    const inputElement = screen.getByTestId('input');
+    expect(inputElement).toHaveValue(defaultValue);
+  });
+});
diff --git a/src/shared/Input/Input.component.tsx b/src/shared/Input/Input.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..68cf9097dbb05b652a539d52cb0ccdefdc9901d1
--- /dev/null
+++ b/src/shared/Input/Input.component.tsx
@@ -0,0 +1,33 @@
+import React, { InputHTMLAttributes } from 'react';
+import { twMerge } from 'tailwind-merge';
+
+type StyleVariant = 'primary';
+type SizeVariant = 'small' | 'medium';
+
+type InputProps = {
+  className?: string;
+  styleVariant?: StyleVariant;
+  sizeVariant?: SizeVariant;
+} & InputHTMLAttributes<HTMLInputElement>;
+
+const styleVariants = {
+  primary:
+    'w-full border border-transparent bg-cultured px-2 py-2.5 font-semibold outline-none hover:border-greyscale-600 focus:border-greyscale-600',
+} as const;
+
+const sizeVariants = {
+  small: 'rounded-s h-10 text-xs',
+  medium: 'rounded-lg h-12 text-sm',
+} as const;
+
+export const Input = ({
+  className = '',
+  sizeVariant = 'small',
+  styleVariant = 'primary',
+  ...props
+}: InputProps): React.ReactNode => (
+  <input
+    {...props}
+    className={twMerge(styleVariants[styleVariant], sizeVariants[sizeVariant], className)}
+  />
+);
diff --git a/src/shared/Input/index.ts b/src/shared/Input/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dfcccdc632e6f3970118276e0d157d0b4cef2d7b
--- /dev/null
+++ b/src/shared/Input/index.ts
@@ -0,0 +1 @@
+export { Input } from './Input.component';
diff --git a/src/shared/Textarea/Textarea.component.test.tsx b/src/shared/Textarea/Textarea.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e337f637ad4575a1b3d334b1d7f13d7abfaa3759
--- /dev/null
+++ b/src/shared/Textarea/Textarea.component.test.tsx
@@ -0,0 +1,53 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { Textarea } from './Textarea.component';
+
+describe('Textarea - Component', () => {
+  it('should render with proper testid', () => {
+    render(<Textarea data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toBeInTheDocument();
+  });
+
+  it('should apply the default style variant when none is provided', () => {
+    render(<Textarea data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toHaveClass('bg-cultured');
+  });
+
+  it('should apply the specified style variant', () => {
+    render(<Textarea styleVariant="primary" data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toHaveClass('bg-cultured');
+  });
+
+  it('should merge custom class with style variant classes', () => {
+    render(<Textarea className="text-red-500" styleVariant="primary" data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toHaveClass('text-red-500');
+    expect(textareaElement).toHaveClass('bg-cultured');
+  });
+
+  it('should handle onChange event', () => {
+    const handleChange = jest.fn();
+    render(<Textarea onChange={handleChange} data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    fireEvent.change(textareaElement, { target: { value: 'Hello, World!' } });
+    expect(handleChange).toHaveBeenCalledTimes(1);
+  });
+
+  it('should render with a placeholder', () => {
+    const placeholderText = 'Type here...';
+    render(<Textarea placeholder={placeholderText} data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toHaveAttribute('placeholder', placeholderText);
+  });
+
+  it('should render with a default value', () => {
+    const defaultValue = 'Initial value';
+    render(<Textarea defaultValue={defaultValue} data-testid="textarea" />);
+    const textareaElement = screen.getByTestId('textarea');
+    expect(textareaElement).toHaveValue(defaultValue);
+  });
+});
diff --git a/src/shared/Textarea/Textarea.component.tsx b/src/shared/Textarea/Textarea.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b300f21a1f13b750181fa14b32def0fa24802853
--- /dev/null
+++ b/src/shared/Textarea/Textarea.component.tsx
@@ -0,0 +1,22 @@
+import React, { TextareaHTMLAttributes } from 'react';
+import { twMerge } from 'tailwind-merge';
+
+type StyleVariant = 'primary';
+
+type TextareaProps = {
+  className?: string;
+  styleVariant?: StyleVariant;
+} & TextareaHTMLAttributes<HTMLTextAreaElement>;
+
+const styleVariants = {
+  primary:
+    'w-full resize-none rounded-lg border border-transparent bg-cultured px-2 py-2.5 text-xs font-semibold outline-none  hover:border-greyscale-600 focus:border-greyscale-600',
+} as const;
+
+export const Textarea = ({
+  className = '',
+  styleVariant = 'primary',
+  ...props
+}: TextareaProps): React.ReactNode => (
+  <textarea {...props} className={twMerge(styleVariants[styleVariant], className)} />
+);
diff --git a/src/shared/Textarea/index.ts b/src/shared/Textarea/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..11d2fe00cef1e12fbe84e4fe247fc6cf436b4fcd
--- /dev/null
+++ b/src/shared/Textarea/index.ts
@@ -0,0 +1 @@
+export { Textarea } from './Textarea.component';
diff --git a/src/types/models.ts b/src/types/models.ts
index bdf8fd63d5d5b94b5051c84a34f3bbf358291153..8f4582e557ce84606857b048b60bb9d7518ca18a 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -10,7 +10,12 @@ import { drugSchema } from '@/models/drugSchema';
 import { elementSearchResult, elementSearchResultType } from '@/models/elementSearchResult';
 import { loginSchema } from '@/models/loginSchema';
 import { mapBackground } from '@/models/mapBackground';
-import { mapOverlay } from '@/models/mapOverlay';
+import {
+  createdOverlayFileSchema,
+  createdOverlaySchema,
+  mapOverlay,
+  uploadedOverlayFileContentSchema,
+} from '@/models/mapOverlay';
 import { mapModelSchema } from '@/models/modelSchema';
 import { organism } from '@/models/organism';
 import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema';
@@ -55,5 +60,8 @@ export type Login = z.infer<typeof loginSchema>;
 export type ConfigurationOption = z.infer<typeof configurationOptionSchema>;
 export type Configuration = z.infer<typeof configurationSchema>;
 export type OverlayBioEntity = z.infer<typeof overlayBioEntitySchema>;
+export type CreatedOverlayFile = z.infer<typeof createdOverlayFileSchema>;
+export type UploadedOverlayFileContent = z.infer<typeof uploadedOverlayFileContentSchema>;
+export type CreatedOverlay = z.infer<typeof createdOverlaySchema>;
 export type Color = z.infer<typeof colorSchema>;
 export type Statistics = z.infer<typeof statisticsSchema>;
diff --git a/src/utils/query-manager/useReduxBusQueryManager.test.ts b/src/utils/query-manager/useReduxBusQueryManager.test.ts
index dc4bc4b6595a1966de2cabb7c3a54143e033ef42..77244318601a6d608b143772ca813e7e390e4eaf 100644
--- a/src/utils/query-manager/useReduxBusQueryManager.test.ts
+++ b/src/utils/query-manager/useReduxBusQueryManager.test.ts
@@ -25,6 +25,10 @@ describe('useReduxBusQueryManager - util', () => {
       data: [],
       loading: 'succeeded' as Loading,
       error: { name: '', message: '' },
+      addOverlay: {
+        loading: 'idle' as Loading,
+        error: { name: '', message: '' },
+      },
     };
 
     const { Wrapper } = getReduxWrapperWithStore({