diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e7aa023eead2a3518860c2af9e7f81421764a4b8..f5f8f23335661abbdcfc33692e02ab48d24a4846 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,6 +4,8 @@ stages:
 - testing
 - security
 - docs
+- package
+- release
 
 # Change pip's cache directory to be inside the project directory since we can
 # only cache local items.
@@ -11,37 +13,42 @@ variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
 
 before_script:
-- python --version  # For debugging
+#- python --version  # For debugging
+- export PIP_ROOT_USER_ACTION=ignore # surpress the warning
 - pip install --upgrade pip
-- pip install --user -r requirements.txt  # install dependencies from file
-
+- pip install --user --no-warn-script-location -r requirements.txt  # install dependencies from file
 PEP8:
   stage: linting
+  needs: []
   script:
   - pip install flake8
   - flake8 --count --max-line-length=88 .
 
 Pylint:
   stage: linting
+  needs: []
   script:
   - pip install pylint
   - find . -type f -name '*.py' | xargs pylint -rn --rcfile='plotid/.pylintrc'  # Find all python files and check the code with pylint
 
 Autoformatting:
   stage: linting
+  needs: []
   script:
   - pip install black
   - black --check --verbose --diff --color .
 
 Typechecker:
   stage: linting
+  needs: []
   script:
   - pip install mypy
-  - mypy --ignore-missing-imports --strict plotid tests examples
+  - mypy --ignore-missing-imports --strict plotid examples
   allow_failure: true
 
 test:
   stage: testing
+  needs: []
   tags:
   - docker
   script:
@@ -60,7 +67,6 @@ pages:
     - public
   rules:
   - if: "$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH"
-  - when: manual
 
 sast:
   before_script:
@@ -86,6 +92,28 @@ include:
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/License-Scanning.gitlab-ci.yml
 
+build_and_upload:
+  stage: package
+  script:
+    - pip install build twine
+    - python3 -m build
+    - TWINE_PASSWORD=${CI_PYPI_TOKEN} TWINE_USERNAME=__token__ python3 -m twine upload dist/* 
+  rules:
+  - if: $CI_COMMIT_TAG
+
+release_job:
+  stage: release
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
+  rules:
+    - if: $CI_COMMIT_TAG  # Run this job when a tag is created
+  before_script:
+    - echo "No before script pls"
+  script:
+    - echo "running release_job"
+  release:  # See https://docs.gitlab.com/ee/ci/yaml/#release for available properties
+    tag_name: '$CI_COMMIT_TAG'
+    description: '$CI_COMMIT_TAG'
+
 # You can override the included template(s) by including variable overrides
 # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
 # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000000000000000000000000000000000000..4573bfcb9f8a6ea580cbd2d8cc4290c7e61c094c
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,19 @@
+cff-version: 1.2.0
+message: "If you use this software, please cite it as below."
+type: software
+title: plotID
+authors:
+  - family-names: Mayr
+    given-names: Hannes
+    email: hannes.mayr@stud.tu-darmstadt.de
+    affiliation: Institut für Fluidsystemtechnik - TU Darmstadt
+  - family-names: Hock
+    given-names: Martin
+    email: Martin.Hock@fst.tu-darmstadt.de
+    affiliation: Institut für Fluidsystemtechnik - TU Darmstadt
+    orcid: https://orcid.org/0000-0001-9917-3152
+  - family-names: Richter
+    given-names: Manuela
+    email: Manuela.Richter@fst.tu-darmstadt.de
+    affiliation: Institut für Fluidsystemtechnik - TU Darmstadt
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 301dcffe183ad8c90d91bc3a3f16505ea21e2fde..aa3acb6d89f6892d7921addbd1b4240ac8e3e850 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
 # Contributing
 Feel free to **report** all **issues** you encounter with plotID in our [issue tracker](https://git.rwth-aachen.de/plotid/plotid_python/-/issues). This will greatly help to improve plotID.  
-Contributing to plotID via **merge requests** is also highly appreciated. Please make sure that your code complies with the [PEP 8 - Style Guide](https://peps.python.org/pep-0008/) before creating a merge request. We try to have the whole code of plotID covered by unittests. So if you contribute new code to plotid please also provide unittests that cover the new functionality. Should you be interested in adding another plot engine to be supported by plotID, have a look at the section [Implement a new plot engine](#add-plot-engine). There are some hints how to proceed in this case.
+Contributing to plotID via **merge requests** is also highly appreciated. Please make sure that your code complies with the [PEP 8 - Style Guide](https://peps.python.org/pep-0008/) before creating a merge request. We enforce the [**black code style**](https://black.readthedocs.io/en/stable/). We try to have the whole code of plotID covered by unittests. So if you contribute new code to plotid please also provide unittests that cover the new functionality. Should you be interested in adding another plot engine to be supported by plotID, have a look at the section [Implement a new plot engine](#add-plot-engine). There are some hints how to proceed in this case.
 
 ## Setup development environment
 Clone the repository and install the dependencies:  
diff --git a/docs/source/conf.py b/docs/source/conf.py
index dbd16bdb547bea25b4eb461140b0e4f9f7a82a83..17c1b689b145153c2488a96fb2bd9708b9f3c9ec 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -40,6 +40,7 @@ autoapi_type = "python"
 autoapi_dirs = ["../../plotid", "../../tests"]
 # autoapi_keep_files = True
 myst_heading_anchors = 2
+autodoc_typehints = "description"
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ["_templates"]
diff --git a/plotid/__init__.py b/plotid/__init__.py
index e572fc044d3a808bc423cb488588097f4b1a40ce..19ab64467ef0756230c448d88061174adae920b6 100644
--- a/plotid/__init__.py
+++ b/plotid/__init__.py
@@ -10,5 +10,5 @@ research data, the plot is based on. Additionally, the script that created the
 plot will also be copied to the directory.
 """
 
-__version__ = "0.2.2"
+__version__ = "0.2.2-b3"
 __author__ = "Institut Fluidsystemtechnik within nfdi4ing at TU Darmstadt"
diff --git a/plotid/create_id.py b/plotid/create_id.py
index 61c1cc03be360848a3986bd00a6e7862721454ba..8bd66e16a5ff5e48f7f314ef3f6a78c671250a31 100644
--- a/plotid/create_id.py
+++ b/plotid/create_id.py
@@ -8,9 +8,10 @@ Functions:
 import time
 import uuid
 import qrcode
+from PIL import Image
 
 
-def create_id(id_method):
+def create_id(id_method: str) -> str:
     """
     Create an Identifier (str).
 
@@ -28,8 +29,7 @@ def create_id(id_method):
     """
     match id_method:
         case "time":
-            figure_id = time.time()  # UNIX Time
-            figure_id = hex(int(figure_id))  # convert time to hexadecimal str
+            figure_id = hex(int(time.time()))  # convert UNIX Time to hexadecimal str
             time.sleep(1)  # break for avoiding duplicate IDs
         case "random":
             figure_id = str(uuid.uuid4())  # creates a random UUID
@@ -44,7 +44,7 @@ def create_id(id_method):
     return figure_id
 
 
-def create_qrcode(figure_id):
+def create_qrcode(figure_id: str) -> Image:
     """
     Create a QR Code from an identifier.
 
diff --git a/plotid/plotoptions.py b/plotid/plotoptions.py
index eebdcc40541cf2de3500b9108457ba8e7e5a428c..8d6abbede22da98257b1fe216ebb017fa2928d1c 100644
--- a/plotid/plotoptions.py
+++ b/plotid/plotoptions.py
@@ -3,6 +3,19 @@
 """Contains the PlotOptions and PlotIDTransfer classes."""
 
 import os
+from typing import Any, Literal, TypedDict, TypeVar
+import matplotlib.pyplot as plt
+from PIL.Image import Image
+
+
+kwargs_types = TypedDict(
+    "kwargs_types",
+    {
+        "prefix": str,
+        "id_method": Literal["time", "random"],
+        "qrcode": bool,
+    },
+)
 
 
 class PlotOptions:
@@ -19,19 +32,19 @@ class PlotOptions:
 
     Attributes
     ----------
-    figs : figure object or list of figures
+    figs :
         Figures that will be tagged.
-    figure_ids: str or list of str
-        IDs that the figures are tagged with.
-    rotation : int
+    rotation :
         Rotation angle for the ID.
-    position : tuple
+    position :
         Relative position of the ID on the plot (x,y).
     **kwargs : dict, optional
         Extra arguments for additional plot options.
 
     Other Parameters
     ----------------
+    figure_ids: str or list of str
+        IDs that the figures are tagged with.
     prefix : str, optional
         Will be added as prefix to the ID.
     id_method : str, optional
@@ -44,7 +57,13 @@ class PlotOptions:
         Print ID on the plot. Default: True.
     """
 
-    def __init__(self, figs, rotation, position, **kwargs):
+    def __init__(
+        self,
+        figs: plt.Figure | Image | list[plt.Figure | Image],
+        rotation: int,
+        position: tuple[float, float],
+        **kwargs: Any,
+    ) -> None:
 
         self.figs = figs
         self.figure_ids = kwargs.get("figure_ids", [])
@@ -55,11 +74,11 @@ class PlotOptions:
         self.qrcode = kwargs.get("qrcode", False)
         self.id_on_plot = kwargs.get("id_on_plot", True)
 
-    def __str__(self):
+    def __str__(self) -> str:
         """Representation if an object of this class is printed."""
         return str(self.__class__) + ": " + str(self.__dict__)
 
-    def validate_input(self):
+    def validate_input(self) -> None:
         """
         Validate if input for PlotOptions is correct type.
 
@@ -71,7 +90,7 @@ class PlotOptions:
 
         Returns
         -------
-        0, if all checks succeed.
+        None.
 
         """
         # Input validation for figs is done in submodules tagplot_$engine.py
@@ -85,8 +104,6 @@ class PlotOptions:
         if not isinstance(self.figs, list):
             self.figs = [self.figs]
 
-        return 0
-
 
 class PlotIDTransfer:
     """
@@ -98,29 +115,36 @@ class PlotIDTransfer:
 
     Attributes
     ----------
-    figs : figure object or list of figures
+    figs :
         Tagged figures.
-    figure_ids: str or list of str
+    figure_ids:
         IDs that the figures are tagged with.
     """
 
-    def __init__(self, figs, figure_ids):
+    def __init__(
+        self, figs: plt.Figure | Image | list[plt.Figure | Image], figure_ids: list[str]
+    ) -> None:
         self.figs = figs
         self.figure_ids = figure_ids
         self.figure_ids = validate_list(self.figure_ids)
 
-    def __str__(self):
+    def __str__(self) -> str:
         """Representation if an object of this class is printed."""
         return str(self.__class__) + ": " + str(self.__dict__)
 
 
-def validate_list(list_var, elt_type=str, is_file=False):
+T = TypeVar("T")
+
+
+def validate_list(
+    list_var: T | list[T] | object, elt_type: type = str, is_file: bool = False
+) -> list[T]:
     """
     Validate if contents of a list are of specific type.
 
     Parameters
     ----------
-    list_var : list or str
+    list_var :
         List or single string which contents will be validated.
     elt_type : datatype, optional
         Datatype of which the list elements must be type of. Otherwise
@@ -154,7 +178,7 @@ def validate_list(list_var, elt_type=str, is_file=False):
                 )
             if is_file:
                 # Check if directory and files exist
-                if not os.path.exists(elt):
+                if not os.path.exists(elt):  # type: ignore
                     raise FileNotFoundError(
                         "The specified directory" f"/file {elt} does not exist."
                     )
diff --git a/plotid/publish.py b/plotid/publish.py
index 8912a2a4100be4b14a79c680c3cbc084580e6810..1c84f999b664bf98424f4717f022a9449fd6b7d6 100644
--- a/plotid/publish.py
+++ b/plotid/publish.py
@@ -6,16 +6,13 @@ Publish a tagged plot along the corresponding research data.
 Export the tagged figure to a directory that also contains the raw data where
 the plot is based on. Additionally, the script that produced the plot will be
 copied to the destination directory.
-
-Functions:
-    publish(PlotIDTransfer object, str or list of str, str or list of str)
-    -> None
 """
 
 import os
 import shutil
 import sys
 import warnings
+from typing import TypedDict, Any
 from plotid.save_plot import save_plot
 from plotid.plotoptions import PlotIDTransfer, validate_list
 
@@ -33,7 +30,13 @@ class PublishOptions:
         Export the plot and copy specified files to the destiantion folder.
     """
 
-    def __init__(self, figs_and_ids, src_datapaths, dst_path, **kwargs):
+    def __init__(
+        self,
+        figs_and_ids: PlotIDTransfer,
+        src_datapaths: str | list[str],
+        dst_path: str,
+        **kwargs: Any,
+    ) -> None:
 
         if not isinstance(figs_and_ids, PlotIDTransfer):
             raise RuntimeError("figs_and_ids is not an instance of PlotIDTransfer.")
@@ -45,11 +48,11 @@ class PublishOptions:
         self.dst_path_head, self.dst_dirname = os.path.split(self.dst_path)
         self.plot_names = kwargs.get("plot_names", self.figure_ids)
 
-    def __str__(self):
+    def __str__(self) -> str:
         """Representation if an object of this class is printed."""
         return str(self.__class__) + ": " + str(self.__dict__)
 
-    def validate_input(self):
+    def validate_input(self) -> None:
         """
         Validate if input for PublishOptions is correct type.
 
@@ -74,9 +77,8 @@ class PublishOptions:
 
         if not os.path.isfile(sys.argv[0]):
             raise FileNotFoundError(
-                "Cannot copy original python script. "
-                "Running publish from a shell is not "
-                "possible."
+                "Cannot copy original python script. Running publish from a shell is "
+                "not possible."
             )
 
         # Check if self.src_datapaths are strings and existing files.
@@ -93,7 +95,7 @@ class PublishOptions:
         if not isinstance(self.data_storage, str):
             raise TypeError("The specified data_storage method is not a string.")
 
-    def export(self):
+    def export(self) -> None:
         """
         Export the plot and copy specified files to the destination folder.
 
@@ -173,7 +175,7 @@ class PublishOptions:
             f"\nwere copied to {self.dst_path}."
         )
 
-    def centralized_data_storage(self):
+    def centralized_data_storage(self) -> None:
         """
         [not implemented yet].
 
@@ -186,15 +188,15 @@ class PublishOptions:
         """
         # Does nothing, not implemented yet
 
-    def individual_data_storage(self, destination, pic_path):
+    def individual_data_storage(self, destination: str, pic_path: str) -> None:
         """
         Store all the data in an individual directory.
 
         Parameters
         ----------
-        destination : str
+        destination :
             Directory where the data should be stored.
-        pic_paths : list
+        pic_path :
             Paths to the picture file that will be stored in destination.
 
         Returns
@@ -238,19 +240,33 @@ class PublishOptions:
             )
 
 
-def publish(figs_and_ids, src_datapath, dst_path, **kwargs):
+kwargs_types_publish = TypedDict(
+    "kwargs_types_publish",
+    {
+        "data_storage": str,
+        "plot_names": str | list[str],
+    },
+)
+
+
+def publish(
+    figs_and_ids: PlotIDTransfer,
+    src_datapath: str | list[str],
+    dst_path: str,
+    **kwargs: kwargs_types_publish,
+) -> None:
     """
     Save plot, data and measuring script.
 
     Parameters
     ----------
-    figs_and_ids : PlotIDTransfer object
+    figs_and_ids :
         Contains figures tagged by tagplot() and their corresponding IDs.
-    src_datapath : str or list of str
+    src_datapath :
         Path to data that should be published.
-    dst_path : str
+    dst_path :
         Path to the destination directory.
-    **kwargs : dict, optional
+    **kwargs :
         Extra arguments for additional publish options.
 
     Other Parameters
diff --git a/plotid/save_plot.py b/plotid/save_plot.py
index d587156baad5c5dd6983514fcd659e3a7dce954b..3b9c8f6c3a7af351c1d3c5b69a927e49ef8401da 100644
--- a/plotid/save_plot.py
+++ b/plotid/save_plot.py
@@ -10,9 +10,12 @@ Functions:
 import warnings
 import matplotlib
 import matplotlib.pyplot as plt
+from PIL.Image import Image
 
 
-def save_plot(figures, plot_names, extension="png"):
+def save_plot(
+    figures: plt.Figure | Image, plot_names: str | list[str], extension: str = "png"
+) -> list[str]:
     """
     Export plot(s).
 
@@ -48,7 +51,7 @@ def save_plot(figures, plot_names, extension="png"):
             " will be taken for all plots with an appended number."
         )
         first_name = plot_names[0]
-        plot_names = [None] * len(figures)
+        plot_names = [""] * len(figures)
         for i, _ in enumerate(plot_names):
             plot_names[i] = first_name + f"{i+1}"
     elif len(plot_names) > len(figures):
diff --git a/plotid/tagplot.py b/plotid/tagplot.py
index da83b3ec1756727ffc14642ff0a464cf08d85f9e..25373320b4c6ed4228b543885b2b9e5164f1d480 100644
--- a/plotid/tagplot.py
+++ b/plotid/tagplot.py
@@ -11,12 +11,21 @@ Functions:
 """
 
 import warnings
-from plotid.plotoptions import PlotOptions
+from typing import Any, Literal
+import matplotlib.pyplot as plt
+from PIL.Image import Image
+
+from plotid.plotoptions import PlotOptions, PlotIDTransfer
 from plotid.tagplot_matplotlib import tagplot_matplotlib
 from plotid.tagplot_image import tagplot_image
 
 
-def tagplot(figs, engine, location="east", **kwargs):
+def tagplot(
+    figs: plt.Figure | Image | list[plt.Figure | Image],
+    engine: Literal["matplotlib", "image"],
+    location: str = "east",
+    **kwargs: Any,
+) -> PlotIDTransfer:
     """
     Tag your figure/plot with an ID.
 
@@ -25,9 +34,9 @@ def tagplot(figs, engine, location="east", **kwargs):
 
     Parameters
     ----------
-    figs : list
+    figs :
         Figures that should be tagged.
-    engine : str
+    engine :
         Plot engine which should be used to tag the plot.
     location : str, optional
         Location for ID to be displayed on the plot. Default is 'east'.
@@ -42,7 +51,7 @@ def tagplot(figs, engine, location="east", **kwargs):
         id_method for creating the ID. Create an ID by Unix time is referenced
         as 'time', create a random ID with id_method='random'.
         The default is 'time'.
-    qcode : boolean, optional
+    qrcode : boolean, optional
         Experimental support for encoding the ID in a QR Code.
 
     Raises
diff --git a/plotid/tagplot_image.py b/plotid/tagplot_image.py
index ccb65a24a92aa86e12b3105777bfa3abd22ead6a..ca1b869ceddac31031abc795a6442361877b5122 100644
--- a/plotid/tagplot_image.py
+++ b/plotid/tagplot_image.py
@@ -11,7 +11,7 @@ from plotid.create_id import create_id, create_qrcode
 from plotid.plotoptions import PlotOptions, PlotIDTransfer
 
 
-def tagplot_image(plotid_object):
+def tagplot_image(plotid_object: PlotOptions) -> PlotIDTransfer:
     """
     Add IDs to images/pictures with pillow.
 
@@ -29,7 +29,7 @@ def tagplot_image(plotid_object):
     # Check if plotid_object is a valid instance of PlotOptions
     if not isinstance(plotid_object, PlotOptions):
         raise TypeError(
-            "The given options container is not an instance" "of PlotOptions."
+            "The given options container is not an instance of PlotOptions."
         )
 
     # Check if figs is a list of files
diff --git a/plotid/tagplot_matplotlib.py b/plotid/tagplot_matplotlib.py
index 1130756b8441379eb63c4088bce9dee62b432680..8d83baf82cc44fc2f9495060b99a79002a77a2bc 100644
--- a/plotid/tagplot_matplotlib.py
+++ b/plotid/tagplot_matplotlib.py
@@ -1,10 +1,5 @@
 # -*- coding: utf-8 -*-
-"""
-Tag your matplotlib plot with an ID.
-
-Functions:
-    tagplot_matplotlib(PlotOptions instance) -> PlotIDTransfer instance
-"""
+"""Tag your matplotlib plot with an ID."""
 
 import matplotlib
 import matplotlib.pyplot as plt
@@ -13,7 +8,7 @@ from plotid.create_id import create_id, create_qrcode
 from plotid.plotoptions import PlotOptions, PlotIDTransfer
 
 
-def tagplot_matplotlib(plotid_object):
+def tagplot_matplotlib(plotid_object: PlotOptions) -> PlotIDTransfer:
     """
     Add IDs to figures with matplotlib.
 
@@ -22,7 +17,8 @@ def tagplot_matplotlib(plotid_object):
 
     Parameters
     ----------
-    plotid_object : instance of PlotOptions
+    plotid_object :
+        Object containing the figures and all additional options.
 
     Returns
     -------
@@ -31,7 +27,7 @@ def tagplot_matplotlib(plotid_object):
     # Check if plotid_object is a valid instance of PlotOptions
     if not isinstance(plotid_object, PlotOptions):
         raise TypeError(
-            "The given options container is not an instance" "of PlotOptions."
+            "The given options container is not an instance of PlotOptions."
         )
 
     # Check if figs is a list of valid figures
@@ -44,7 +40,7 @@ def tagplot_matplotlib(plotid_object):
 
     # Loop to create and position the IDs
     for fig in plotid_object.figs:
-        fig_id = create_id(plotid_object.id_method)
+        fig_id: str = create_id(plotid_object.id_method)
         fig_id = plotid_object.prefix + fig_id
         plotid_object.figure_ids.append(fig_id)
         plt.figure(fig)
diff --git a/requirements.txt b/requirements.txt
index 06dc0e651939436d4983c5e9851f673cb61c9dce..c803da6b677eff2762dc89dd4722bb2a10aa8404 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ matplotlib==3.5.2
 myst-parser==0.18.0
 numpy==1.22.3
 packaging==21.3
-Pillow==9.1.1
+Pillow==9.3.0
 pyparsing==3.0.8
 python-dateutil==2.8.2
 qrcode==7.3.1
diff --git a/tests/test_create_id.py b/tests/test_create_id.py
index c494a265fc9bd5fb15804ec67aef1f545114d80c..a3ee0c400b391e35b73db7d390b5a158b82c3129 100644
--- a/tests/test_create_id.py
+++ b/tests/test_create_id.py
@@ -15,24 +15,24 @@ class TestCreateID(unittest.TestCase):
     Class for all unittests of the create_id module.
     """
 
-    def test_existence(self):
+    def test_existence(self) -> None:
         """Test if create_id returns a string."""
         self.assertIsInstance(cid.create_id("time"), str)
         self.assertIsInstance(cid.create_id("random"), str)
 
-    def test_errors(self):
+    def test_errors(self) -> None:
         """Test if Errors are raised when id_method is wrong."""
         with self.assertRaises(ValueError):
             cid.create_id(3)
         with self.assertRaises(ValueError):
             cid.create_id("h")
 
-    def test_length(self):
+    def test_length(self) -> None:
         """Test if figure_id has the correct length."""
         self.assertEqual(len(cid.create_id("time")), 10)
         self.assertEqual(len(cid.create_id("random")), 8)
 
-    def test_qrcode(self):
+    def test_qrcode(self) -> None:
         """Test if qrcode returns a image."""
         self.assertIsInstance(create_qrcode("test_ID"), qrcode.image.pil.PilImage)
 
diff --git a/tests/test_plotoptions.py b/tests/test_plotoptions.py
index bd992c139cdfd34258728bf97246ce02392f8371..b9ba6504d18b8875fd8c4278190957d6e165e997 100644
--- a/tests/test_plotoptions.py
+++ b/tests/test_plotoptions.py
@@ -16,7 +16,7 @@ class TestTagplot(unittest.TestCase):
     Class for all unittests of the plotoptions module.
     """
 
-    def test_validate_input(self):
+    def test_validate_input(self) -> None:
         """
         Test if input validation runs successful.
         """
@@ -24,17 +24,17 @@ class TestTagplot(unittest.TestCase):
             "FIG", ROTATION, POSITION, prefix="xyz", id_method="random"
         ).validate_input()
 
-    def test_prefix(self):
+    def test_prefix(self) -> None:
         """Test if Error is raised if prefix is not a string."""
         with self.assertRaises(TypeError):
             PlotOptions(["FIG"], ROTATION, POSITION, prefix=3).validate_input()
 
-    def test_data_storage(self):
+    def test_data_storage(self) -> None:
         """Test if Error is raised if id_method is not a string."""
         with self.assertRaises(TypeError):
             PlotOptions(["FIG"], ROTATION, POSITION, id_method=4).validate_input()
 
-    def test_str_plotoptions(self):
+    def test_str_plotoptions(self) -> None:
         """
         Test if the string representation of a PlotOptions object is correct.
         """
@@ -54,7 +54,7 @@ class TestTagplot(unittest.TestCase):
             "'random', 'qrcode': False, 'id_on_plot': False}",
         )
 
-    def test_str_plotidtransfer(self):
+    def test_str_plotidtransfer(self) -> None:
         """
         Test if the string representation of a PlotIDTransfer object is
         correct.
diff --git a/tests/test_publish.py b/tests/test_publish.py
index f279f7f0b9232689dc9599d277c94c6fc7c7ea04..9106236f489c55e0ddc5f8adbe27765a8db7826e 100644
--- a/tests/test_publish.py
+++ b/tests/test_publish.py
@@ -35,7 +35,7 @@ class TestPublish(unittest.TestCase):
     Class for all unittests of the publish module.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """Generate source and destination directory and source files."""
         os.makedirs(SRC_DIR, exist_ok=True)
         os.makedirs(DST_PARENT_DIR, exist_ok=True)
@@ -45,11 +45,10 @@ class TestPublish(unittest.TestCase):
     # Skip test if tests are run from command line.
     @unittest.skipIf(
         not os.path.isfile(sys.argv[0]),
-        "Publish is not called "
-        "from a Python script. Therefore, the script cannot be "
+        "Publish is not called from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_publish(self):
+    def test_publish(self) -> None:
         """
         Test publish and check if an exported picture file exists.
         The destination path is given with trailing slash.
@@ -70,7 +69,7 @@ class TestPublish(unittest.TestCase):
         "from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_publish_multiple_figs_custom_name(self):
+    def test_publish_multiple_figs_custom_name(self) -> None:
         """
         Test publish with multiple figures and check if all exported picture
         files exist with the provided strings as their names.
@@ -86,7 +85,7 @@ class TestPublish(unittest.TestCase):
         "from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_publish_multiple_figs_standard_name(self):
+    def test_publish_multiple_figs_standard_name(self) -> None:
         """
         Test publish with multiple figures and check if all exported picture
         files exist with the IDs as their names.
@@ -97,7 +96,7 @@ class TestPublish(unittest.TestCase):
                 os.path.join(DST_PATH, identifier, identifier + ".png")
             )
 
-    def test_figs_and_ids(self):
+    def test_figs_and_ids(self) -> None:
         """
         Test if RuntimeError is raised when figs_and_ids is not an
         PlotIDTransfer object.
@@ -105,14 +104,20 @@ class TestPublish(unittest.TestCase):
         with self.assertRaises(RuntimeError):
             publish("FIGS_AND_IDS", SRC_DIR, DST_PATH)
 
-    def test_wrong_ids(self):
+    def test_wrong_ids(self) -> None:
         """Test if Error is raised if IDs are of wrong type."""
         with self.assertRaises(TypeError):
             publish(PlotIDTransfer(FIG, 3), SRC_DIR, DST_PATH)
         with self.assertRaises(TypeError):
             publish(PlotIDTransfer(FIG, ["i", 4]), SRC_DIR, DST_PATH)
 
-    def test_publish_multiple_src_files(self):
+    # Skip test if tests are run from command line.
+    @unittest.skipIf(
+        not os.path.isfile(sys.argv[0]),
+        "Publish is not called from a Python script. Therefore, the script cannot be "
+        "copied.",
+    )
+    def test_publish_multiple_src_files(self) -> None:
         """
         Test publish with multiple source files and check
         if the exported data files exist.
@@ -132,19 +137,25 @@ class TestPublish(unittest.TestCase):
                 assert os.path.isdir(path)
                 assert os.path.isfile(os.path.join(path, file))
 
-    def test_src_directory(self):
+    def test_src_directory(self) -> None:
         """Test if Error is raised when source directory does not exist."""
         with self.assertRaises(FileNotFoundError):
             publish(FIGS_AND_IDS, "not_existing_folder", DST_PATH)
 
-    def test_src_directory_type(self):
+    # Skip test if tests are run from command line.
+    @unittest.skipIf(
+        not os.path.isfile(sys.argv[0]),
+        "Publish is not called from a Python script. Therefore, the script cannot be "
+        "copied.",
+    )
+    def test_src_directory_type(self) -> None:
         """Test if Error is raised when source directory is not a string."""
         with self.assertRaises(TypeError):
             publish(FIGS_AND_IDS, [SRC_DIR, 4], DST_PATH)
         with self.assertRaises(TypeError):
             publish(FIGS_AND_IDS, 4, DST_PATH)
 
-    def test_dst_directory(self):
+    def test_dst_directory(self) -> None:
         """
         Test if Error is raised when the directory above the
         destination dir does not exist.
@@ -152,7 +163,7 @@ class TestPublish(unittest.TestCase):
         with self.assertRaises(FileNotFoundError):
             publish(FIGS_AND_IDS, SRC_DIR, "not_existing_folder/data")
 
-    def test_script_exists(self):
+    def test_script_exists(self) -> None:
         """
         Test if Error is raised when publish is called from a shell.
 
@@ -206,7 +217,7 @@ class TestPublish(unittest.TestCase):
         "from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_dst_already_exists_yes(self):
+    def test_dst_already_exists_yes(self) -> None:
         """
         Test if publish succeeds if the user wants to overwrite an existing
         destination directory.
@@ -224,7 +235,7 @@ class TestPublish(unittest.TestCase):
         "from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_dst_already_exists_no(self):
+    def test_dst_already_exists_no(self) -> None:
         """
         Test if publish exits with error if the user does not want to overwrite
         an existing destination directory by user input 'no'.
@@ -244,7 +255,7 @@ class TestPublish(unittest.TestCase):
         "from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_dst_already_exists_empty(self):
+    def test_dst_already_exists_empty(self) -> None:
         """
         Test if publish exits with error if the user does not want to overwrite
         an existing destination directory by missing user input.
@@ -260,11 +271,10 @@ class TestPublish(unittest.TestCase):
     # Skip test if tests are run from command line.
     @unittest.skipIf(
         not os.path.isfile(sys.argv[0]),
-        "Publish is not called "
-        "from a Python script. Therefore, the script cannot be "
+        "Publish is not called from a Python script. Therefore, the script cannot be "
         "copied.",
     )
-    def test_dst_invisible_already_exists(self):
+    def test_dst_invisible_already_exists(self) -> None:
         """
         Test if after a crash of publish the partially copied data is
         removed.
@@ -281,16 +291,28 @@ class TestPublish(unittest.TestCase):
         os.remove("test_picture1.tmp.png")
         os.remove("test_picture2.tmp.png")
 
-    def test_plot_names(self):
+    def test_plot_names(self) -> None:
         """Test if Error is raised if plot_name is not a string."""
         with self.assertRaises(TypeError):
-            publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, 7.6, data_storage="individual")
+            publish(
+                FIGS_AND_IDS,
+                SRC_DIR,
+                DST_PATH,
+                plot_names=7.6,
+                data_storage="individual",
+            )
         with self.assertRaises(TypeError):
-            publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, ())
+            publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, plot_names=())
         with self.assertRaises(TypeError):
-            publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, ["test", 3])
+            publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, plot_names=["test", 3])
 
-    def test_data_storage(self):
+    # Skip test if tests are run from command line.
+    @unittest.skipIf(
+        not os.path.isfile(sys.argv[0]),
+        "Publish is not called from a Python script. Therefore, the script cannot be "
+        "copied.",
+    )
+    def test_data_storage(self) -> None:
         """
         Test if Error is raised when unsupported storage method was chosen.
         """
@@ -303,7 +325,7 @@ class TestPublish(unittest.TestCase):
         with self.assertRaises(TypeError):
             publish(FIGS_AND_IDS, SRC_DIR, DST_PATH, data_storage=[])
 
-    def test_str(self):
+    def test_str(self) -> None:
         """
         Test if the string representation of a PublishOptions object is
         correct.
@@ -319,7 +341,7 @@ class TestPublish(unittest.TestCase):
             str(publish_obj),
         )
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """Delete all files created in setUp."""
         shutil.rmtree(SRC_DIR)
         shutil.rmtree(DST_PARENT_DIR)
diff --git a/tests/test_save_plot.py b/tests/test_save_plot.py
index 33e54d83e4eef303cc92566fec18308a85e9910f..4959d223a951371621db12c5de2347557cd3be18 100644
--- a/tests/test_save_plot.py
+++ b/tests/test_save_plot.py
@@ -21,11 +21,11 @@ class TestSavePlot(unittest.TestCase):
     Class for all unittests of the save_plot module.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         plt.savefig(IMG1)
         plt.savefig(IMG2)
 
-    def test_save_plot_matplotlib(self):
+    def test_save_plot_matplotlib(self) -> None:
         """
         Test if save_plot succeeds with valid arguments
         using the matplotlib engine.
@@ -34,7 +34,7 @@ class TestSavePlot(unittest.TestCase):
         self.assertIsInstance(plot_paths, list)
         os.remove(PLOT_NAME + ".tmp.jpg")
 
-    def test_save_plot_image_png(self):
+    def test_save_plot_image_png(self) -> None:
         """
         Test if save_plot succeeds with valid arguments using the image engine
         and a png file.
@@ -44,7 +44,7 @@ class TestSavePlot(unittest.TestCase):
         self.assertIsInstance(plot_paths, list)
         os.remove(PLOT_NAME + ".tmp.png")
 
-    def test_save_plot_image_jpg(self):
+    def test_save_plot_image_jpg(self) -> None:
         """
         Test if save_plot succeeds with valid arguments using the image engine
         and a list of jpg files.
@@ -56,7 +56,7 @@ class TestSavePlot(unittest.TestCase):
         os.remove(PLOT_NAME + "1.tmp.jpg")
         os.remove(PLOT_NAME + "2.tmp.jpg")
 
-    def test_more_figs_than_names(self):
+    def test_more_figs_than_names(self) -> None:
         """
         Test if a warning is raised if more figures than plot names are given.
         Additionally, check if files are named correctly.
@@ -67,17 +67,17 @@ class TestSavePlot(unittest.TestCase):
             assert os.path.isfile(PLOT_NAME + f"{i}.tmp.png")
             os.remove(PLOT_NAME + f"{i}.tmp.png")
 
-    def test_more_names_than_figs(self):
+    def test_more_names_than_figs(self) -> None:
         """Test if  Error is raised if more names than figures are given."""
         with self.assertRaises(IndexError):
             save_plot([FIGURE, FIGURE], [PLOT_NAME, PLOT_NAME, PLOT_NAME])
 
-    def test_wrong_fig_type(self):
+    def test_wrong_fig_type(self) -> None:
         """Test if Error is raised when not a figure object is given."""
         with self.assertRaises(TypeError):
             save_plot("figure", "PLOT_NAME", extension="jpg")
 
-    def test_wrong_fig_in_list(self):
+    def test_wrong_fig_in_list(self) -> None:
         """
         Test if Error is raised when one figure is invalid in a list of
         valid figures.
@@ -86,7 +86,7 @@ class TestSavePlot(unittest.TestCase):
             save_plot([FIGURE, "figure", FIGURE], "PLOT_NAME", extension="jpg")
         os.remove("PLOT_NAME1.tmp.jpg")
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         os.remove(IMG1)
         os.remove(IMG2)
 
diff --git a/tests/test_tagplot.py b/tests/test_tagplot.py
index 3bcccc344b54fadef4234e0a91fbcbb4ab539eaf..5754f2b72332adfbd24bd29b10a0144103e77657 100644
--- a/tests/test_tagplot.py
+++ b/tests/test_tagplot.py
@@ -27,18 +27,18 @@ class TestTagplot(unittest.TestCase):
     Class for all unittests of the tagplot module.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         plt.savefig(IMG1)
         plt.savefig(IMG2)
 
-    def test_tagplot(self):
+    def test_tagplot(self) -> None:
         """
         Test if tagplot runs successful.
         """
         tagplot(FIGS_AS_LIST, PLOT_ENGINE, prefix=PROJECT_ID, id_method=METHOD)
         tagplot(IMGS_AS_LIST, "image", location="north")
 
-    def test_prefix(self):
+    def test_prefix(self) -> None:
         """Test if Error is raised if prefix is not a string."""
         with self.assertRaises(TypeError):
             tagplot(
@@ -49,7 +49,7 @@ class TestTagplot(unittest.TestCase):
                 id_method=METHOD,
             )
 
-    def test_plotengine(self):
+    def test_plotengine(self) -> None:
         """
         Test if Errors are raised if the provided plot engine is not supported.
         """
@@ -58,7 +58,7 @@ class TestTagplot(unittest.TestCase):
         with self.assertRaises(ValueError):
             tagplot(FIGS_AS_LIST, "xyz", location="south")
 
-    def test_idmethod(self):
+    def test_idmethod(self) -> None:
         """
         Test if Errors are raised if the id_method is not an string.
         """
@@ -69,7 +69,7 @@ class TestTagplot(unittest.TestCase):
         with self.assertRaises(TypeError):
             tagplot(FIGS_AS_LIST, PLOT_ENGINE, id_method=[0, 1])
 
-    def test_location(self):
+    def test_location(self) -> None:
         """
         Test if Errors are raised if the provided location is not supported.
         """
@@ -78,7 +78,7 @@ class TestTagplot(unittest.TestCase):
         with self.assertWarns(Warning):
             tagplot(FIGS_AS_LIST, PLOT_ENGINE, location="up")
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         os.remove(IMG1)
         os.remove(IMG2)
 
diff --git a/tests/test_tagplot_image.py b/tests/test_tagplot_image.py
index a14b4b9ea545812b04fce5900a8039423db302dd..1dea02dc55d5e0a3adde55f1b1fae25bfaa598c6 100644
--- a/tests/test_tagplot_image.py
+++ b/tests/test_tagplot_image.py
@@ -30,11 +30,11 @@ class TestTagplotImage(unittest.TestCase):
     Class for all unittests of the tagplot_image module.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         plt.savefig(IMG1)
         plt.savefig(IMG2)
 
-    def test_images(self):
+    def test_images(self) -> None:
         """
         Test of returned objects. Check if they are png and jpg files,
         respectively.
@@ -52,7 +52,7 @@ class TestTagplotImage(unittest.TestCase):
         self.assertIsInstance(figs_and_ids.figs[0], PngImagePlugin.PngImageFile)
         self.assertIsInstance(figs_and_ids.figs[1], JpegImagePlugin.JpegImageFile)
 
-    def test_single_image(self):
+    def test_single_image(self) -> None:
         """
         Test of returned objects. Check if png files are returned,
         if a single matplot figure is given (not as a list).
@@ -62,7 +62,7 @@ class TestTagplotImage(unittest.TestCase):
         figs_and_ids = tagplot_image(options)
         self.assertIsInstance(figs_and_ids.figs[0], PngImagePlugin.PngImageFile)
 
-    def test_image_not_str(self):
+    def test_image_not_str(self) -> None:
         """Test if Error is raised if wrong type of image is given."""
         options = PlotOptions(
             3, ROTATION, POSITION, prefix=PROJECT_ID, id_method=METHOD
@@ -71,21 +71,21 @@ class TestTagplotImage(unittest.TestCase):
         with self.assertRaises(TypeError):
             tagplot_image(options)
 
-    def test_image_not_file(self):
+    def test_image_not_file(self) -> None:
         """Test if Error is raised if the image file does not exist."""
         options = PlotOptions("xyz", ROTATION, POSITION)
         options.validate_input()
         with self.assertRaises(TypeError):
             tagplot_image(options)
 
-    def test_image_plotoptions(self):
+    def test_image_plotoptions(self) -> None:
         """
         Test if Error is raised if not an instance of PlotOptions is passed.
         """
         with self.assertRaises(TypeError):
             tagplot_image("wrong_object")
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         os.remove(IMG1)
         os.remove(IMG2)
 
diff --git a/tests/test_tagplot_matplotlib.py b/tests/test_tagplot_matplotlib.py
index 3bda534dfec8e16646b5ee06dfb30586a75c77ac..2142698b585d91ea194fe68ed6b53f614c04f865 100644
--- a/tests/test_tagplot_matplotlib.py
+++ b/tests/test_tagplot_matplotlib.py
@@ -27,7 +27,7 @@ class TestTagplotMatplotlib(unittest.TestCase):
     Class for all unittests of the tagplot_matplotlib module.
     """
 
-    def test_mplfigures(self):
+    def test_mplfigures(self) -> None:
         """Test of returned objects. Check if they are matplotlib figures."""
         options = PlotOptions(
             FIGS_AS_LIST,
@@ -42,7 +42,7 @@ class TestTagplotMatplotlib(unittest.TestCase):
         self.assertIsInstance(figs_and_ids.figs[0], Figure)
         self.assertIsInstance(figs_and_ids.figs[1], Figure)
 
-    def test_single_mplfigure(self):
+    def test_single_mplfigure(self) -> None:
         """
         Test of returned objects. Check if matplotlib figures are returned,
         if a single matplot figure is given (not as a list).
@@ -52,14 +52,14 @@ class TestTagplotMatplotlib(unittest.TestCase):
         figs_and_ids = tagplot_matplotlib(options)
         self.assertIsInstance(figs_and_ids.figs[0], Figure)
 
-    def test_mplerror(self):
+    def test_mplerror(self) -> None:
         """Test if Error is raised if wrong type of figures is given."""
         options = PlotOptions(3, ROTATION, POSITION)
         options.validate_input()
         with self.assertRaises(TypeError):
             tagplot_matplotlib(options)
 
-    def test_mpl_plotoptions(self):
+    def test_mpl_plotoptions(self) -> None:
         """
         Test if Error is raised if not an instance of PlotOptions is passed.
         """