diff --git a/modules/avalanche b/modules/avalanche
index fdf0f40d7a24c7b08c255ba2ee05ecbb847cc264..3e147ba27a59e6dc008c7d32cbebfd29f58b5a36 160000
--- a/modules/avalanche
+++ b/modules/avalanche
@@ -1 +1 @@
-Subproject commit fdf0f40d7a24c7b08c255ba2ee05ecbb847cc264
+Subproject commit 3e147ba27a59e6dc008c7d32cbebfd29f58b5a36
diff --git a/src/functionObjects/utilities/Make/files b/src/functionObjects/utilities/Make/files
index a274f616e1c035a8fa081082769e91b75a0b2e86..c64ef4e5319762f76902456707741c43930ffd06 100644
--- a/src/functionObjects/utilities/Make/files
+++ b/src/functionObjects/utilities/Make/files
@@ -2,6 +2,8 @@ abort/abort.C
 
 codedFunctionObject/codedFunctionObject.C
 
+areaWrite/areaWrite.C
+
 ensightWrite/ensightWrite.C
 ensightWrite/ensightWriteUpdate.C
 
diff --git a/src/functionObjects/utilities/Make/options b/src/functionObjects/utilities/Make/options
index 67eeb1ac213d684ab8bf74b3de222e478853d50c..5ba64ac1d682792bac27403a83afcd028d857d1e 100644
--- a/src/functionObjects/utilities/Make/options
+++ b/src/functionObjects/utilities/Make/options
@@ -5,6 +5,7 @@ EXE_INC = \
     -I$(LIB_SRC)/meshTools/lnInclude \
     -I$(LIB_SRC)/conversion/lnInclude \
     -I$(LIB_SRC)/sampling/lnInclude \
+    -I$(LIB_SRC)/surfMesh/lnInclude \
     -I$(LIB_SRC)/dynamicMesh/lnInclude \
     -I$(LIB_SRC)/ODE/lnInclude \
     -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \
@@ -16,6 +17,7 @@ LIB_LIBS = \
     -lmeshTools \
     -lconversion \
     -lsampling \
+    -lsurfMesh \
     -ldynamicMesh \
     -lfluidThermophysicalModels \
     -lcompressibleTransportModels \
diff --git a/src/functionObjects/utilities/areaWrite/areaWrite.C b/src/functionObjects/utilities/areaWrite/areaWrite.C
new file mode 100644
index 0000000000000000000000000000000000000000..9f44cf4be239a335c5c886b2fae3bba0b4979908
--- /dev/null
+++ b/src/functionObjects/utilities/areaWrite/areaWrite.C
@@ -0,0 +1,396 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "areaWrite.H"
+#include "polySurface.H"
+
+#include "fvMesh.H"
+#include "mapPolyMesh.H"
+#include "areaFields.H"
+#include "HashOps.H"
+#include "Time.H"
+#include "UIndirectList.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(areaWrite, 0);
+
+    addToRunTimeSelectionTable
+    (
+        functionObject,
+        areaWrite,
+        dictionary
+    );
+}
+
+Foam::scalar Foam::areaWrite::mergeTol_ = 1e-10;
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::areaWrite::areaWrite
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+:
+    functionObjects::fvMeshFunctionObject(name, runTime, dict),
+    loadFromFiles_(false),
+    verbose_(false),
+    outputPath_
+    (
+        time_.globalPath()/functionObject::outputPrefix/name
+    ),
+    selectAreas_(),
+    fieldSelection_(),
+    meshes_(),
+    surfaces_(nullptr),
+    writers_()
+{
+    outputPath_.clean();  // Remove unneeded ".."
+
+    read(dict);
+}
+
+
+Foam::areaWrite::areaWrite
+(
+    const word& name,
+    const objectRegistry& obr,
+    const dictionary& dict,
+    const bool loadFromFiles
+)
+:
+    functionObjects::fvMeshFunctionObject(name, obr, dict),
+    loadFromFiles_(loadFromFiles),
+    verbose_(false),
+    outputPath_
+    (
+        time_.globalPath()/functionObject::outputPrefix/name
+    ),
+    selectAreas_(),
+    fieldSelection_(),
+    meshes_(),
+    surfaces_(nullptr),
+    writers_()
+{
+    outputPath_.clean();  // Remove unneeded ".."
+
+    read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::areaWrite::verbose(const bool verbosity)
+{
+    verbose_ = verbosity;
+}
+
+
+bool Foam::areaWrite::read(const dictionary& dict)
+{
+    fvMeshFunctionObject::read(dict);
+
+    writers_.clear();
+    selectAreas_.clear();
+    fieldSelection_.clear();
+
+    surfaces_.reset
+    (
+        new objectRegistry
+        (
+            IOobject
+            (
+                "::areaWrite::",
+                obr_.time().constant(),
+                obr_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE,
+                false
+            )
+        )
+    );
+
+    verbose_ = dict.lookupOrDefault("verbose", false);
+
+    // All possible area meshes for the given fvMesh region
+    meshes_ = obr().lookupClass<faMesh>();
+
+    dict.readIfPresent("areas", selectAreas_);
+
+    if (selectAreas_.empty())
+    {
+        word areaName;
+        if (!dict.readIfPresent("area", areaName))
+        {
+            wordList available = obr().sortedNames<faMesh>();
+
+            if (available.size())
+            {
+                areaName = available.first();
+            }
+        }
+
+        if (!areaName.empty())
+        {
+            selectAreas_.resize(1);
+            selectAreas_.first() = areaName;
+        }
+    }
+
+    // Restrict to specified meshes
+    meshes_.filterKeys(selectAreas_);
+
+    dict.readEntry("fields", fieldSelection_);
+    fieldSelection_.uniq();
+
+
+    // Surface writer type and format options
+    const word writerType = dict.get<word>("surfaceFormat");
+
+    const dictionary writerOptions
+    (
+        dict.subOrEmptyDict("formatOptions").subOrEmptyDict(writerType)
+    );
+
+    for (const word& areaName : meshes_.keys())
+    {
+        // Define surface writer, but do NOT yet attach a surface
+
+        auto surfWriter = surfaceWriter::New(writerType, writerOptions);
+
+        // Use outputDir/TIME/surface-name
+        surfWriter->useTimeDir() = true;
+        surfWriter->verbose() = verbose_;
+
+        writers_.set(areaName, surfWriter);
+    }
+
+    // Ensure all surfaces and merge information are expired
+    expire();
+
+    return true;
+}
+
+
+bool Foam::areaWrite::execute()
+{
+    return true;
+}
+
+
+bool Foam::areaWrite::write()
+{
+    // Just needed for warnings
+    wordList allFields;
+    HashTable<wordHashSet> selected;
+    DynamicList<label> missed(fieldSelection_.size());
+
+
+    for (const word& areaName : meshes_.sortedToc())
+    {
+        const faMesh& areaMesh = *meshes_[areaName];
+
+        polySurface* surfptr = surfaces_->getObjectPtr<polySurface>(areaName);
+
+        if (!surfptr)
+        {
+            // Construct null and add to registry (owned by registry)
+            surfptr = new polySurface(areaName, *surfaces_, true);
+        }
+
+        pointField pts(areaMesh.patch().localPoints());
+        faceList fcs(areaMesh.patch().localFaces());
+
+        // Copy in geometry
+        surfptr->transfer(std::move(pts), std::move(fcs));
+
+        surfaceWriter& outWriter = *writers_[areaName];
+
+        if (outWriter.needsUpdate())
+        {
+            outWriter.setSurface(*surfptr);
+        }
+
+
+        // Determine the per-surface number of fields
+        // Only seems to be needed for VTK legacy
+
+        selected.clear();
+
+        const IOobjectList objects(areaMesh.thisDb(), obr_.time().timeName());
+
+        if (loadFromFiles_)
+        {
+            allFields = objects.names();
+            selected = objects.classes(fieldSelection_);
+        }
+        else
+        {
+            // Check currently available fields
+            allFields = areaMesh.thisDb().names();
+            selected = areaMesh.thisDb().classes(fieldSelection_);
+        }
+
+        if (Pstream::parRun())
+        {
+            Pstream::mapCombineGather(selected, HashSetOps::plusEqOp<word>());
+            Pstream::mapCombineScatter(selected);
+        }
+
+        missed.clear();
+
+        // Detect missing fields
+        forAll(fieldSelection_, i)
+        {
+            if (findStrings(fieldSelection_[i], allFields).empty())
+            {
+                missed.append(i);
+            }
+        }
+
+        if (missed.size())
+        {
+            WarningInFunction
+                << nl
+                << "Cannot find "
+                << (loadFromFiles_ ? "field file" : "registered field")
+                << " matching "
+                << UIndirectList<wordRe>(fieldSelection_, missed) << endl;
+        }
+
+
+        // Currently only support area field types
+        label nAreaFields = 0;
+
+        forAllConstIters(selected, iter)
+        {
+            const word& clsName = iter.key();
+            const label n = iter.val().size();
+
+            if (fieldTypes::area.found(clsName))
+            {
+                nAreaFields += n;
+            }
+        }
+
+
+        // Propagate field counts (per surface)
+        outWriter.nFields() = nAreaFields;
+
+
+        // Begin writing
+
+        outWriter.open(outputPath_/areaName);
+
+        outWriter.beginTime(obr_.time());
+
+        // Write fields
+
+        performAction<areaScalarField>(outWriter, areaMesh, objects);
+        performAction<areaVectorField>(outWriter, areaMesh, objects);
+        performAction<areaSphericalTensorField>(outWriter, areaMesh, objects);
+        performAction<areaSymmTensorField>(outWriter, areaMesh, objects);
+        performAction<areaTensorField>(outWriter, areaMesh, objects);
+
+        // Finish this time step
+
+        // Write geometry if no fields were written so that we still
+        // can have something to look at
+
+        if (!outWriter.wroteData())
+        {
+            outWriter.write();
+        }
+
+        outWriter.endTime();
+    }
+
+    return true;
+}
+
+
+void Foam::areaWrite::expire()
+{
+    surfaces_->clear();
+
+    // Dimension as fraction of mesh bounding box
+    const scalar mergeDim = mergeTol_ * mesh_.bounds().mag();
+
+    forAllIters(writers_, iter)
+    {
+        surfaceWriter& writer = *(iter.val());
+        writer.expire();
+        writer.mergeDim() = mergeDim;
+    }
+}
+
+
+void Foam::areaWrite::updateMesh(const mapPolyMesh& mpm)
+{
+    if (&mpm.mesh() == &mesh_)
+    {
+        expire();
+    }
+}
+
+
+void Foam::areaWrite::movePoints(const polyMesh& mesh)
+{
+    if (&mesh == &mesh_)
+    {
+        expire();
+    }
+}
+
+
+void Foam::areaWrite::readUpdate(const polyMesh::readUpdateState state)
+{
+    if (state != polyMesh::UNCHANGED)
+    {
+        expire();
+    }
+}
+
+
+Foam::scalar Foam::areaWrite::mergeTol()
+{
+    return mergeTol_;
+}
+
+
+Foam::scalar Foam::areaWrite::mergeTol(const scalar tol)
+{
+    const scalar prev(mergeTol_);
+    mergeTol_ = tol;
+    return prev;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/utilities/areaWrite/areaWrite.H b/src/functionObjects/utilities/areaWrite/areaWrite.H
new file mode 100644
index 0000000000000000000000000000000000000000..4c200538e7727003052ec542634cc293cbf64515
--- /dev/null
+++ b/src/functionObjects/utilities/areaWrite/areaWrite.H
@@ -0,0 +1,249 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::areaWrite
+
+Description
+    Write finite area mesh/fields to standard output formats.
+
+    Example of function object specification:
+
+    \verbatim
+    surfaces
+    {
+        type    areaWrite;
+        libs    ("libutilityFunctionObjects.so");
+
+        // Write at same frequency as fields
+        writeControl    outputTime;
+        writeInterval   1;
+
+        // Fields to be sampled
+        fields          (p U);
+
+        // Output surface format
+        surfaceFormat   vtk;
+
+        formatOptions
+        {
+            vtk
+            {
+                precision  10;
+            }
+        }
+    }
+    \endverbatim
+
+    Entries:
+    \table
+        Property | Description                              | Required | Default
+        type    | Type-name: \c areaWrite                   | yes |
+        region  | name for a single region                  | no  | region0
+        area    | select a single area                      | no  |
+        areas   | wordRe list of multiple areas             | no  |
+        fields  | wordRe list of fields                     | yes |
+        surfaceFormat | output surface format               | yes |
+        formatOptions | dictionary of format options        | no  |
+    \endtable
+
+SourceFiles
+    areaWrite.C
+    areaWriteTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef areaWrite_H
+#define areaWrite_H
+
+#include "fvMeshFunctionObject.H"
+#include "polyMesh.H"
+#include "areaFieldsFwd.H"
+#include "surfaceWriter.H"
+#include "HashPtrTable.H"
+#include "IOobjectList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class faMesh;
+class polySurface;
+
+/*---------------------------------------------------------------------------*\
+                          Class areaWrite Declaration
+\*---------------------------------------------------------------------------*/
+
+class areaWrite
+:
+    public functionObjects::fvMeshFunctionObject
+{
+    // Static Data Members
+
+        //- Tolerance for merging points (fraction of mesh bounding box)
+        static scalar mergeTol_;
+
+
+    // Private Data
+
+        //- Load fields from files (not from objectRegistry)
+        const bool loadFromFiles_;
+
+        //- Output verbosity
+        bool verbose_;
+
+        //- Output path
+        fileName outputPath_;
+
+
+    // Read from dictionary
+
+        //- Names of areas to select
+        wordRes selectAreas_;
+
+        //- Names of fields to write
+        wordRes fieldSelection_;
+
+        //- Pointers to the requested mesh regions
+        HashTable<const faMesh*> meshes_;
+
+        //- Hold intermediate surfaces.
+        //  The faMesh has an indirect face list but we require real ones.
+        autoPtr<objectRegistry> surfaces_;
+
+
+    // Output control
+
+        //- Surface writers (one per surface)
+        HashPtrTable<surfaceWriter> writers_;
+
+
+    // Private Member Functions
+
+        //- Write fieldName on surface and on outputDir path
+        template<class Type>
+        void writeSurface
+        (
+            surfaceWriter& writer,
+            const Field<Type>& values,
+            const word& fieldName
+        );
+
+        //- Write all applicable fields
+        template<class GeoField>
+        void performAction
+        (
+            surfaceWriter& writer,
+            const faMesh& areaMesh,
+            const IOobjectList& objects
+        );
+
+        //- Mark intermediate surfaces and writers as needing an update.
+        void expire();
+
+        //- No copy construct
+        areaWrite(const areaWrite&) = delete;
+
+        //- No copy assignment
+        void operator=(const areaWrite&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("areaWrite");
+
+
+    // Constructors
+
+        //- Construct from Time and dictionary
+        areaWrite
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict
+        );
+
+        //- Construct for given objectRegistry and dictionary
+        //  allow the possibility to load fields from files
+        areaWrite
+        (
+            const word& name,
+            const objectRegistry& obr,
+            const dictionary& dict,
+            const bool loadFromFiles = false
+        );
+
+
+    //- Destructor
+    virtual ~areaWrite() = default;
+
+
+    // Member Functions
+
+        //- Set verbosity level
+        void verbose(const bool verbosity = true);
+
+        //- Read the areaWrite dictionary
+        virtual bool read(const dictionary& dict);
+
+        //- Execute, currently does nothing
+        virtual bool execute();
+
+        //- Sample and write
+        virtual bool write();
+
+        //- Update for changes of mesh - expires the surfaces
+        virtual void updateMesh(const mapPolyMesh& mpm);
+
+        //- Update for mesh point-motion - expires the surfaces
+        virtual void movePoints(const polyMesh& mesh);
+
+        //- Update for changes of mesh due to readUpdate - expires the surfaces
+        virtual void readUpdate(const polyMesh::readUpdateState state);
+
+        //- Get merge tolerance
+        static scalar mergeTol();
+
+        //- Set merge tolerance and return old value
+        static scalar mergeTol(const scalar tol);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "areaWriteTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/utilities/areaWrite/areaWriteTemplates.C b/src/functionObjects/utilities/areaWrite/areaWriteTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..c2e905fae44348292e64d73673ba896b2f66569a
--- /dev/null
+++ b/src/functionObjects/utilities/areaWrite/areaWriteTemplates.C
@@ -0,0 +1,108 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "areaWrite.H"
+#include "areaFields.H"
+#include "faMesh.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+void Foam::areaWrite::writeSurface
+(
+    surfaceWriter& writer,
+    const Field<Type>& values,
+    const word& fieldName
+)
+{
+    fileName outputName = writer.write(fieldName, values);
+
+    // Case-local file name with "<case>" to make relocatable
+
+    dictionary propsDict;
+    propsDict.add
+    (
+        "file",
+        time_.relativePath(outputName, true)
+    );
+    setProperty(fieldName, propsDict);
+}
+
+
+template<class GeoField>
+void Foam::areaWrite::performAction
+(
+    surfaceWriter& writer,
+    const faMesh& areaMesh,
+    const IOobjectList& objects
+)
+{
+    wordList fieldNames;
+    if (loadFromFiles_)
+    {
+        fieldNames = objects.sortedNames<GeoField>(fieldSelection_);
+    }
+    else
+    {
+        fieldNames = areaMesh.thisDb().sortedNames<GeoField>(fieldSelection_);
+    }
+
+    for (const word& fieldName : fieldNames)
+    {
+        if (verbose_)
+        {
+            Info<< "write: " << fieldName << endl;
+        }
+
+        if (loadFromFiles_)
+        {
+            const GeoField fld
+            (
+                IOobject
+                (
+                    fieldName,
+                    time_.timeName(),
+                    areaMesh.thisDb(),
+                    IOobject::MUST_READ
+                ),
+                areaMesh
+            );
+
+            writeSurface(writer, fld, fieldName);
+        }
+        else
+        {
+            writeSurface
+            (
+                writer,
+                areaMesh.thisDb().lookupObject<GeoField>(fieldName),
+                fieldName
+            );
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/areaWrite b/tutorials/finiteArea/surfactantFoam/planeTransport/system/areaWrite
new file mode 100644
index 0000000000000000000000000000000000000000..0b7aec7ca74d74b063e1d8470ec54a27488106f6
--- /dev/null
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/areaWrite
@@ -0,0 +1,29 @@
+// -*- C++ -*-
+// Use the areaWrite function object
+
+areaWrite
+{
+    type    areaWrite;
+    libs    ("libutilityFunctionObjects.so");
+    log     true;
+
+    writeControl    writeTime;
+    writeInterval   1;
+
+    // Fields to output (words or regex)
+    fields  (".*");
+
+    surfaceFormat ensight;
+    // surfaceFormat vtk;
+
+    formatOptions
+    {
+        vtk
+        {
+            format  ascii;
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/controlDict b/tutorials/finiteArea/surfactantFoam/planeTransport/system/controlDict
index 2014bf8f3efd55f784cae9b77d43008c1e2418dd..30a865538d42545e74515e24e9e8cf3e550e7518 100644
--- a/tutorials/finiteArea/surfactantFoam/planeTransport/system/controlDict
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/controlDict
@@ -43,6 +43,9 @@ timePrecision       6;
 
 runTimeModifiable   yes;
 
+functions
+{
+    #include "areaWrite"
+}
 
 // ************************************************************************* //
-