diff --git a/ChemistryLib/CreateChemicalSolverInterface.cpp b/ChemistryLib/CreateChemicalSolverInterface.cpp
index d1d7683d4fa965789946c9add884499d7cd295e2..3f4a3d28253d3d816437f588f6d6f842b397435c 100644
--- a/ChemistryLib/CreateChemicalSolverInterface.cpp
+++ b/ChemistryLib/CreateChemicalSolverInterface.cpp
@@ -17,9 +17,11 @@
 #include "PhreeqcIOData/CreateEquilibriumPhase.h"
 #include "PhreeqcIOData/CreateKineticReactant.h"
 #include "PhreeqcIOData/CreateOutput.h"
+#include "PhreeqcIOData/CreateUserPunch.h"
 #include "PhreeqcIOData/EquilibriumPhase.h"
 #include "PhreeqcIOData/KineticReactant.h"
 #include "PhreeqcIOData/ReactionRate.h"
+#include "PhreeqcIOData/UserPunch.h"
 #include "PhreeqcKernel.h"
 #include "PhreeqcKernelData/AqueousSolution.h"
 #include "PhreeqcKernelData/CreateAqueousSolution.h"
@@ -93,6 +95,11 @@ createChemicalSolverInterface<ChemicalSolver::Phreeqc>(
     auto const project_file_name = BaseLib::joinPaths(
         output_directory,
         BaseLib::extractBaseNameWithoutExtension(config.getProjectFileName()));
+
+    // user punch
+    auto user_punch = PhreeqcIOData::createUserPunch(
+        //! \ogs_file_param{prj__chemical_system__user_punch}
+        config.getConfigSubtreeOptional("user_punch"), mesh);
     auto output = PhreeqcIOData::createOutput(
         components, equilibrium_phases, kinetic_reactants, project_file_name);
 
diff --git a/ChemistryLib/PhreeqcIO.cpp b/ChemistryLib/PhreeqcIO.cpp
index 5fd72adc44353fad7041c073254d03e4e6850e67..60c5f0c49931ed7bc7cd659cf6342d22c37d01f5 100644
--- a/ChemistryLib/PhreeqcIO.cpp
+++ b/ChemistryLib/PhreeqcIO.cpp
@@ -370,6 +370,8 @@ std::istream& operator>>(std::istream& in, PhreeqcIO& phreeqc_io)
         auto& components = aqueous_solution.components;
         auto& equilibrium_phases = phreeqc_io._equilibrium_phases;
         auto& kinetic_reactants = phreeqc_io._kinetic_reactants;
+        auto& user_punch = phreeqc_io._user_punch;
+        auto& secondary_variables = user_punch->secondary_variables;
         for (int item_id = 0; item_id < static_cast<int>(accepted_items.size());
              ++item_id)
         {
@@ -426,6 +428,18 @@ std::istream& operator>>(std::istream& in, PhreeqcIO& phreeqc_io)
                         accepted_items[item_id];
                     break;
                 }
+                case ItemType::SecondaryVariable:
+                {
+                    // Update values of secondary variables
+                    auto& secondary_variable = BaseLib::findElementOrError(
+                        secondary_variables.begin(), secondary_variables.end(),
+                        compare_by_name,
+                        "Could not find secondary variable'" + item_name +
+                            "'.");
+                    (*secondary_variable.value)[chemical_system_id] =
+                        accepted_items[item_id];
+                    break;
+                }
             }
         }
     }
diff --git a/ChemistryLib/PhreeqcIOData/CreateOutput.cpp b/ChemistryLib/PhreeqcIOData/CreateOutput.cpp
index 7f2d5f9b7457e00673decdcff4d47819166779b5..669967e7b739f1bce793df45518c92baa736c299 100644
--- a/ChemistryLib/PhreeqcIOData/CreateOutput.cpp
+++ b/ChemistryLib/PhreeqcIOData/CreateOutput.cpp
@@ -15,6 +15,7 @@
 #include "CreateOutput.h"
 #include "EquilibriumPhase.h"
 #include "KineticReactant.h"
+#include "UserPunch.h"
 
 namespace ChemistryLib
 {
@@ -24,6 +25,7 @@ std::unique_ptr<Output> createOutput(
     std::vector<Component> const& components,
     std::vector<EquilibriumPhase> const& equilibrium_phases,
     std::vector<KineticReactant> const& kinetic_reactants,
+    std::unique_ptr<UserPunch> const& user_punch,
     std::string const& project_file_name)
 {
     // Mark which phreeqc output items will be held.
@@ -48,6 +50,13 @@ std::unique_ptr<Output> createOutput(
                                     kinetic_reactant.item_type);
     }
 
+    if (user_punch)
+    {
+        auto const& secondary_variables = user_punch->secondary_variables;
+        std::transform(secondary_variables.begin(), secondary_variables.end(),
+                       std::back_inserter(accepted_items), accepted_item);
+    }
+
     // Record ids of which phreeqc output items will be dropped.
     BasicOutputSetups basic_output_setups(project_file_name);
     auto const num_dropped_basic_items =
diff --git a/ChemistryLib/PhreeqcIOData/CreateOutput.h b/ChemistryLib/PhreeqcIOData/CreateOutput.h
index 3ef3993bb1a8384803567e0cfb708d02e1c56e0f..ce90837555837e1dbd346e8713e66a0ac876aaec 100644
--- a/ChemistryLib/PhreeqcIOData/CreateOutput.h
+++ b/ChemistryLib/PhreeqcIOData/CreateOutput.h
@@ -21,11 +21,13 @@ struct Output;
 struct Component;
 struct EquilibriumPhase;
 struct KineticReactant;
+struct UserPunch;
 
 std::unique_ptr<Output> createOutput(
     std::vector<Component> const& components,
     std::vector<EquilibriumPhase> const& equilibrium_phases,
     std::vector<KineticReactant> const& kinetic_reactants,
+    std::unique_ptr<UserPunch> const& user_punch,
     std::string const& project_file_name);
 }  // namespace PhreeqcIOData
 }  // namespace ChemistryLib
diff --git a/ChemistryLib/PhreeqcIOData/CreateUserPunch.cpp b/ChemistryLib/PhreeqcIOData/CreateUserPunch.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9fd59e131dacca96b98ca4bf5307e58aba2308d4
--- /dev/null
+++ b/ChemistryLib/PhreeqcIOData/CreateUserPunch.cpp
@@ -0,0 +1,56 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2019, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include <boost/optional/optional.hpp>
+
+#include "BaseLib/ConfigTree.h"
+#include "MeshLib/Mesh.h"
+#include "UserPunch.h"
+
+namespace ChemistryLib
+{
+namespace PhreeqcIOData
+{
+std::unique_ptr<UserPunch> createUserPunch(
+    boost::optional<BaseLib::ConfigTree> const& config,
+    MeshLib::Mesh const& mesh)
+{
+    if (!config)
+        return nullptr;
+
+    std::vector<SecondaryVariable> secondary_variables;
+    for (auto const& variable_name :
+         //! \ogs_file_param{prj__chemical_system__user_punch__headline}
+         config->getConfigParameter<std::vector<std::string>>("headline"))
+    {
+        auto value = MeshLib::getOrCreateMeshProperty<double>(
+            const_cast<MeshLib::Mesh&>(mesh),
+            variable_name,
+            MeshLib::MeshItemType::Node,
+            1);
+        std::fill(std::begin(*value),
+                  std::end(*value),
+                  std::numeric_limits<double>::quiet_NaN());
+
+        secondary_variables.emplace_back(variable_name, value);
+    }
+
+    std::vector<std::string> statements;
+    for (auto const& statement :
+         //! \ogs_file_param{prj__chemical_system__user_punch__statement}
+         config->getConfigParameterList<std::string>("statement"))
+    {
+        statements.emplace_back(statement);
+    }
+
+    return std::make_unique<UserPunch>(std::move(secondary_variables),
+                                       std::move(statements));
+}
+}  // namespace PhreeqcIOData
+}  // namespace ChemistryLib
diff --git a/ChemistryLib/PhreeqcIOData/CreateUserPunch.h b/ChemistryLib/PhreeqcIOData/CreateUserPunch.h
new file mode 100644
index 0000000000000000000000000000000000000000..48f3f7762b8a6438bbdfffb336ad88fc5f82220e
--- /dev/null
+++ b/ChemistryLib/PhreeqcIOData/CreateUserPunch.h
@@ -0,0 +1,35 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2019, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <boost/optional/optional_fwd.hpp>
+#include <memory>
+
+namespace BaseLib
+{
+class ConfigTree;
+}
+
+namespace MeshLib
+{
+class Mesh;
+}
+
+namespace ChemistryLib
+{
+namespace PhreeqcIOData
+{
+struct UserPunch;
+
+std::unique_ptr<UserPunch> createUserPunch(
+    boost::optional<BaseLib::ConfigTree> const& config,
+    MeshLib::Mesh const& mesh);
+}  // namespace PhreeqcIOData
+}  // namespace ChemistryLib
diff --git a/ChemistryLib/PhreeqcIOData/Output.h b/ChemistryLib/PhreeqcIOData/Output.h
index 69d1c808f0600d4a9112bd9ea5590ef8af6743f4..8b1322ecbde541803922ed55876dec8d32cf54d3 100644
--- a/ChemistryLib/PhreeqcIOData/Output.h
+++ b/ChemistryLib/PhreeqcIOData/Output.h
@@ -63,7 +63,8 @@ enum class ItemType
     pe,
     Component,
     EquilibriumPhase,
-    KineticReactant
+    KineticReactant,
+    SecondaryVariable
 };
 
 struct OutputItem
diff --git a/ChemistryLib/PhreeqcIOData/UserPunch.cpp b/ChemistryLib/PhreeqcIOData/UserPunch.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..07a4c736ba1b97b0748af9f789f735c3bff2f4d9
--- /dev/null
+++ b/ChemistryLib/PhreeqcIOData/UserPunch.cpp
@@ -0,0 +1,38 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2019, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "UserPunch.h"
+#include "BaseLib/ConfigTreeUtil.h"
+
+namespace ChemistryLib
+{
+namespace PhreeqcIOData
+{
+std::ostream& operator<<(std::ostream& os, UserPunch const& user_punch)
+{
+    os << "USER_PUNCH" << "\n";
+    os << "-headings ";
+    auto const& secondary_variables = user_punch.secondary_variables;
+    for (auto& secondary_variable : secondary_variables)
+        os << secondary_variable.name.c_str() << " ";
+    os << "\n";
+
+    os << "-start" << "\n";
+    int line_number = 1;
+    for (auto const& statement : user_punch.statements)
+    {
+        os << line_number << " " << statement.c_str() << "\n";
+        ++line_number;
+    }
+    os << "-end" << "\n";
+
+    return os;
+}
+}  // namespace PhreeqcIOData
+}  // namespace ChemistryLib
diff --git a/ChemistryLib/PhreeqcIOData/UserPunch.h b/ChemistryLib/PhreeqcIOData/UserPunch.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a1ceb425092a1e0cd4a2b0728c77fa4154dbf6a
--- /dev/null
+++ b/ChemistryLib/PhreeqcIOData/UserPunch.h
@@ -0,0 +1,56 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2019, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "MeshLib/PropertyVector.h"
+#include "Output.h"
+
+namespace BaseLib
+{
+class ConfigTree;
+}
+
+namespace ChemistryLib
+{
+namespace PhreeqcIOData
+{
+struct SecondaryVariable
+{
+    SecondaryVariable(std::string const name_,
+                      MeshLib::PropertyVector<double>* value_)
+        : name(name_), value(value_)
+    {
+    }
+
+    std::string const name;
+    MeshLib::PropertyVector<double>* value;
+    static const ItemType item_type = ItemType::SecondaryVariable;
+};
+
+struct UserPunch
+{
+    UserPunch(std::vector<SecondaryVariable>&& secondary_variables_,
+              std::vector<std::string>&& statements_)
+        : secondary_variables(secondary_variables_), statements(statements_)
+    {
+    }
+
+    friend std::ostream& operator<<(std::ostream& os,
+                                    UserPunch const& user_punch);
+
+    std::vector<SecondaryVariable> secondary_variables;
+    std::vector<std::string> const statements;
+};
+}  // namespace PhreeqcIOData
+}  // namespace ChemistryLib