diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index c2f3a094429464fdf89e70215023e97747cd56cd..bb5ccdf3dd41c58632f3e438587e330fe5777530 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -12,6 +12,113 @@
 #include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
+template <size_t Dimension, typename DataType>
+void
+WriterModule::registerDiscreteFunctionP0(const std::string& name,
+                                         const IDiscreteFunction& i_discrete_function,
+                                         OutputNamedItemValueSet& named_item_value_set)
+{
+  const DiscreteFunctionP0<Dimension, DataType>& discrete_function =
+    dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(i_discrete_function);
+  named_item_value_set.add(NamedItemValue{name, discrete_function.cellValues()});
+}
+
+template <size_t Dimension>
+void
+WriterModule::registerDiscreteFunctionP0(const std::string& name,
+                                         const IDiscreteFunction& i_discrete_function,
+                                         OutputNamedItemValueSet& named_item_value_set)
+{
+  const ASTNodeDataType& data_type = i_discrete_function.dataType();
+  switch (data_type) {
+  case ASTNodeDataType::bool_t: {
+    registerDiscreteFunctionP0<Dimension, bool>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case ASTNodeDataType::unsigned_int_t: {
+    registerDiscreteFunctionP0<Dimension, uint64_t>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case ASTNodeDataType::int_t: {
+    registerDiscreteFunctionP0<Dimension, int64_t>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case ASTNodeDataType::double_t: {
+    registerDiscreteFunctionP0<Dimension, double>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case ASTNodeDataType::vector_t: {
+    switch (data_type.dimension()) {
+    case 1: {
+      registerDiscreteFunctionP0<Dimension, TinyVector<1, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    case 2: {
+      registerDiscreteFunctionP0<Dimension, TinyVector<2, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    case 3: {
+      registerDiscreteFunctionP0<Dimension, TinyVector<3, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    default: {
+      throw UnexpectedError("invalid vector dimension");
+    }
+    }
+    break;
+  }
+  case ASTNodeDataType::matrix_t: {
+    Assert(data_type.nbRows() == data_type.nbColumns(), "invalid matrix dimensions");
+    switch (data_type.nbRows()) {
+    case 1: {
+      registerDiscreteFunctionP0<Dimension, TinyMatrix<1, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    case 2: {
+      registerDiscreteFunctionP0<Dimension, TinyMatrix<2, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    case 3: {
+      registerDiscreteFunctionP0<Dimension, TinyMatrix<3, double>>(name, i_discrete_function, named_item_value_set);
+      break;
+    }
+    default: {
+      throw UnexpectedError("invalid matrix dimension");
+    }
+    }
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid data type " + dataTypeName(data_type));
+  }
+  }
+}
+
+void
+WriterModule::registerDiscreteFunctionP0(const NamedDiscreteFunction& named_discrete_function,
+                                         OutputNamedItemValueSet& named_item_value_set)
+{
+  const IDiscreteFunction& i_discrete_function = *named_discrete_function.discreteFunction();
+  const std::string& name                      = named_discrete_function.name();
+  switch (i_discrete_function.mesh()->dimension()) {
+  case 1: {
+    registerDiscreteFunctionP0<1>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case 2: {
+    registerDiscreteFunctionP0<2>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  case 3: {
+    registerDiscreteFunctionP0<3>(name, i_discrete_function, named_item_value_set);
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
 WriterModule::WriterModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const NamedDiscreteFunction>>);
@@ -86,7 +193,7 @@ WriterModule::WriterModule()
                               ));
 
   this->_addBuiltinFunction(
-    "writeVTK",
+    "write",
     std::make_shared<
       BuiltinFunctionEmbedder<void(std::shared_ptr<const IWriter>,
                                    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>&, const double&)>>(
@@ -115,10 +222,7 @@ WriterModule::WriterModule()
 
           switch (i_discrete_function.descriptor().type()) {
           case DiscreteFunctionType::P0: {
-            const DiscreteFunctionP0<2, double>& discrete_function =
-              dynamic_cast<const DiscreteFunctionP0<2, double>&>(i_discrete_function);
-
-            named_item_value_set.add(NamedItemValue{named_discrete_function->name(), discrete_function.cellValues()});
+            WriterModule::registerDiscreteFunctionP0(*named_discrete_function, named_item_value_set);
             break;
           }
           default: {
diff --git a/src/language/modules/WriterModule.hpp b/src/language/modules/WriterModule.hpp
index 49ad6d9fe5b0ce5a39d3b6c0322d200875d48157..08f17851e15153baa4b8cdcfb9dcec25fa7c984e 100644
--- a/src/language/modules/WriterModule.hpp
+++ b/src/language/modules/WriterModule.hpp
@@ -5,7 +5,12 @@
 #include <language/utils/ASTNodeDataTypeTraits.hpp>
 #include <utils/PugsMacros.hpp>
 
+class OutputNamedItemValueSet;
 class NamedDiscreteFunction;
+class IDiscreteFunction;
+
+#include <string>
+
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const NamedDiscreteFunction>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("output");
@@ -17,6 +22,15 @@ inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IWriter>> =
 
 class WriterModule : public BuiltinModule
 {
+ private:
+  template <size_t Dimension, typename DataType>
+  static void registerDiscreteFunctionP0(const std::string& name, const IDiscreteFunction&, OutputNamedItemValueSet&);
+
+  template <size_t Dimension>
+  static void registerDiscreteFunctionP0(const std::string& name, const IDiscreteFunction&, OutputNamedItemValueSet&);
+
+  static void registerDiscreteFunctionP0(const NamedDiscreteFunction&, OutputNamedItemValueSet&);
+
  public:
   std::string_view
   name() const final