diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8047520391cb75d3f46acad5c87a720414f3c040..08782444aaad92cb5142f5940b19272dfea18249 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,12 +22,20 @@ if("${PUGS_SHORT_VERSION}" STREQUAL "")
   message(FATAL_ERROR "Unable to compute short version from PUGS_VERSION=${PUGS_VERSION}")
 endif()
 
-
-set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
-
 # set project version as PUGS_SHORT_VERSION
 project (Pugs VERSION ${PUGS_SHORT_VERSION})
 
+# -----------------------------------------------------
+# dynamic libraries
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+link_libraries("-rdynamic")
+set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+
+#------------------------------------------------------
+
+set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
+
 #------------------------------------------------------
 
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -372,12 +380,14 @@ endif()
 #------------------------------------------------------
 
 # Rang (colors? Useless thus necessary!)
+add_subdirectory(${PUGS_SOURCE_DIR}/packages/rang/)
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/rang/include")
 
 # CLI11
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/CLI11/include")
 
 # PEGTL
+add_subdirectory(${PUGS_SOURCE_DIR}/packages/PEGTL/)
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/PEGTL/include/tao")
 
 # Pugs src
@@ -400,7 +410,7 @@ if(${PUGS_HAS_MPI})
   endif()
   set(MPIEXEC_NUMPROC 3)
   set(MPIEXEC_PATH_FLAG --path)
-  set(MPI_LAUNCHER ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_NUMPROC} ${MPIEXEC_PATH_FLAG} ${PUGS_BINARY_DIR} ${MPIEXEC_OPTION_FLAGS})
+  set(MPI_LAUNCHER ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_NUMPROC} ${MPIEXEC_OPTION_FLAGS})
 endif()
 
 add_custom_target(all_unit_tests
@@ -427,6 +437,7 @@ endif()
 
 add_custom_target(run_mpi_unit_tests
   COMMAND ${MPI_LAUNCHER} "${PUGS_BINARY_DIR}/mpi_unit_tests"
+  WORKING_DIRECTORY ${PUGS_BINARY_DIR}
   DEPENDS run_unit_tests
   COMMENT ${RUN_MPI_UNIT_TESTS_COMMENT}
   )
@@ -594,10 +605,6 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
   endif()
 endif()
 
-# -----------------------------------------------------
-
-link_libraries("-rdynamic")
-
 # ------------------- Source files --------------------
 # Pugs binary
 add_executable(
diff --git a/README.md b/README.md
index 36e460a851a53d40b4ab442460594091fa22b20f..c859bc694043fc05a83ed47dae3f515eca0bbb42 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,10 @@ that are assembled together by a user friendly language.
 ## Requirements
 
 For the most basic build, `pugs` only requires
-- a C++-17 compiler.
+- a C++-20 compiler.
   - g++-10 or higher versions
-  - clang-10 or higher versions
-- `CMake` (at least 3.16)
+  - clang-11 or higher versions
+- `CMake` (at least 3.19)
 
 > **Warning**:<br>
 > Building `pugs` in its source directory is **forbidden**. Trying to
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eebccac12c3c1c7b80b6b1e13ff81d0e802a8def..30d920bb0c111049ae655f67e3951424134dbb01 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,6 +3,21 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
 #------------------------------------------------------
 
+install(
+  DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+)
+
+install(
+  DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+  PATTERN "CMakeFiles" EXCLUDE
+)
+
 # Pugs utils
 add_subdirectory(utils)
 
diff --git a/src/language/ast/ASTModulesImporter.cpp b/src/language/ast/ASTModulesImporter.cpp
index 4cae0a921c139b3ae68a3decb0ce4f83876c4701..e1dc0043e689b506776540bb57e4851dc10883ae 100644
--- a/src/language/ast/ASTModulesImporter.cpp
+++ b/src/language/ast/ASTModulesImporter.cpp
@@ -26,10 +26,10 @@ ASTModulesImporter::_importModule(ASTNode& import_node)
     std::cout << " * importing '" << rang::fgB::green << module_name << rang::style::reset << "' module\n";
   }
 
-  m_module_repository.populateSymbolTable(module_name_node, m_symbol_table);
-  m_module_repository.registerOperators(module_name);
+  ModuleRepository::getInstance().populateSymbolTable(module_name_node, m_symbol_table);
+  ModuleRepository::getInstance().registerOperators(module_name);
   if (CheckpointResumeRepository::isCreated()) {
-    m_module_repository.registerCheckpointResume(module_name);
+    ModuleRepository::getInstance().registerCheckpointResume(module_name);
   }
 }
 
@@ -49,7 +49,7 @@ ASTModulesImporter::ASTModulesImporter(ASTNode& root_node) : m_symbol_table{*roo
 {
   Assert(root_node.is_root());
   OperatorRepository::instance().reset();
-  m_module_repository.populateMandatoryData(root_node, m_symbol_table);
+  ModuleRepository::getInstance().populateMandatoryData(root_node, m_symbol_table);
 
   this->_importAllModules(root_node);
 
diff --git a/src/language/ast/ASTModulesImporter.hpp b/src/language/ast/ASTModulesImporter.hpp
index 566155c4a19ecd00f0b049a29030c0036f212d8a..15ba8a7dc6cecbbb39bfb11c4d202d39200061a4 100644
--- a/src/language/ast/ASTModulesImporter.hpp
+++ b/src/language/ast/ASTModulesImporter.hpp
@@ -14,8 +14,6 @@ class ASTModulesImporter
   std::set<std::string> m_imported_modules;
   SymbolTable& m_symbol_table;
 
-  ModuleRepository m_module_repository;
-
   void _importModule(ASTNode& import_node);
   void _importAllModules(ASTNode& node);
 
@@ -23,7 +21,7 @@ class ASTModulesImporter
   const ModuleRepository&
   moduleRepository() const
   {
-    return m_module_repository;
+    return ModuleRepository::getInstance();
   }
 
   ASTModulesImporter(ASTNode& root_node);
diff --git a/src/language/modules/BinaryOperatorRegisterForVh.cpp b/src/language/modules/BinaryOperatorRegisterForVh.cpp
index 3eb83537323d91bbf6266cbefebf4d004d74c2be..d1c20cb78718568a3535e30f0b5c9ce255be1db6 100644
--- a/src/language/modules/BinaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/BinaryOperatorRegisterForVh.cpp
@@ -1,6 +1,6 @@
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
diff --git a/src/language/modules/CoreModule.cpp b/src/language/modules/CoreModule.cpp
index d7b331a1dc606a83cf77dfe405b184b9ff143308..47e0183256413b7579165b26364dff68f0614a74 100644
--- a/src/language/modules/CoreModule.cpp
+++ b/src/language/modules/CoreModule.cpp
@@ -42,7 +42,7 @@
 #include <utils/checkpointing/ResumingManager.hpp>
 #include <utils/checkpointing/WriteOStream.hpp>
 
-#include <random>
+#include <language/modules/ModuleRepository.hpp>
 
 CoreModule::CoreModule() : BuiltinModule(true)
 {
@@ -219,3 +219,5 @@ CoreModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<CoreModule> core_module;
diff --git a/src/language/modules/DevUtilsModule.cpp b/src/language/modules/DevUtilsModule.cpp
index 361b4357eda0ebbfe4350cc6c64bbe72cfaa0829..4595ea6c9d9a45431b303c00b4de6defdda38b7b 100644
--- a/src/language/modules/DevUtilsModule.cpp
+++ b/src/language/modules/DevUtilsModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/DevUtilsModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <dev/ParallelChecker.hpp>
 #include <language/modules/MeshModuleTypes.hpp>
 #include <language/modules/SchemeModuleTypes.hpp>
@@ -126,3 +128,5 @@ DevUtilsModule::registerOperators() const
 void
 DevUtilsModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<DevUtilsModule> dev_utils_module;
diff --git a/src/language/modules/LinearSolverModule.cpp b/src/language/modules/LinearSolverModule.cpp
index aef6470d2455909f5ad2d307aa4e5cb859b8bcbb..d3e2de0faeb52c213f372e3a751f075ea2d735d7 100644
--- a/src/language/modules/LinearSolverModule.cpp
+++ b/src/language/modules/LinearSolverModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/LinearSolverModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <algebra/LinearSolver.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/TypeDescriptor.hpp>
@@ -98,3 +100,5 @@ LinearSolverModule::registerOperators() const
 void
 LinearSolverModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<LinearSolverModule> linear_solver_module;
diff --git a/src/language/modules/MathFunctionRegisterForVh.cpp b/src/language/modules/MathFunctionRegisterForVh.cpp
index b31fe47d0eba87ff2e7f0823a530bf170e410d6c..d9a30e86996a9cd6b99af63d64a8ab33264c7fe3 100644
--- a/src/language/modules/MathFunctionRegisterForVh.cpp
+++ b/src/language/modules/MathFunctionRegisterForVh.cpp
@@ -1,6 +1,7 @@
 #include <language/modules/MathFunctionRegisterForVh.hpp>
 
 #include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
 #include <scheme/DiscreteFunctionVariant.hpp>
diff --git a/src/language/modules/MathModule.cpp b/src/language/modules/MathModule.cpp
index 19a35a1529b1b65310c3c51d69fe4c5d02dc4d8a..1caea452428125e7a1c16aa88cb8278a5685fb35 100644
--- a/src/language/modules/MathModule.cpp
+++ b/src/language/modules/MathModule.cpp
@@ -1,5 +1,6 @@
 #include <language/modules/MathModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 
 #include <cmath>
@@ -136,3 +137,5 @@ MathModule::registerOperators() const
 void
 MathModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<MathModule> math_module;
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index c76260ce58c75c130e4638054fc394458fedbf63..c060da50d6da17915937edda6f5403a9e604355e 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/MeshModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <algebra/TinyVector.hpp>
 #include <language/node_processor/ExecutionPolicy.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
@@ -160,27 +162,28 @@ MeshModule::MeshModule()
 
                                           ));
 
-  this
-    ->_addBuiltinFunction("interpolate",
-                          std::function(
+  this->_addBuiltinFunction("interpolate",
+                            std::function(
 
-                            [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
-                               const FunctionSymbolId& function_id) -> std::shared_ptr<const ItemValueVariant> {
-                              return ItemValueVariantFunctionInterpoler{mesh_v, *item_type, function_id}.interpolate();
-                            }
+                              [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
+                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const ItemValueVariant> {
+                                return ItemValueVariantFunctionInterpoler{mesh_v, *item_type, function_id}
+                                  .interpolate();
+                              }
 
-                            ));
+                              ));
 
-  this->_addBuiltinFunction(
-    "interpolate_array",
-    std::function(
+  this->_addBuiltinFunction("interpolate_array",
+                            std::function(
 
-      [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
-         const std::vector<FunctionSymbolId>& function_id_list) -> std::shared_ptr<const ItemArrayVariant> {
-        return ItemArrayVariantFunctionInterpoler{mesh_v, *item_type, function_id_list}.interpolate();
-      }
+                              [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
+                                 const std::vector<FunctionSymbolId>& function_id_list)
+                                -> std::shared_ptr<const ItemArrayVariant> {
+                                return ItemArrayVariantFunctionInterpoler{mesh_v, *item_type, function_id_list}
+                                  .interpolate();
+                              }
 
-      ));
+                              ));
 
   this->_addBuiltinFunction("transform",
                             std::function(
@@ -442,3 +445,5 @@ MeshModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<MeshModule> mesh_module;
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 59689fb6fee512e45cd76c7c9d3cd5c11d543ee8..c9c487b67b5fdbf63d2b307baf1497519666a5d8 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -1,14 +1,6 @@
 #include <language/modules/ModuleRepository.hpp>
 
 #include <language/ast/ASTNode.hpp>
-#include <language/modules/CoreModule.hpp>
-#include <language/modules/DevUtilsModule.hpp>
-#include <language/modules/LinearSolverModule.hpp>
-#include <language/modules/MathModule.hpp>
-#include <language/modules/MeshModule.hpp>
-#include <language/modules/SchemeModule.hpp>
-#include <language/modules/SocketModule.hpp>
-#include <language/modules/WriterModule.hpp>
 #include <language/utils/BasicAffectationRegistrerFor.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
@@ -19,9 +11,29 @@
 
 #include <utils/PugsAssert.hpp>
 
-#include <algorithm>
 #include <set>
 
+ModuleRepository* ModuleRepository::m_instance = nullptr;
+
+ModuleRepository&
+ModuleRepository::getInstance()
+{
+  if (m_instance == nullptr) {
+    m_instance = new ModuleRepository;
+  }
+
+  return *m_instance;
+}
+
+void
+ModuleRepository::destroy()
+{
+  if (m_instance != nullptr) {
+    delete m_instance;
+    m_instance = nullptr;
+  }
+}
+
 void
 ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
 {
@@ -52,18 +64,6 @@ ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
   Assert(success, "module has already been subscribed");
 }
 
-ModuleRepository::ModuleRepository()
-{
-  this->_subscribe(std::make_unique<CoreModule>());
-  this->_subscribe(std::make_unique<LinearSolverModule>());
-  this->_subscribe(std::make_unique<MathModule>());
-  this->_subscribe(std::make_unique<MeshModule>());
-  this->_subscribe(std::make_unique<SchemeModule>());
-  this->_subscribe(std::make_unique<SocketModule>());
-  this->_subscribe(std::make_unique<DevUtilsModule>());
-  this->_subscribe(std::make_unique<WriterModule>());
-}
-
 template <typename NameEmbedderMapT, typename EmbedderTableT>
 void
 ModuleRepository::_populateEmbedderTableT(const ASTNode& module_node,
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index 407d7b55bd9b7c8454159a315b9c2f7a256eecb6..35bf6f7aa6a7704f2804125c488ac0bf115ddf39 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -31,7 +31,23 @@ class ModuleRepository
                             const IModule::NameValueMap& name_value_map,
                             SymbolTable& symbol_table);
 
+  static ModuleRepository* m_instance;
+
  public:
+  static ModuleRepository& getInstance();
+
+  static void destroy();
+
+  template <typename ModuleT>
+  struct Subscribe
+  {
+    Subscribe()
+    {
+      static_assert(std::is_base_of_v<IModule, ModuleT>, "module must inherit IModule interface");
+      ModuleRepository::getInstance()._subscribe(std::make_unique<ModuleT>());
+    }
+  };
+
   void populateSymbolTable(const ASTNode& module_name_node, SymbolTable& symbol_table);
   void populateMandatoryData(const ASTNode& root_node, SymbolTable& symbol_table);
   void registerOperators(const std::string& module_name);
@@ -46,7 +62,10 @@ class ModuleRepository
   ModuleRepository(const ModuleRepository&) = delete;
   ModuleRepository(ModuleRepository&&)      = delete;
 
-  ModuleRepository();
+ private:
+  ModuleRepository() = default;
+
+ public:
   ~ModuleRepository() = default;
 };
 
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index f1a7d8d73606a6cd71e4aa6b4f7253f8773a804e..40cc703cc6dcc0029da81366fb95134fc1329599 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -1,4 +1,7 @@
 #include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
+
+#include <language/modules/ModuleRepository.hpp>
 
 #include <analysis/GaussLegendreQuadratureDescriptor.hpp>
 #include <analysis/GaussLobattoQuadratureDescriptor.hpp>
@@ -809,3 +812,5 @@ SchemeModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<SchemeModule> scheme_module;
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index c61b3084406dbd8ae9889c79b4172195daaa6fe8..cf169175c4b46f7364799e16bdb4b84633d1f61c 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -2,7 +2,6 @@
 #define SCHEME_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/modules/SchemeModuleTypes.hpp>
 
 class SchemeModule : public BuiltinModule
 {
diff --git a/src/language/modules/SocketModule.cpp b/src/language/modules/SocketModule.cpp
index 79321d088f960e0bb7450c82820cf6568e79d174..9c2ecdbe1b40730d0181d7f4ee6f3bf396f70a34 100644
--- a/src/language/modules/SocketModule.cpp
+++ b/src/language/modules/SocketModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/SocketModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
@@ -271,3 +273,5 @@ SocketModule::registerCheckpointResume() const
                            throw NotImplementedError("checkpoint/resume with sockets");
                          }));
 }
+
+ModuleRepository::Subscribe<SocketModule> socket_module;
diff --git a/src/language/modules/UnaryOperatorRegisterForVh.cpp b/src/language/modules/UnaryOperatorRegisterForVh.cpp
index e2e9c2db9b72a9a687806d24dee836cfd37a22e8..3d52ab54687eabcd6162536d88325b3285d45e1f 100644
--- a/src/language/modules/UnaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/UnaryOperatorRegisterForVh.cpp
@@ -1,6 +1,6 @@
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
 
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
 #include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index 6d84506501de277f1970c13a7c24cd587ed79f20..e524a885639ac9a2a2da6330422565447bcb347d 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -1,7 +1,9 @@
 #include <language/modules/WriterModule.hpp>
 
-#include <language/modules/MeshModule.hpp>
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/ModuleRepository.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/TypeDescriptor.hpp>
@@ -242,3 +244,5 @@ WriterModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<WriterModule> writer_module{};
diff --git a/src/main.cpp b/src/main.cpp
index a373bd2cd867b21b1d931449f12b64239ef03c22..cd576a9f63a8273b366a76a298b508e2fbb1ff54 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,7 @@
 #include <analysis/QuadratureManager.hpp>
 #include <dev/ParallelChecker.hpp>
 #include <language/PugsParser.hpp>
+#include <language/modules/ModuleRepository.hpp>
 #include <mesh/DualConnectivityManager.hpp>
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/MeshDataManager.hpp>
@@ -11,6 +12,8 @@
 #include <utils/RandomEngine.hpp>
 #include <utils/checkpointing/ResumingManager.hpp>
 
+#include <utils/PluginsLoader.hpp>
+
 int
 main(int argc, char* argv[])
 {
@@ -20,6 +23,8 @@ main(int argc, char* argv[])
 
   std::string filename = initialize(argc, argv);
 
+  PluginsLoader plugins_loader;
+
   SynchronizerManager::create();
   RandomEngine::create();
   QuadratureManager::create();
@@ -32,6 +37,8 @@ main(int argc, char* argv[])
   parser(filename);
   ExecutionStatManager::printInfo();
 
+  ModuleRepository::destroy();
+
   GlobalVariableManager::destroy();
 
   DualMeshManager::destroy();
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 13b0db80862c2b28e80dc18ff22951b08157db28..39e8cb35925fb1417222080e2ac0e060814ad353 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -16,6 +16,7 @@ add_library(
   Messenger.cpp
   Partitioner.cpp
   PETScWrapper.cpp
+  PluginsLoader.cpp
   PugsUtils.cpp
   RandomEngine.cpp
   ReproducibleSumManager.cpp
diff --git a/src/utils/ConsoleManager.hpp b/src/utils/ConsoleManager.hpp
index a2afe46fbc0ad16e263d1f5a9b3f8a84d5202d94..693219e30285d1b0acae26d4b43d310882069784 100644
--- a/src/utils/ConsoleManager.hpp
+++ b/src/utils/ConsoleManager.hpp
@@ -1,7 +1,7 @@
 #ifndef CONSOLE_MANAGER_HPP
 #define CONSOLE_MANAGER_HPP
 
-#include <string>
+#include <ostream>
 
 class ConsoleManager
 {
diff --git a/src/utils/PluginsLoader.cpp b/src/utils/PluginsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..35e24605e8a01b4d350aadb18bcacda313e9004c
--- /dev/null
+++ b/src/utils/PluginsLoader.cpp
@@ -0,0 +1,104 @@
+#include <utils/PluginsLoader.hpp>
+
+#include <utils/ConsoleManager.hpp>
+
+#include <dlfcn.h>
+
+#include <rang.hpp>
+
+#include <filesystem>
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <vector>
+
+std::vector<std::string>
+split(const std::string& full_string)
+{
+  std::vector<std::string> split_string;
+
+  std::stringstream is(full_string);
+
+  std::string segment;
+  while (std::getline(is, segment, ';')) {
+    if (segment.size() > 0) {
+      split_string.push_back(segment);
+    }
+  }
+  return split_string;
+}
+
+void
+PluginsLoader::_open(const std::string& plugin)
+{
+  auto handle = dlopen(plugin.c_str(), RTLD_NOW);
+  if (handle != nullptr) {
+    m_dl_handler_stack.push(handle);
+    if (ConsoleManager::showPreamble()) {
+      std::cout << " * \"" << rang::fgB::green << plugin << rang::fg::reset << "\"\n";
+    }
+  } else {
+    std::cerr << "   " << rang::fgB::red << "cannot load " << rang::fg::reset << '\"' << rang::fgB::yellow << plugin
+              << rang::fg::reset << "\"\n";
+  }
+}
+
+PluginsLoader::PluginsLoader()
+{
+  std::vector<std::string> plugin_vector;
+
+  {
+    char* env = getenv("PUGS_PLUGIN");
+    if (env != nullptr) {
+      std::string plugins = env;
+      plugin_vector       = split(plugins);
+    }
+  }
+
+  {
+    char* env = getenv("PUGS_PLUGIN_DIR");
+    if (env != nullptr) {
+      std::string paths                    = env;
+      std::vector<std::string> path_vector = split(paths);
+      for (auto&& path : path_vector) {
+        if (access(path.c_str(), R_OK) == -1) {
+          std::cerr << ' ' << rang::fgB::red << 'X' << rang::fg::reset << " cannot access plugin dir \""
+                    << rang::fgB::yellow << path << rang::fg::reset << "\"\n";
+        } else {
+          for (auto&& entry :
+               std::filesystem::directory_iterator(path,
+                                                   (std::filesystem::directory_options::follow_directory_symlink |
+                                                    std::filesystem::directory_options::skip_permission_denied))) {
+            if (entry.path().extension() == ".so") {
+              plugin_vector.push_back(entry.path().string());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // keep unique entries
+  std::sort(plugin_vector.begin(), plugin_vector.end());
+  plugin_vector.resize(std::distance(plugin_vector.begin(), std::unique(plugin_vector.begin(), plugin_vector.end())));
+
+  if (plugin_vector.size() > 0) {
+    if (ConsoleManager::showPreamble()) {
+      std::cout << rang::style::bold << "Loading plugins" << rang::style::reset << '\n';
+    }
+    for (auto&& plugin : plugin_vector) {
+      this->_open(plugin);
+    }
+    if (ConsoleManager::showPreamble()) {
+      std::cout << "-------------------------------------------------------\n";
+    }
+  }
+}
+
+PluginsLoader::~PluginsLoader()
+{
+  while (not m_dl_handler_stack.empty()) {
+    dlclose(m_dl_handler_stack.top());
+    m_dl_handler_stack.pop();
+  }
+}
diff --git a/src/utils/PluginsLoader.hpp b/src/utils/PluginsLoader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..76be25c6934c5f336360e938d9a40d75f075e6d3
--- /dev/null
+++ b/src/utils/PluginsLoader.hpp
@@ -0,0 +1,23 @@
+#ifndef PLUGINS_LOADER_HPP
+#define PLUGINS_LOADER_HPP
+
+#include <stack>
+#include <string>
+
+class PluginsLoader
+{
+ private:
+  std::stack<void*> m_dl_handler_stack;
+
+  void _open(const std::string& filename);
+
+ public:
+  PluginsLoader();
+
+  PluginsLoader(const PluginsLoader&) = delete;
+  PluginsLoader(PluginsLoader&&)      = delete;
+
+  ~PluginsLoader();
+};
+
+#endif   // PLUGINS_LOADER_HPP
diff --git a/tools/generate-plugin.sh b/tools/generate-plugin.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a99f6976013582125f678a705a7a337dc5c3ac2d
--- /dev/null
+++ b/tools/generate-plugin.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+BOLD='\e[1m'
+RESET='\e[0m'
+
+GREEN='\e[92m'
+RED='\e[91m'
+YELLOW='\e[93m'
+
+echo -ne ${BOLD}
+echo -e "---------------------"
+echo -e "pugs plugin generator"
+echo -e "---------------------"
+echo -e ${RESET}
+
+CURRENT_DIR="$(pwd -P)"
+SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+PUGS_DIR="$(dirname ${SCRIPT_DIR})"
+
+if [[ "${CURRENT_DIR}" =~ "${PUGS_DIR}" ]]
+then
+    echo -e ${RED}"Aborting..."${RESET}
+    echo -e "run this script outside of pugs sources"
+    exit 1
+fi
+
+NAME_RE='^[A-Z][a-zA-Z0-9]*$'
+
+echo "   Plugin name must fulfill the following constrains:"
+echo "   - be a single word that starts by an upper case,"
+echo "   - contains only letters or numbers,"
+echo "   and preferably separate words with caps."
+echo
+echo "   ex.: MyFirstPlugin"
+echo
+
+while [[ ! "${PLUGIN_NAME}" =~ $NAME_RE ]]
+do
+    echo -n "Give plugin name: "
+    read -r PLUGIN_NAME
+
+    if [[ ! "${PLUGIN_NAME}" =~ $NAME_RE ]]
+    then
+	echo -e ${RED}"  invalid name!"${RESET}
+	echo
+	unset PLUGIN_NAME
+    fi
+
+done
+
+PLUGIN_UP="${PLUGIN_NAME^^}"
+PLUGIN_LOW="${PLUGIN_NAME,,}"
+echo
+echo -e "creating plugin ${YELLOW}${PLUGIN_NAME}${RESET} in directory ${YELLOW}${PLUGIN_LOW}${RESET}"
+echo
+
+if [[ -e ${PLUGIN_LOW} ]]
+then
+    echo -e ${RED}"Aborting..."${RESET}
+    echo -e "directory \"${PLUGIN_LOW}\" ${YELLOW}already exists${RESET}!"
+    exit 1
+fi
+
+
+mkdir "${PLUGIN_LOW}"
+mkdir "${PLUGIN_LOW}/cmake"
+
+cp "${PUGS_DIR}"/cmake/CheckNotInSources.cmake "${PLUGIN_LOW}"/cmake/
+cp "${PUGS_DIR}"/tools/plugin-template/FindPugs.cmake "${PLUGIN_LOW}"/cmake/
+cp "${PUGS_DIR}"/.gitignore "${PLUGIN_LOW}"
+cp "${PUGS_DIR}"/.clang-format "${PLUGIN_LOW}"
+cat "${PUGS_DIR}"/tools/plugin-template/CMakeLists.txt-template | sed s/_PLUGIN_NAME_/${PLUGIN_NAME}/g | sed s/_PLUGIN_LOW_/${PLUGIN_LOW}/g | sed s/_PLUGIN_UP_/${PLUGIN_UP}/g > "${PLUGIN_LOW}"/CMakeLists.txt
+cat "${PUGS_DIR}"/tools/plugin-template/Module.hpp-template | sed s/_PLUGIN_NAME_/${PLUGIN_NAME}/g | sed s/_PLUGIN_LOW_/${PLUGIN_LOW}/g | sed s/_PLUGIN_UP_/${PLUGIN_UP}/g > "${PLUGIN_LOW}"/${PLUGIN_NAME}Module.hpp
+cat "${PUGS_DIR}"/tools/plugin-template/Module.cpp-template | sed s/_PLUGIN_NAME_/${PLUGIN_NAME}/g | sed s/_PLUGIN_LOW_/${PLUGIN_LOW}/g | sed s/_PLUGIN_UP_/${PLUGIN_UP}/g > "${PLUGIN_LOW}"/${PLUGIN_NAME}Module.cpp
+cat "${PUGS_DIR}"/tools/plugin-template/README.md-template | sed s/_PLUGIN_NAME_/${PLUGIN_NAME}/g | sed s/_PLUGIN_LOW_/${PLUGIN_LOW}/g | sed s/_PLUGIN_UP_/${PLUGIN_UP}/g > "${PLUGIN_LOW}"/README.md
+
+(cd "${PLUGIN_LOW}"; git init -q)
+(cd "${PLUGIN_LOW}"; git add .)
+(cd "${PLUGIN_LOW}"; git commit -m "init" -q)
+
+echo -e ${GREEN}"Creation finished successfully!"${RESET}
diff --git a/tools/plugin-template/CMakeLists.txt-template b/tools/plugin-template/CMakeLists.txt-template
new file mode 100644
index 0000000000000000000000000000000000000000..ae56000c37ed40cf18def1262e67554e0f5ee302
--- /dev/null
+++ b/tools/plugin-template/CMakeLists.txt-template
@@ -0,0 +1,95 @@
+cmake_minimum_required (VERSION 3.19)
+
+project("_PLUGIN_LOW_")
+
+# CMake utils
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+# Forbids in-source builds
+include(CheckNotInSources)
+
+# use PkgConfig to find packages
+find_package(PkgConfig REQUIRED)
+find_package(Pugs REQUIRED)
+
+# -----------------------------------------------------
+# dynamic libraries
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+link_libraries("-rdynamic")
+set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+
+#------------------------------------------------------
+
+set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
+
+#------------------------------------------------------
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+#------------------------------------------------------
+
+set(PUGS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+set(PUGS_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+
+# Add new build types
+set(CMAKE_CXX_FLAGS_COVERAGE
+  "-g -O0 --coverage"
+  CACHE STRING "Flags used by the C++ compiler during coverage builds."
+  FORCE )
+set(CMAKE_C_FLAGS_COVERAGE
+  "-g -O0 --coverage"
+  CACHE STRING "Flags used by the C compiler during coverage builds."
+  FORCE )
+set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
+  "--coverage"
+  CACHE STRING "Flags used for linking binaries during coverage builds."
+  FORCE )
+set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
+  "--coverage"
+  CACHE STRING "Flags used by the shared libraries linker during coverage builds."
+  FORCE )
+mark_as_advanced(
+  CMAKE_CXX_FLAGS_COVERAGE
+  CMAKE_C_FLAGS_COVERAGE
+  CMAKE_EXE_LINKER_FLAGS_COVERAGE
+  CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
+
+if(CMAKE_BUILD_TYPE)
+  string(REGEX MATCH "^(Release|Debug|Coverage)$" VALID_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
+  if(NOT VALID_BUILD_TYPE)
+    message(FATAL_ERROR "Invalid CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE}'")
+  endif()
+endif()
+
+# Default build type is Release
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE "Release" CACHE STRING
+      "Choose the type of build: Release Debug Coverage."
+      FORCE)
+endif()
+
+#------------------------------------------------------
+# default build shared libraries
+if (NOT BUILD_SHARED_LIBS)
+  set(BUILD_SHARED_LIBS ON CACHE STRING "" FORCE)
+endif()
+
+#------------------------------------------------------
+
+# Checks if compiler version is compatible with Pugs sources
+set(GNU_CXX_MIN_VERSION "10.0.0")
+set(CLANG_CXX_MIN_VERSION "11.0.0")
+
+#------------------------------------------------------
+# Change Kokkos namespace to avoid conflicts
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DKokkos=InlineKokkos")
+
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include/kokkos")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include/tao/")
+
+add_library(_PLUGIN_NAME_
+  _PLUGIN_NAME_Module.cpp
+)
diff --git a/tools/plugin-template/FindPugs.cmake b/tools/plugin-template/FindPugs.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6269bed8a9055339894cdd9fa9e63b2b479c9b70
--- /dev/null
+++ b/tools/plugin-template/FindPugs.cmake
@@ -0,0 +1,10 @@
+# Finds for the pugs installation directory
+
+find_path(PUGS_PREFIX_PATH include/utils/pugs_version.hpp
+  HINTS
+  $ENV{PUGS_INSTALL_DIR}
+  /usr/local/pugs
+  NO_DEFAULT_PATH
+)
+
+find_package_handle_standard_args(Pugs REQUIRED_VARS PUGS_PREFIX_PATH )
diff --git a/tools/plugin-template/Module.cpp-template b/tools/plugin-template/Module.cpp-template
new file mode 100644
index 0000000000000000000000000000000000000000..dd264e03631590e3cf0db152779e2118df86d474
--- /dev/null
+++ b/tools/plugin-template/Module.cpp-template
@@ -0,0 +1,28 @@
+#include <_PLUGIN_NAME_Module.hpp>
+
+#include <language/modules/ModuleRepository.hpp>
+#include <language/utils/BuiltinFunctionEmbedder.hpp>
+
+_PLUGIN_NAME_Module::_PLUGIN_NAME_Module() : BuiltinModule(false)
+{
+  // Simple hello world example
+  this->_addBuiltinFunction("_PLUGIN_LOW__hello", std::function(
+
+                                                    []() -> void { std::cout << "_PLUGIN_NAME_: Hello world\n"; }
+
+                                                    ));
+}
+
+void
+_PLUGIN_NAME_Module::registerOperators() const
+{
+  // Kept empty for basic use
+}
+
+void
+_PLUGIN_NAME_Module::registerCheckpointResume() const
+{
+  // kept empty for basic use
+}
+
+ModuleRepository::Subscribe<_PLUGIN_NAME_Module> _PLUGIN_LOW__module;
diff --git a/tools/plugin-template/Module.hpp-template b/tools/plugin-template/Module.hpp-template
new file mode 100644
index 0000000000000000000000000000000000000000..757e3b5e650390251d15515cfdcf26a9c970a102
--- /dev/null
+++ b/tools/plugin-template/Module.hpp-template
@@ -0,0 +1,22 @@
+#ifndef _PLUGIN_UP__MODULE_HPP
+#define _PLUGIN_UP__MODULE_HPP
+
+#include <language/modules/BuiltinModule.hpp>
+
+class _PLUGIN_NAME_Module : public BuiltinModule
+{
+ public:
+  std::string_view
+  name() const final
+  {
+    return "_PLUGIN_LOW_";
+  }
+
+  void registerOperators() const final;
+  void registerCheckpointResume() const final;
+
+  _PLUGIN_NAME_Module();
+  ~_PLUGIN_NAME_Module() = default;
+};
+
+#endif   // _PLUGIN_UP__MODULE_HPP
diff --git a/tools/plugin-template/README.md-template b/tools/plugin-template/README.md-template
new file mode 100644
index 0000000000000000000000000000000000000000..d89c74f974696b95dc875e5357007b5b67fba557
--- /dev/null
+++ b/tools/plugin-template/README.md-template
@@ -0,0 +1,67 @@
+`pugs`'s plugin `_PLUGIN_NAME_`
+===============================
+
+# Building `_PLUGIN_NAME_`
+
+## `pugs` installation
+
+Building this plugin requires an **installed** version of `pugs`.
+`pugs` follows standard `cmake` installation recipes.
+
+Before building `pugs` one should define its installation directory.
+In the `pugs` compilation directory one should execute
+```shell
+cmake -DCMAKE_INSTALL_PREFIX=pugs_install_dir ...
+```
+where `pugs_install_dir` is the chosen installation directory.
+
+Then one simply runs
+```shell
+make install
+```
+
+## building the plugin `_PLUGIN_NAME_`
+
+> **Warning**:<br>
+> Building `_PLUGIN_NAME_` in its source directory is
+> **forbidden**. Trying to do so will result in a failure. However it
+> generally leaves some garbage files in the source directory, namely
+> the `CMakeCache.txt` and the `CMakeFiles` directory. `CMake` itself
+> is not able to remove them, to avoid the risk of compilation issues,
+> one has to dot it manually...
+
+In the build directory one runs
+```shell
+PUGS_INSTALL_DIR=pugs_install_dir cmake _PLUGIN_LOW__dir
+```
+where `pugs_install_dir` has the same value as above and `_PLUGIN_LOW__dir`
+is the directory that contains this `README.md` file.
+
+Then to build the plugin, one runs
+```shell
+make
+```
+
+If anything runs fine, the dynamic library `lib_PLUGIN_NAME_.so` is
+built.
+
+# Using `_PLUGIN_NAME_`
+
+In order to use the created plugin, one simply has to give the
+location of `lib_PLUGIN_NAME_.so` to `pugs`. This is done by means of
+environment variables. There are two possibilities:
+- `PUGS_PLUGIN` contains a semicolumn separated list of plugin
+  libraries,
+- `PUGS_PLUGIN_DIR` contains a semicolumn separated list of path to
+  plugin libraries.
+
+Example
+```shell
+export PUGS_PLUGIN="/pathtoplugin/lib_PLUGIN_NAME_.so"
+```
+or
+```shell
+export PUGS_PLUGIN_DIR="/pathtoplugin1;/pathtoplugin2"
+```
+
+Then one launches `pugs` classically.