diff --git a/qim3d/io/__init__.py b/qim3d/io/__init__.py
index 7dc89cfca6d71d61fff9207c9c2d28ae7ad406fd..3384ec71aa400a9ff34debdd75d6b0a1c5dc8bb9 100644
--- a/qim3d/io/__init__.py
+++ b/qim3d/io/__init__.py
@@ -1,5 +1,5 @@
 from .downloader import Downloader
 from .load import DataLoader, load, ImgExamples
-from .save import save
+from .save import DataSaver, save 
 from .sync import Sync
 from . import logger
\ No newline at end of file
diff --git a/qim3d/io/save.py b/qim3d/io/save.py
index 5ee21dfe0376ed6b615c164317eaa335f14d7376..ad988b9a73b534f2381bc23f5973312e83e47952 100644
--- a/qim3d/io/save.py
+++ b/qim3d/io/save.py
@@ -1,10 +1,106 @@
+"""Provides functionality for saving data to various file formats."""
+
+import os
+import tifffile
+import numpy as np
+from qim3d.io.logger import log
+
 class DataSaver:
-    def __init__(self):
-        self.verbose = False
+    """Utility class for saving data to different file formats.
+
+    Attributes:
+        replace (bool): Specifies if an existing file with identical path is replaced.
+        compression (bool): Specifies if the file is with Deflate compression.
+
+    Methods:
+        save_tiff(path,data): Save data to a TIFF file to the given path.
+        load(path,data): Save data to the given path. 
+
+    Example:
+        image = qim3d.examples.blobs_256x256
+        saver = qim3d.io.DataSaver(compression=True)
+        saver.save_tiff("image.tif",image)
+    """
+
+    def __init__(self, **kwargs):
+        """Initializes a new instance of the DataSaver class.
+
+        Args:
+            replace (bool, optional): Specifies if an existing file with identical path should be replaced. 
+                Default is False.
+            compression (bool, optional): Specifies if the file should be saved with Deflate compression.
+                Default is False.
+        """
+        self.replace = kwargs.get("replace",False)
+        self.compression = kwargs.get("compression",False)
+
+    def save_tiff(self,path,data):
+        """Save data to a TIFF file to the given path.
+
+        Args: 
+            path (str): The path to save file to
+            data (numpy.ndarray): The data to be saved    
+        """
+        tifffile.imwrite(path,data,compression=self.compression)
 
     def save(self, path, data):
-        raise NotImplementedError("Save is not implemented yet")
+        """Save data to the given path.
+
+        Args: 
+            path (str): The path to save file to
+            data (numpy.ndarray): The data to be saved
+        
+        Raises:
+            ValueError: If the file format is not supported.
+            ValueError: If the specified folder does not exist.
+            ValueError: If a file extension is not provided.
+            ValueError: if a file with the specified path already exists and replace=False.
+
+        Example:
+            image = qim3d.examples.blobs_256x256
+            saver = qim3d.io.DataSaver(compression=True)
+            saver.save("image.tif",image)
+        """
 
+        folder = os.path.dirname(path) or '.'
+        # Check if directory exists
+        if os.path.isdir(folder):
+            _, ext = os.path.splitext(path)
+            # Check if provided path contains file extension
+            if ext:
+                # Check if a file with the given path already exists
+                if os.path.isfile(path) and not self.replace:
+                    raise ValueError("A file with the provided path already exists. To replace it set 'replace=True'")
+                
+                if path.endswith((".tif",".tiff")):
+                    return self.save_tiff(path,data)
+                else:
+                    raise ValueError("Unsupported file format")
+            else:
+                raise ValueError('Please provide a file extension')
+        else:
+            raise ValueError(f'The directory {folder} does not exist. Please provide a valid directory')
+        
+def save(path,
+        data,
+        replace=False,
+        compression=False,
+        **kwargs
+        ):
+    """Save data to a specified file path.
 
-def save(path, data):
-    return DataSaver().save(path, data)
+    Args:
+        path (str): The path to save file to
+        data (numpy.ndarray): The data to be saved  
+        replace (bool, optional): Specifies if an existing file with identical path should be replaced. 
+            Default is False.
+        compression (bool, optional): Specifies if the file should be saved with Deflate compression (lossless).
+            Default is False.
+        **kwargs: Additional keyword arguments to be passed to the DataSaver constructor
+    
+    Example:
+        image = qim3d.examples.blobs_256x256
+        qim3d.io.save("image.tif",image,compression=True)
+    """
+        
+    DataSaver(replace=replace, compression=compression, **kwargs).save(path, data)
\ No newline at end of file
diff --git a/qim3d/tests/io/test_save.py b/qim3d/tests/io/test_save.py
new file mode 100644
index 0000000000000000000000000000000000000000..43e2597f8fe9f8caa26b0b292e5379007075c7c3
--- /dev/null
+++ b/qim3d/tests/io/test_save.py
@@ -0,0 +1,166 @@
+import qim3d
+import tempfile
+import numpy as np
+import os
+import hashlib
+import pytest
+
+def test_image_exist():
+    # Create random test image
+    test_image = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"test_image.tif")
+
+        # Save to temporary directory
+        qim3d.io.save(image_path,test_image)
+
+        # Assert that test image has been saved
+        assert os.path.exists(image_path)
+
+def test_compression():
+    # Get test image (should not be random in order for compression to function)
+    test_image = qim3d.examples.blobs_256x256
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"test_image.tif")
+        compressed_image_path = os.path.join(temp_dir,"compressed_test_image.tif")
+
+        # Save to temporary directory with and without compression
+        qim3d.io.save(image_path,test_image)
+        qim3d.io.save(compressed_image_path,test_image,compression=True)
+
+        # Compute file sizes
+        file_size = os.path.getsize(image_path)
+        compressed_file_size = os.path.getsize(compressed_image_path)
+
+        # Assert that compressed file size is smaller than non-compressed file size
+        assert compressed_file_size < file_size
+
+def test_image_matching():
+    # Create random test image
+    original_image = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"original_image.tif")
+
+        # Save to temporary directory
+        qim3d.io.save(image_path,original_image)
+
+        # Load from temporary directory
+        saved_image = qim3d.io.load(image_path)
+
+        # Get hashes
+        original_hash = calculate_image_hash(original_image)
+        saved_hash = calculate_image_hash(saved_image)
+
+        # Assert that original image is identical to saved_image
+        assert original_hash == saved_hash
+
+def test_compressed_image_matching():
+    # Get test image (should not be random in order for compression to function)
+    original_image = qim3d.examples.blobs_256x256
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"original_image.tif")
+
+        # Save to temporary directory
+        qim3d.io.save(image_path,original_image,compression=True)
+
+        # Load from temporary directory
+        saved_image_compressed = qim3d.io.load(image_path)
+
+        # Get hashes
+        original_hash = calculate_image_hash(original_image)
+        compressed_hash = calculate_image_hash(saved_image_compressed)
+
+        # Assert that original image is identical to saved_image
+        assert original_hash == compressed_hash
+
+def test_file_replace():
+    # Create random test image
+    test_image1 = np.random.randint(0,256,(100,100,100),'uint8')
+    test_image2 = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"test_image.tif")
+
+        # Save first test image to temporary directory
+        qim3d.io.save(image_path,test_image1)
+        # Get hash
+        hash1 = calculate_image_hash(qim3d.io.load(image_path))
+        
+        # Replace existing file
+        qim3d.io.save(image_path,test_image2,replace=True)
+        # Get hash again
+        hash2 = calculate_image_hash(qim3d.io.load(image_path))
+
+        # Assert that the file was modified by checking if the second modification time is newer than the first
+        assert hash1 != hash2
+
+def test_file_already_exists():
+    # Create random test image
+    test_image1 = np.random.randint(0,256,(100,100,100),'uint8')
+    test_image2 = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,"test_image.tif")
+
+        # Save first test image to temporary directory
+        qim3d.io.save(image_path,test_image1)
+
+        with pytest.raises(ValueError,match="A file with the provided path already exists. To replace it set 'replace=True'"):
+            # Try to save another image to the existing path
+            qim3d.io.save(image_path,test_image2)
+
+def test_no_file_ext():
+    # Create random test image
+    test_image = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create filename without extension
+    filename = 'test_image'
+
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,filename)
+
+        with pytest.raises(ValueError,match='Please provide a file extension'):
+            # Try to save the test image to a path witout file extension
+            qim3d.io.save(image_path,test_image)
+
+def test_folder_doesnt_exist():
+    # Create random test image
+    test_image = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create invalid path 
+    invalid_path = os.path.join('this','path','doesnt','exist.tif')
+
+    with pytest.raises(ValueError,match=f'The directory {os.path.dirname(invalid_path)} does not exist. Please provide a valid directory'):
+        # Try to save test image to an invalid path
+        qim3d.io.save(invalid_path,test_image)
+    
+def test_unsupported_file_format():
+    # Create random test image
+    test_image = np.random.randint(0,256,(100,100,100),'uint8')
+
+    # Create filename with unsupported format
+    filename = 'test_image.unsupported'
+    # Create temporary directory
+    with tempfile.TemporaryDirectory() as temp_dir:
+        image_path = os.path.join(temp_dir,filename)    
+
+        with pytest.raises(ValueError,match='Unsupported file format'):
+            # Try to save test image with an unsupported file extension
+            qim3d.io.save(image_path,test_image)
+
+
+def calculate_image_hash(image): 
+    image_bytes = image.tobytes()
+    hash_object = hashlib.md5(image_bytes)
+    return hash_object.hexdigest()
\ No newline at end of file