diff --git a/dumux/freeflow/staggered/vtkoutputfields.hh b/dumux/freeflow/staggered/vtkoutputfields.hh
index e4273b45348d1fe2dd518cc37adb95daea289410..8e848827ad2ca35d4441da2ddb3e489268ac58b3 100644
--- a/dumux/freeflow/staggered/vtkoutputfields.hh
+++ b/dumux/freeflow/staggered/vtkoutputfields.hh
@@ -23,7 +23,8 @@
 #ifndef DUMUX_NAVIER_STOKES_VTK_OUTPUT_FIELDS_HH
 #define DUMUX_NAVIER_STOKES_VTK_OUTPUT_FIELDS_HH
 
-#include <dumux/common/basicproperties.hh>
+#include <dumux/common/properties.hh>
+#include <dune/common/fvector.hh>
 
 namespace Dumux
 {
@@ -36,13 +37,35 @@ template<class TypeTag>
 class NavierStokesVtkOutputFields
 {
     using Indices = typename GET_PROP_TYPE(TypeTag, Indices);
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
     using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
+    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+    using FaceVariables = typename GET_PROP_TYPE(TypeTag, FaceVariables);
+
+    using GlobalPosition = Dune::FieldVector<Scalar, GridView::dimensionworld>;
+
 public:
     template <class VtkOutputModule>
     static void init(VtkOutputModule& vtk)
     {
         vtk.addVolumeVariable([](const VolumeVariables& v){ return v.pressure(); }, "p");
 
+        const bool writeFaceVars_ = getParamFromGroup<bool>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Vtk.WriteFaceData", false);
+        if(writeFaceVars_)
+        {
+            vtk.addFaceVariable([](const FaceVariables& f){ return f.velocitySelf(); }, "scalarFaceVelocity");
+
+            auto faceVelocityVector = [](const SubControlVolumeFace& scvf, const FaceVariables& f)
+                                      {
+                                          GlobalPosition velocity(0.0);
+                                          velocity[scvf.directionIndex()] = f.velocitySelf();
+                                          return velocity;
+                                      };
+
+            vtk.addFaceVariable(faceVelocityVector, "vectorFaceVelocity");
+        }
+
         if(GET_PROP_VALUE(TypeTag, EnableEnergyBalance))
             vtk.addVolumeVariable( [](const VolumeVariables& v){ return v.temperature(); },"temperature");
     }
diff --git a/dumux/io/staggeredvtkoutputmodule.hh b/dumux/io/staggeredvtkoutputmodule.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a531c138196dcaa90f640b279dfa6799b5315372
--- /dev/null
+++ b/dumux/io/staggeredvtkoutputmodule.hh
@@ -0,0 +1,232 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program is free software: you can redistribute it and/or modify    *
+ *   it under the terms of the GNU General Public License as published by    *
+ *   the Free Software Foundation, either version 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program is distributed in the hope that it will be useful,         *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
+ *   GNU General Public License for more details.                            *
+ *                                                                           *
+ *   You should have received a copy of the GNU General Public License       *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \brief A VTK output module to simplify writing dumux simulation data to VTK format. Specialization for staggered grids with dofs on faces.
+ */
+#ifndef STAGGERED_VTK_OUTPUT_MODULE_HH
+#define STAGGERED_VTK_OUTPUT_MODULE_HH
+
+#include <dune/common/fvector.hh>
+
+#include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/pointcloudvtkwriter.hh>
+#include <dumux/io/vtksequencewriter.hh>
+
+
+namespace Dumux
+{
+
+/*!
+ * \ingroup InputOutput
+ * \brief A VTK output module to simplify writing dumux simulation data to VTK format
+ *        Specialization for staggered grids with dofs on faces.
+ */
+template<typename TypeTag>
+class StaggeredVtkOutputModule : public VtkOutputModule<TypeTag>
+{
+    friend class VtkOutputModule<TypeTag>;
+    using ParentType = VtkOutputModule<TypeTag>;
+    using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
+    using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using FaceVariables = typename GET_PROP_TYPE(TypeTag, FaceVariables);
+    using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace);
+
+
+    enum { dim = GridView::dimension };
+    enum { dimWorld = GridView::dimensionworld };
+
+    using GlobalPosition = Dune::FieldVector<Scalar, dimWorld>;
+
+    using DofTypeIndices = typename GET_PROP(TypeTag, DofTypeIndices);
+    typename DofTypeIndices::CellCenterIdx cellCenterIdx;
+    typename DofTypeIndices::FaceIdx faceIdx;
+
+    struct PriVarScalarDataInfo { unsigned int pvIdx; std::string name; };
+    struct PriVarVectorDataInfo { std::vector<unsigned int> pvIdx; std::string name; };
+
+    struct FaceVarScalarDataInfo { std::function<Scalar(const FaceVariables&)> get; std::string name; };
+    struct FaceVarVectorDataInfo { std::function<GlobalPosition(const SubControlVolumeFace& scvf, const FaceVariables&)> get; std::string name; };
+
+    using Positions = std::vector<Scalar>;
+    using Data = std::vector<std::vector<Scalar>>;
+
+public:
+
+    StaggeredVtkOutputModule(const Problem& problem,
+                    const FVGridGeometry& fvGridGeometry,
+                    const GridVariables& gridVariables,
+                    const SolutionVector& sol,
+                    const std::string& name,
+                    bool verbose = true,
+                    Dune::VTK::DataMode dm = Dune::VTK::conforming)
+    : ParentType(problem, fvGridGeometry, gridVariables, sol, name, verbose, dm)
+    , problem_(problem)
+    , gridGeom_(fvGridGeometry)
+    , gridVariables_(gridVariables)
+    , sol_(sol)
+    , faceWriter_(std::make_shared<PointCloudVtkWriter<Scalar, dim>>(coordinates_))
+    , sequenceWriter_(faceWriter_, problem.name() + "-face", "","",
+                      fvGridGeometry.gridView().comm().rank(),
+                      fvGridGeometry.gridView().comm().size() )
+
+    {
+        writeFaceVars_ = getParamFromGroup<bool>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Vtk.WriteFaceData", false);
+        coordinatesInitialized_ = false;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    //! Methods to conveniently add primary and secondary variables upon problem initialization
+    //! Do not call these methods after initialization
+    //////////////////////////////////////////////////////////////////////////////////////////////
+
+    template<typename Vector>
+    void addFaceField(const Vector& v, const std::string& name, int nComp = 1)
+    {
+        if (v.size() == this->gridGeom_.gridView().size(1))
+            int x;
+            // fields_.emplace_back(gridGeom_.gridView(), gridGeom_.elementMapper(), v, name, nComp, 0);
+        else
+            DUNE_THROW(Dune::RangeError, "Size mismatch of added field!");
+    }
+
+    void addFaceVariable(std::function<Scalar(const FaceVariables&)>&& f, const std::string& name)
+    {
+        faceVarScalarDataInfo_.push_back(FaceVarScalarDataInfo{f, name});
+    }
+
+    void addFaceVariable(std::function<GlobalPosition(const SubControlVolumeFace& scvf, const FaceVariables&)>&& f, const std::string& name)
+    {
+        faceVarVectorDataInfo_.push_back(FaceVarVectorDataInfo{f, name});
+    }
+
+
+    void write(double time, Dune::VTK::OutputType type = Dune::VTK::ascii)
+    {
+        ParentType::write(time, type);
+        if(writeFaceVars_)
+            getFaceDataAndWrite_(time);
+    }
+
+
+private:
+
+    void updateCoordinates_()
+    {
+        coordinates_.resize(gridGeom_.numFaceDofs());
+        for(auto&& facet : facets(gridGeom_.gridView()))
+        {
+            const int dofIdxGlobal = gridGeom_.gridView().indexSet().index(facet);
+            coordinates_[dofIdxGlobal] = facet.geometry().center();
+        }
+        coordinatesInitialized_ = true;
+    }
+
+     /*!
+     * \brief Gathers all face-related data and invokes the face vtk-writer using these data.
+     */
+    void getFaceDataAndWrite_(const Scalar time)
+    {
+        const auto numPoints = gridGeom_.numFaceDofs();
+
+        // make sure not to iterate over the same dofs twice
+        std::vector<bool> dofVisited(numPoints, false);
+
+        // get fields for all primary coordinates and variables
+        if(!coordinatesInitialized_)
+            updateCoordinates_();
+
+        std::vector<std::vector<Scalar>> faceVarScalarData;
+        std::vector<std::vector<GlobalPosition>> faceVarVectorData;
+
+        if(!faceVarScalarDataInfo_.empty())
+            faceVarScalarData.resize(faceVarScalarDataInfo_.size(), std::vector<Scalar>(numPoints));
+
+        if(!faceVarVectorDataInfo_.empty())
+            faceVarVectorData.resize(faceVarVectorDataInfo_.size(), std::vector<GlobalPosition>(numPoints));
+
+        for (const auto& element : elements(gridGeom_.gridView(), Dune::Partitions::interior))
+        {
+            auto fvGeometry = localView(gridGeom_);
+            auto elemFaceVars = localView(gridVariables_.curGridFaceVars());
+
+            if (!faceVarScalarDataInfo_.empty() || !faceVarVectorDataInfo_.empty())
+            {
+                fvGeometry.bind(element);
+                elemFaceVars.bindElement(element, fvGeometry, sol_);
+
+                for (auto&& scvf : scvfs(fvGeometry))
+                {
+                    const auto dofIdxGlobal = scvf.dofIndex();
+                    if(dofVisited[dofIdxGlobal])
+                        continue;
+
+                    dofVisited[dofIdxGlobal] = true;
+
+                    const auto& faceVars = elemFaceVars[scvf];
+
+                    for (std::size_t i = 0; i < faceVarScalarDataInfo_.size(); ++i)
+                        faceVarScalarData[i][dofIdxGlobal] = faceVarScalarDataInfo_[i].get(faceVars);
+
+                    for (std::size_t i = 0; i < faceVarVectorDataInfo_.size(); ++i)
+                            faceVarVectorData[i][dofIdxGlobal] = faceVarVectorDataInfo_[i].get(scvf, faceVars);
+                }
+            }
+        }
+
+        if(!faceVarScalarDataInfo_.empty())
+            for (std::size_t i = 0; i < faceVarScalarDataInfo_.size(); ++i)
+                faceWriter_->addPointData(faceVarScalarData[i], faceVarScalarDataInfo_[i].name);
+
+        if(!faceVarVectorDataInfo_.empty())
+            for (std::size_t i = 0; i < faceVarVectorDataInfo_.size(); ++i)
+                faceWriter_->addPointData(faceVarVectorData[i], faceVarVectorDataInfo_[i].name, 3);
+
+        sequenceWriter_.write(time);
+        // faceScalarFields_.clear();
+        // faceVectorFields_.clear();
+    }
+
+
+    const Problem& problem_;
+    const FVGridGeometry& gridGeom_;
+    const GridVariables& gridVariables_;
+    const SolutionVector& sol_;
+
+    std::shared_ptr<PointCloudVtkWriter<Scalar, dim>> faceWriter_;
+
+    VTKSequenceWriter<PointCloudVtkWriter<Scalar, dim>> sequenceWriter_;
+
+    bool writeFaceVars_;
+
+    std::vector<GlobalPosition> coordinates_;
+    bool coordinatesInitialized_;
+
+    std::vector<FaceVarScalarDataInfo> faceVarScalarDataInfo_;
+    std::vector<FaceVarVectorDataInfo> faceVarVectorDataInfo_;
+
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/test/freeflow/staggered/test_donea.cc b/test/freeflow/staggered/test_donea.cc
index b3afe34f9627861b2836c03668fafb7a99b78a57..70ba090206f6f734e5d50991475336c0119c38fb 100644
--- a/test/freeflow/staggered/test_donea.cc
+++ b/test/freeflow/staggered/test_donea.cc
@@ -53,6 +53,7 @@
 #include <dumux/discretization/methods.hh>
 
 #include <dumux/io/vtkoutputmodule.hh>
+#include <dumux/io/staggeredvtkoutputmodule.hh>
 
 /*!
  * \brief Provides an interface for customizing error messages associated with
@@ -157,7 +158,7 @@ int main(int argc, char** argv) try
 
     // intialize the vtk output module
     using VtkOutputFields = typename GET_PROP_TYPE(TypeTag, VtkOutputFields);
-    VtkOutputModule<TypeTag> vtkWriter(*problem, *fvGridGeometry, *gridVariables, x, problem->name());
+    StaggeredVtkOutputModule<TypeTag> vtkWriter(*problem, *fvGridGeometry, *gridVariables, x, problem->name());
     VtkOutputFields::init(vtkWriter); //! Add model specific output fields
     vtkWriter.addField(problem->getAnalyticalPressureSolution(), "pressureExact", 1);
     vtkWriter.addField(problem->getAnalyticalVelocitySolution(), "velocityExact", GridView::dimensionworld);