diff --git a/changelog/794.feature.rst b/changelog/794.feature.rst
new file mode 100644
index 000000000..8b35ab7cf
--- /dev/null
+++ b/changelog/794.feature.rst
@@ -0,0 +1 @@
+Allows addition of an ``NDCube`` and ``NDData`` (with the WCS of ``NDData`` being set to None), and combines their uncertainties and masks.
diff --git a/ndcube/conftest.py b/ndcube/conftest.py
index e52ad7643..bfef8d6a8 100644
--- a/ndcube/conftest.py
+++ b/ndcube/conftest.py
@@ -678,6 +678,39 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln):
     return NDCube(data_cube, wcs=wcs_2d_lt_ln, unit=u.ct)
 
 
+@pytest.fixture
+def ndcube_2d_ln_lt_no_unit_no_unc(wcs_2d_lt_ln):
+    shape = (10, 12)
+    data_cube = data_nd(shape).astype(float)
+    return NDCube(data_cube, wcs=wcs_2d_lt_ln)
+
+
+@pytest.fixture
+def ndcube_2d_unit_unc(wcs_2d_lt_ln):
+    shape = (10, 12)
+    data_cube = data_nd(shape).astype(float)
+    uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=u.ct)
+
+    return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct)
+
+
+@pytest.fixture
+def ndcube_2d_uncertainty_no_unit(wcs_2d_lt_ln):
+    shape = (10, 12)
+    data_cube = data_nd(shape).astype(float)
+    uncertainty = StdDevUncertainty(np.ones(shape)*0.2)
+
+    return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty)
+
+
+@pytest.fixture
+def ndcube_2d_ln_lt_mask(wcs_2d_lt_ln):
+    shape = (10, 12)
+    data_cube = data_nd(shape).astype(float)
+    mask = np.ones(data_cube.shape, dtype=bool)
+    return NDCube(data_cube, wcs=wcs_2d_lt_ln, mask=mask)
+
+
 @pytest.fixture
 def ndcube_2d_dask(wcs_2d_lt_ln):
     shape = (8, 4)
@@ -719,6 +752,9 @@ def ndcube_1d_l(wcs_1d_l):
     "ndcube_2d_ln_lt_units",
     "ndcube_2d_dask",
     "ndcube_1d_l",
+    "ndcube_2d_ln_lt_no_unit_no_unc",
+    "ndcube_2d_uncertainty_no_unit",
+    "ndcube_2d_unit_unc",
 ])
 def all_ndcubes(request):
     """
diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py
index 5f5f4ee40..cf397e267 100644
--- a/ndcube/ndcube.py
+++ b/ndcube/ndcube.py
@@ -11,6 +11,7 @@
 
 import astropy.nddata
 import astropy.units as u
+from astropy.nddata import NDData
 from astropy.units import UnitsError
 from astropy.wcs.utils import _split_matrix
 
@@ -964,15 +965,87 @@ def _new_instance(self, **kwargs):
     def __neg__(self):
         return self._new_instance(data=-self.data)
 
-    def __add__(self, value):
-        if hasattr(value, 'unit'):
+    def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and):
+        """
+        Users are allowed to choose whether they want operation_ignores_mask to be True or False,
+        and are allowed to choose whether they want handle_mask to be AND / OR .
+        """
+        kwargs = {}
+
+        if isinstance(value, NDData) and value.wcs is None:
+            if self.unit is not None and value.unit is not None:
+                value_data = (value.data * value.unit).to_value(self.unit)
+            elif self.unit is None and value.unit is None:
+                value_data = value.data
+            else:
+                raise TypeError("Adding objects requires both have a unit or neither has a unit.") # change the test as well.
+
+            # check whether there is a mask.
+            # Neither self nor value has a mask
+            self_unmasked = self.mask is None or self.mask is False or not self.mask.any()
+            value_unmasked = value.mask is None or value.mask is False or not value.mask.any()
+
+            if (self_unmasked and value_unmasked) or operation_ignores_mask is True:
+                # addition
+                kwargs["data"] = self.data + value_data
+
+                # combine the uncertainty;
+                if self.uncertainty is not None and value.uncertainty is not None:
+                    result_data = kwargs["data"]
+                    if self.unit is not None:
+                        result_data *= self.unit
+                    new_uncertainty = self.uncertainty.propagate(
+                        np.add, value, result_data=result_data, correlation=0
+                    )
+                    kwargs["uncertainty"] = new_uncertainty
+                elif self.uncertainty is not None:
+                    new_uncertainty = self.uncertainty
+                    kwargs["uncertainty"] = new_uncertainty
+                elif value.uncertainty is not None:
+                    new_uncertainty = value.uncertainty
+                    kwargs["uncertainty"] = new_uncertainty
+                else:
+                    new_uncertainty = None
+            else:
+                # TODO
+                # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture.
+                # Conditional statements to permutate the two different scenarios (when it does not ignore the mask).
+                kwargs["data"] = self.data + value_data
+                self_data, value_data = self.data, value.data # May require a copy
+                self_mask, value_mask = self.mask, value.mask # May require handling/converting of cases when masks aren't boolean arrays but are None, True, or False.
+                if not operation_ignores_mask:
+                    no_op_value = 0 # Value to set masked values since we are doing addition. (Would need to be 1 if we were doing multiplication.)
+                    if (self_mask is True and value_mask is False):
+                        idx = np.logical_and(self_mask, np.logical_not(value_mask))
+                        self_data[idx] = no_op_value
+                    elif (self_mask is False and value_mask is True):
+                        idx = np.logical_and(value_mask, np.logical_not(self_mask))
+                        value_data[idx] = no_op_value
+                    elif (self_mask is True and value_mask is True):
+                        idx = np.logical_and(self_mask, value_mask)
+                        self_data[idx] = no_op_value
+                        value_data[idx] = no_op_value
+
+                    #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here.
+                    # if both are F, no operation of setting the values to be 0 needs to be done.
+                else:
+                    pass # If operation ignores mask, nothing needs to be done. This line not needed in actual code.  Only here for clarity.
+
+                # Perform addition
+                new_data = self_data + value_data
+                # Calculate new mask.
+                new_mask = handle_mask(self_mask, value_mask) if handle_mask else None
+                kwargs["data"] = new_data
+                kwargs["mask"] = new_mask
+
+        elif hasattr(value, 'unit'):
             if isinstance(value, u.Quantity):
                 # NOTE: if the cube does not have units, we cannot
                 # perform arithmetic between a unitful quantity.
                 # This forces a conversion to a dimensionless quantity
                 # so that an error is thrown if value is not dimensionless
                 cube_unit = u.Unit('') if self.unit is None else self.unit
-                new_data = self.data + value.to_value(cube_unit)
+                kwargs["data"] = self.data + value.to_value(cube_unit)
             else:
                 # NOTE: This explicitly excludes other NDCube objects and NDData objects
                 # which could carry a different WCS than the NDCube
@@ -980,8 +1053,25 @@ def __add__(self, value):
         elif self.unit not in (None, u.Unit("")):
             raise TypeError("Cannot add a unitless object to an NDCube with a unit.")
         else:
-            new_data = self.data + value
-        return self._new_instance(data=new_data)
+            kwargs["data"] = self.data + value
+
+        # return the new NDCube instance
+        return self._new_instance(**kwargs)
+
+    def __add__(self, value):
+        # when value has a mask, raise error and point user to the add method. TODO
+        #
+        # check whether there is a mask.
+        # Neither self nor value has a mask
+
+        self_masked = not(self.mask is None or self.mask is False or not self.mask.any())
+        value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False
+
+        if  (value_masked or (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None)): # value has a mask,
+            # let the users call the add method
+            raise TypeError('Please use the add method.')
+
+        return self.add(value) # the mask keywords cannot be given by users.
 
     def __radd__(self, value):
         return self.__add__(value)
@@ -1330,6 +1420,28 @@ def squeeze(self, axis=None):
         return self[tuple(item)]
 
 
+def fill(fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False):
+    """
+    Replaces masked data values with input value.
+
+    Returns a new instance or alters values in place.
+
+    Parameters
+    ----------
+    fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`
+        The value to replace masked data with.
+    unmask: `bool`, optional
+        If True, the newly filled masked values are unmasked. If False, they remain masked
+        Default=False
+    uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional
+        The value to replace masked uncertainties with.
+    fill_in_place: `bool`, optional
+        If `True`, the masked values are filled in place.  If `False`, a new instance is returned
+        with masked values filled.  Default=False.
+    """
+    # ...code implementation here.
+
+
 def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask):
     m = None if (mask is None or mask is False or operation_ignores_mask) else mask
     if m is None:
diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py
index a9e7d0727..93944921d 100644
--- a/ndcube/tests/helpers.py
+++ b/ndcube/tests/helpers.py
@@ -122,13 +122,26 @@ def assert_metas_equal(test_input, expected_output):
                 assert test_input[key] == expected_output[key]
 
 
-def assert_cubes_equal(test_input, expected_cube, check_data=True):
+def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncertainty_values=False):
     assert isinstance(test_input, type(expected_cube))
     assert np.all(test_input.mask == expected_cube.mask)
     if check_data:
         np.testing.assert_array_equal(test_input.data, expected_cube.data)
     assert_wcs_are_equal(test_input.wcs, expected_cube.wcs)
-    if test_input.uncertainty:
+    if check_uncertainty_values:
+        # Check output and expected uncertainty are of same type. Remember they could be None.
+        # If the uncertainties are not None,...
+        # Check units, shape, and values of the uncertainty.
+        if (test_input.uncertainty is not None and expected_cube.uncertainty is not None):
+            assert type(test_input.uncertainty) is type(expected_cube.uncertainty)
+            assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \
+                f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}"
+        elif test_input.uncertainty is None:
+            assert expected_cube.uncertainty is None, "Test uncertainty should not be None."
+        elif expected_cube.uncertainty is None:
+            assert test_input.uncertainty is None, "Test uncertainty should be None."
+
+    elif test_input.uncertainty:
         assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape
     assert np.all(test_input.shape == expected_cube.shape)
     assert_metas_equal(test_input.meta, expected_cube.meta)
diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py
index 01f2aa0f2..0247609fd 100644
--- a/ndcube/tests/test_ndcube.py
+++ b/ndcube/tests/test_ndcube.py
@@ -12,7 +12,7 @@
 import astropy.wcs
 from astropy.coordinates import SkyCoord, SpectralCoord
 from astropy.io import fits
-from astropy.nddata import UnknownUncertainty
+from astropy.nddata import NDData, StdDevUncertainty, UnknownUncertainty
 from astropy.tests.helper import assert_quantity_allclose
 from astropy.time import Time
 from astropy.units import UnitsError
@@ -23,6 +23,7 @@
 
 from ndcube import ExtraCoords, NDCube, NDMeta
 from ndcube.tests import helpers
+from ndcube.tests.helpers import assert_cubes_equal
 from ndcube.utils.exceptions import NDCubeUserWarning
 
 
@@ -1131,13 +1132,229 @@ def check_arithmetic_value_and_units(cube_new, data_expected):
     u.Quantity(np.random.rand(12), u.ct),
     u.Quantity(np.random.rand(10, 12), u.ct),
 ])
-def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value):
+def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods aims for the special scenario of only integers being added together.
     cube_quantity = u.Quantity(ndcube_2d_ln_lt_units.data, ndcube_2d_ln_lt_units.unit)
     # Add
     new_cube = ndcube_2d_ln_lt_units + value
     check_arithmetic_value_and_units(new_cube, cube_quantity + value)
 
 
+# Only one of them has a unit.
+# An expected typeError should be raised.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)),
+                                                            wcs=None,
+                                                            unit=u.m,
+                                                            uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1))
+                            ),
+                            ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)),
+                                                            wcs=None,
+                                                            uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1))
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_one_unit(ndc, value):
+    assert isinstance(ndc, NDCube)
+    with pytest.raises(TypeError, match="Adding objects requires both have a unit or neither has a unit."):
+        ndc + value
+
+
+# Both NDData and NDCube have unit and uncertainty. No mask is involved.
+# Test different scenarios when units are equivalent and when they are not. TODO (bc somewhere is checking the units are the same)
+# what is an equivalent unit in astropy for count (ct)?
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_unit_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                          wcs=None,
+                                                          unit=u.ct,
+                                                          uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct))
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unit_unc_nddata_unit_unc(ndc, value):
+    output_cube = ndc + value # perform the addition
+    # Construct expected cube
+    expected_unit = u.ct
+    expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit)
+    expected_uncertainty = ndc.uncertainty.propagate(
+                            operation=np.add,
+                            other_nddata=value,
+                            result_data=expected_data*expected_unit,
+                            correlation=0,
+    )
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit)
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True)
+
+# Both have unit, NDCube has no uncertainty and NDData has uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                          wcs=None,
+                                                          unit=u.ct,
+                                                          uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct))
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unit_nddata_unit_unc(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_unit = u.ct
+    expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit)
+    expected_uncertainty = value.uncertainty
+
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit)
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True)
+
+
+# Both have units, NDData has no uncertainty and NDCube has uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_unit_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                          wcs=None,
+                                                          unit=u.ct)
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unit_unc_nddata_unit(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_unit = u.ct
+    expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit)
+    expected_uncertainty = ndc.uncertainty
+
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit)
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True)
+
+
+# Both have units, neither has uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                          wcs=None,
+                                                          unit=u.ct)
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unit_nddata_unit(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_unit = u.ct
+    expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit)
+    expected_cube = NDCube(expected_data, ndc.wcs, unit=expected_unit)
+
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube)
+
+
+# Neither has a unit, both have uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                                      wcs=None,
+                                                                      uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1))
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unc_nddata_unc(ndc, value):
+    output_cube = ndc + value # perform the addition
+    # Construct expected cube
+    expected_data = ndc.data + value.data
+    expected_uncertainty = ndc.uncertainty.propagate(
+                            operation=np.add,
+                            other_nddata=value,
+                            result_data=expected_data,
+                            correlation=0,
+    )
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty)
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True)
+
+
+# Neither has a unit, NDData has uncertainty and NDCube has no uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_ln_lt_no_unit_no_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                                      wcs=None,
+                                                                      uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1))
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_nddata_unc(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_data = ndc.data + value.data
+    expected_uncertainty = value.uncertainty
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty)
+
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube)
+
+
+# Neither has a unit, NDData has no uncertainty and NDCube has uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                                      wcs=None)
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_unc_nddata(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_data = ndc.data + value.data
+    expected_uncertainty = ndc.uncertainty
+    expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty)
+
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube)
+
+
+# Neither has unit or uncertainty.
+@pytest.mark.parametrize(("ndc", "value"),
+                        [
+                            ("ndcube_2d_ln_lt_no_unit_no_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones.
+                                                                      wcs=None)
+                            ),
+                        ],
+                        indirect=("ndc",))
+def test_arithmetic_add_cube_nddata(ndc, value):
+    output_cube = ndc + value # perform the addition
+
+    # Construct expected cube
+    expected_data = ndc.data + value.data
+    expected_cube = NDCube(expected_data, ndc.wcs)
+
+    # Assert output cube is same as expected cube
+    assert_cubes_equal(output_cube, expected_cube)
+
+
+# The case when both NDData and NDCube have uncertainty, unit. Also:
+# NDCube has mask
+# Then, NDData has mask (add one new parametrize).
+@pytest.mark.parametrize('value', [
+    NDData(np.ones((10, 12)),
+           wcs=None,
+           uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)),
+
+    NDData(np.ones((10, 12)) * 2,
+           wcs=None,
+           uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.05),
+           mask=np.ones((10, 12), dtype=bool))
+])
+def test_arithmetic_add_cube_unit_mask_nddata_unc_unit_mask(ndcube_2d_ln_lt_mask, value):
+    with pytest.raises(TypeError, match='Please use the add method.'):
+        ndcube_2d_ln_lt_mask + value
+
+
 @pytest.mark.parametrize('value', [
     10 * u.ct,
     u.Quantity([10], u.ct),