diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index d93927e177c6724bc26dd8f48d54318753b1118d..192f3aa568fea8c7dc7d5a7ce7e526f2aef304fe 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -11,11 +11,14 @@
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/GmshReader.hpp>
 #include <mesh/IBoundaryDescriptor.hpp>
+#include <mesh/IZoneDescriptor.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/MeshRelaxer.hpp>
 #include <mesh/MeshTransformer.hpp>
 #include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NamedZoneDescriptor.hpp>
 #include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <mesh/NumberedZoneDescriptor.hpp>
 #include <utils/Exceptions.hpp>
 
 #include <Kokkos_Core.hpp>
@@ -44,6 +47,25 @@ MeshModule::MeshModule()
 
                               ));
 
+  this->_addBuiltinFunction("zoneTag",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IZoneDescriptor>(int64_t)>>(
+
+                              [](int64_t zone_tag) -> std::shared_ptr<const IZoneDescriptor> {
+                                return std::make_shared<NumberedZoneDescriptor>(zone_tag);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("zoneName",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IZoneDescriptor>(const std::string&)>>(
+
+                              [](const std::string& zone_name) -> std::shared_ptr<const IZoneDescriptor> {
+                                return std::make_shared<NamedZoneDescriptor>(zone_name);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("boundaryTag",
                             std::make_shared<
                               BuiltinFunctionEmbedder<std::shared_ptr<const IBoundaryDescriptor>(int64_t)>>(
diff --git a/src/language/modules/MeshModule.hpp b/src/language/modules/MeshModule.hpp
index f6ab9c8509b59f264800883e2bbe149d4d060a41..9047412586f24200789ebea289e9b8c4f0fbd27d 100644
--- a/src/language/modules/MeshModule.hpp
+++ b/src/language/modules/MeshModule.hpp
@@ -15,6 +15,11 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary");
 
+class IZoneDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("zone");
+
 class MeshModule : public BuiltinModule
 {
  public:
diff --git a/src/mesh/MeshCellZone.cpp b/src/mesh/MeshCellZone.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..36418e2b510dae6a15d4f30229c5330ff09af2c9
--- /dev/null
+++ b/src/mesh/MeshCellZone.cpp
@@ -0,0 +1,36 @@
+#include <mesh/MeshCellZone.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template <size_t Dimension>
+MeshCellZone<Dimension>::MeshCellZone(const Mesh<Connectivity<Dimension>>&, const RefCellList& ref_cell_list)
+  : m_cell_list(ref_cell_list.list()), m_zone_name(ref_cell_list.refId().tagName())
+{}
+
+template MeshCellZone<2>::MeshCellZone(const Mesh<Connectivity<2>>&, const RefCellList&);
+template MeshCellZone<3>::MeshCellZone(const Mesh<Connectivity<3>>&, const RefCellList&);
+
+template <size_t Dimension>
+MeshCellZone<Dimension>
+getMeshCellZone(const Mesh<Connectivity<Dimension>>& mesh, const IZoneDescriptor& zone_descriptor)
+{
+  for (size_t i_ref_cell_list = 0; i_ref_cell_list < mesh.connectivity().template numberOfRefItemList<ItemType::cell>();
+       ++i_ref_cell_list) {
+    const auto& ref_cell_list = mesh.connectivity().template refItemList<ItemType::cell>(i_ref_cell_list);
+    const RefId& ref          = ref_cell_list.refId();
+    if (ref == zone_descriptor) {
+      return MeshCellZone<Dimension>{mesh, ref_cell_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find zone with name " << rang::fgB::red << zone_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshCellZone<1> getMeshCellZone(const Mesh<Connectivity<1>>&, const IZoneDescriptor&);
+template MeshCellZone<2> getMeshCellZone(const Mesh<Connectivity<2>>&, const IZoneDescriptor&);
+template MeshCellZone<3> getMeshCellZone(const Mesh<Connectivity<3>>&, const IZoneDescriptor&);
diff --git a/src/mesh/MeshCellZone.hpp b/src/mesh/MeshCellZone.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..de4903689b328bddbac3f0bacf363497374f8fe4
--- /dev/null
+++ b/src/mesh/MeshCellZone.hpp
@@ -0,0 +1,49 @@
+#ifndef MESH_CELL_ZONE_HPP
+#define MESH_CELL_ZONE_HPP
+
+#include <mesh/IZoneDescriptor.hpp>
+#include <mesh/RefItemList.hpp>
+#include <utils/Array.hpp>
+
+template <size_t Dimension>
+class Connectivity;
+
+template <typename ConnectivityType>
+class Mesh;
+
+template <size_t Dimension>
+class [[nodiscard]] MeshCellZone   // clazy:exclude=copyable-polymorphic
+{
+ protected:
+  Array<const CellId> m_cell_list;
+  std::string m_zone_name;
+
+ public:
+  template <size_t MeshDimension>
+  friend MeshCellZone<MeshDimension> getMeshCellZone(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                     const IZoneDescriptor& zone_descriptor);
+
+  MeshCellZone& operator=(const MeshCellZone&) = default;
+  MeshCellZone& operator=(MeshCellZone&&) = default;
+
+  const Array<const CellId>& cellList() const
+  {
+    return m_cell_list;
+  }
+
+ protected:
+  MeshCellZone(const Mesh<Connectivity<Dimension>>& mesh, const RefCellList& ref_cell_list);
+
+ public:
+  MeshCellZone(const MeshCellZone&) = default;
+  MeshCellZone(MeshCellZone &&)     = default;
+
+  MeshCellZone()          = default;
+  virtual ~MeshCellZone() = default;
+};
+
+template <size_t Dimension>
+MeshCellZone<Dimension> getMeshCellZone(const Mesh<Connectivity<Dimension>>& mesh,
+                                        const IZoneDescriptor& zone_descriptor);
+
+#endif   // MESH_CELL_ZONE_HPP