diff --git a/CMakeLists.txt b/CMakeLists.txt
index 66f56188535e5a3b7f26bb74d67b4d5cd283eaea..08782444aaad92cb5142f5940b19272dfea18249 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,12 +22,19 @@ if("${PUGS_SHORT_VERSION}" STREQUAL "")
   message(FATAL_ERROR "Unable to compute short version from PUGS_VERSION=${PUGS_VERSION}")
 endif()
 
+# 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 project version as PUGS_SHORT_VERSION
-project (Pugs VERSION ${PUGS_SHORT_VERSION})
+set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
 
 #------------------------------------------------------
 
@@ -380,6 +387,7 @@ include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/rang/include")
 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
@@ -597,10 +605,6 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
   endif()
 endif()
 
-# -----------------------------------------------------
-
-link_libraries("-rdynamic")
-
 # ------------------- Source files --------------------
 # Pugs binary
 add_executable(
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 42d62fa0abac86a74a36379271482bc15b685997..c9c487b67b5fdbf63d2b307baf1497519666a5d8 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -13,6 +13,27 @@
 
 #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)
 {
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index b2e217b9374bb918e75d5a6dbe028ae4f8e12923..35bf6f7aa6a7704f2804125c488ac0bf115ddf39 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -31,13 +31,12 @@ class ModuleRepository
                             const IModule::NameValueMap& name_value_map,
                             SymbolTable& symbol_table);
 
+  static ModuleRepository* m_instance;
+
  public:
-  static ModuleRepository&
-  getInstance()
-  {
-    static ModuleRepository m_instance;
-    return m_instance;
-  }
+  static ModuleRepository& getInstance();
+
+  static void destroy();
 
   template <typename ModuleT>
   struct Subscribe
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/PluginsLoader.cpp b/src/utils/PluginsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8616c7a68ecb6703a91f4885693fbd020fd2406f
--- /dev/null
+++ b/src/utils/PluginsLoader.cpp
@@ -0,0 +1,96 @@
+#include <utils/PluginsLoader.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);
+    std::cout << " * \"" << rang::fgB::green << plugin << rang::fg::reset << "\"\n";
+  } else {
+    std::cout << "   " << 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::cout << ' ' << 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) {
+    std::cout << rang::style::bold << "Loading plugins" << rang::style::reset << '\n';
+    for (auto&& plugin : plugin_vector) {
+      this->_open(plugin);
+    }
+    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