diff --git a/src/fileFormats/vtk/core/foamVtkCore.C b/src/fileFormats/vtk/core/foamVtkCore.C
index 62612deb57a46b245c1bc909af23f752622bfaa4..6b8ab91b9306fc00b6e40967692fa9ebc0cb872b 100644
--- a/src/fileFormats/vtk/core/foamVtkCore.C
+++ b/src/fileFormats/vtk/core/foamVtkCore.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2017-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -46,6 +46,7 @@ Foam::vtk::fileTagNames
     { fileTag::POINT_DATA, "PointData" },
     { fileTag::POLY_DATA, "PolyData" },
     { fileTag::UNSTRUCTURED_GRID, "UnstructuredGrid" },
+    { fileTag::MULTI_BLOCK, "vtkMultiBlockDataSet" },
 };
 
 
diff --git a/src/fileFormats/vtk/core/foamVtkCore.H b/src/fileFormats/vtk/core/foamVtkCore.H
index d209d60c9315ac608c86e34d839dd7f495b23697..8584c9672ceebb8655b6c6403ffcb5ccb85b3d81 100644
--- a/src/fileFormats/vtk/core/foamVtkCore.H
+++ b/src/fileFormats/vtk/core/foamVtkCore.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -115,6 +115,7 @@ namespace vtk
         POINT_DATA,             //!< "PointData"
         POLY_DATA,              //!< "PolyData"
         UNSTRUCTURED_GRID,      //!< "UnstructuredGrid"
+        MULTI_BLOCK,            //!< "vtkMultiBlockDataSet"
     };
 
     //- Strings corresponding to the vtk xml tags
diff --git a/src/fileFormats/vtk/output/foamVtkOutput.C b/src/fileFormats/vtk/output/foamVtkOutput.C
index 5a54c2241ba43e8ea8d480b0f754b7c3e9993979..2ee7dbd7a57d95061cd1b267e7227d0caa6e4a39 100644
--- a/src/fileFormats/vtk/output/foamVtkOutput.C
+++ b/src/fileFormats/vtk/output/foamVtkOutput.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -33,6 +33,7 @@ License
 #include "foamVtkLegacyAsciiFormatter.H"
 #include "foamVtkLegacyRawFormatter.H"
 #include "typeInfo.H"
+#include "instant.H"
 
 // * * * * * * * * * * * * * * * Static Data * * * * * * * * * * * * * * * * //
 
@@ -101,19 +102,52 @@ Foam::vtk::newFormatter
 }
 
 
+void Foam::vtk::writeSeries
+(
+    Ostream& os,
+    const word& prefix,
+    const word& suffix,
+    const UList<instant>& series
+)
+{
+    // Begin file-series (JSON)
+    os  << "{\n  \"file-series-version\" : \"1.0\",\n  \"files\" : [\n";
+
+    // Track how many entries are remaining
+    // - trailing commas on all but the final entry (JSON requirement)
+    label nremain = series.size();
+
+    // Each entry
+    //   { "name" : "<prefix>name<suffix>", "time" : value }
+
+    for (const instant& inst : series)
+    {
+        os  << "    { \"name\" : \""
+            << prefix << inst.name() << suffix
+            << "\", \"time\" : " << inst.value() << " }";
+
+        if (--nremain)
+        {
+            os  << ',';
+        }
+        os  << nl;
+    }
+
+    os  << "  ]\n}\n";
+}
+
+
 Foam::label Foam::vtk::writeVtmFile
 (
     std::ostream& os,
     const UList<fileName>& files
 )
 {
-    const word& content = "vtkMultiBlockDataSet";
-
     asciiFormatter vtmFile(os);
 
     vtmFile
         .xmlHeader()
-        .beginVTKFile(content, "1.0");
+        .beginVTKFile(fileTagNames[vtk::fileTag::MULTI_BLOCK], "1.0");
 
     forAll(files, i)
     {
@@ -124,7 +158,7 @@ Foam::label Foam::vtk::writeVtmFile
             .closeTag(true);
     }
 
-    vtmFile.endTag(content).endVTKFile();
+    vtmFile.endTag(fileTagNames[vtk::fileTag::MULTI_BLOCK]).endVTKFile();
 
     return files.size();
 }
diff --git a/src/fileFormats/vtk/output/foamVtkOutput.H b/src/fileFormats/vtk/output/foamVtkOutput.H
index 4a71b5208d310885e04cfcfc5a14e42cd1f99906..55f31cd91fca4baf85ed5a48a3ade1cc5e137f55 100644
--- a/src/fileFormats/vtk/output/foamVtkOutput.H
+++ b/src/fileFormats/vtk/output/foamVtkOutput.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -54,6 +54,9 @@ SourceFiles
 
 namespace Foam
 {
+// Forward declarations
+class instant;
+
 namespace vtk
 {
 
@@ -73,6 +76,19 @@ namespace vtk
     );
 
 
+    //- Write file series (JSON format) for specified time instances
+    //
+    //  \param prefix before the \c instant.name()
+    //  \param suffix after the \c instant.name()
+    //  \param series the list of name/value entries
+    void writeSeries
+    (
+        Ostream& os,
+        const word& prefix,
+        const word& suffix,
+        const UList<instant>& series
+    );
+
     //- Write vtm datasets for specified files
     label writeVtmFile(std::ostream& os, const UList<fileName>& files);
 
diff --git a/src/functionObjects/lagrangian/Make/files b/src/functionObjects/lagrangian/Make/files
index 93227fe85df0ffb5c1a640e303d548fb87e7e06f..921ca2d707fb5ad2be880af88a27b2b1a6b2941a 100644
--- a/src/functionObjects/lagrangian/Make/files
+++ b/src/functionObjects/lagrangian/Make/files
@@ -2,4 +2,6 @@ cloudInfo/cloudInfo.C
 icoUncoupledKinematicCloud/icoUncoupledKinematicCloud.C
 dsmcFields/dsmcFields.C
 
+vtkCloud/vtkCloud.C
+
 LIB = $(FOAM_LIBBIN)/liblagrangianFunctionObjects
diff --git a/src/functionObjects/lagrangian/Make/options b/src/functionObjects/lagrangian/Make/options
index 7a7bf38ba33656c867a1948e6553d05e1e249cf4..e54a3f92866dec616395219507a5a4bd0ec7ed80 100644
--- a/src/functionObjects/lagrangian/Make/options
+++ b/src/functionObjects/lagrangian/Make/options
@@ -2,6 +2,8 @@ EXE_INC = \
     -I$(LIB_SRC)/finiteVolume/lnInclude \
     -I$(LIB_SRC)/transportModels \
     -I$(LIB_SRC)/transportModels/incompressible/singlePhaseTransportModel \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/conversion/lnInclude \
     -I$(LIB_SRC)/meshTools/lnInclude \
     -I$(LIB_SRC)/lagrangian/basic/lnInclude \
     -I$(LIB_SRC)/lagrangian/intermediate/lnInclude \
@@ -12,6 +14,7 @@ EXE_INC = \
 LIB_LIBS = \
     -lfiniteVolume \
     -lincompressibleTransportModels \
+    -lconversion \
     -lmeshTools \
     -llagrangian \
     -llagrangianIntermediate \
diff --git a/src/functionObjects/lagrangian/vtkCloud/vtkCloud.C b/src/functionObjects/lagrangian/vtkCloud/vtkCloud.C
new file mode 100644
index 0000000000000000000000000000000000000000..cb6f4571ed178a783e3769b790a04328792a1444
--- /dev/null
+++ b/src/functionObjects/lagrangian/vtkCloud/vtkCloud.C
@@ -0,0 +1,457 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 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 "vtkCloud.H"
+#include "Cloud.H"
+#include "dictionary.H"
+#include "fvMesh.H"
+#include "foamVtkOutputOptions.H"
+#include "addToRunTimeSelectionTable.H"
+#include "pointList.H"
+#include "stringOps.H"
+#include <fstream>
+
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+    defineTypeNameAndDebug(vtkCloud, 0);
+    addToRunTimeSelectionTable(functionObject, vtkCloud, dictionary);
+}
+}
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::functionObjects::vtkCloud::writeVerts
+(
+    autoPtr<vtk::formatter>& format,
+    const label nParcels
+) const
+{
+    if (Pstream::master())
+    {
+        format().tag(vtk::fileTag::VERTS);
+
+        // Same payload throughout
+        const uint64_t payLoad = (nParcels * sizeof(label));
+
+        //
+        // 'connectivity'
+        // = linear mapping onto points
+        //
+        {
+            format().openDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY)
+                .closeTag();
+
+            format().writeSize(payLoad);
+            for (label i=0; i < nParcels; ++i)
+            {
+                format().write(i);
+            }
+            format().flush();
+
+            format().endDataArray();
+        }
+
+        //
+        // 'offsets'  (connectivity offsets)
+        // = linear mapping onto points (with 1 offset)
+        //
+        {
+            format().openDataArray<label>(vtk::dataArrayAttr::OFFSETS)
+                .closeTag();
+
+            format().writeSize(payLoad);
+            for (label i=0; i < nParcels; ++i)
+            {
+                format().write(i+1);
+            }
+            format().flush();
+
+            format().endDataArray();
+        }
+
+        format().endTag(vtk::fileTag::VERTS);
+    }
+}
+
+
+bool Foam::functionObjects::vtkCloud::writeCloud
+(
+    const fileName& outputName,
+    const word& cloudName
+) const
+{
+    const auto* objPtr = mesh_.lookupObjectPtr<cloud>(cloudName);
+    if (!objPtr)
+    {
+        return false;
+    }
+
+    objectRegistry obrTmp
+    (
+        IOobject
+        (
+            "vtk::vtkCloud::" + cloudName,
+            mesh_.time().constant(),
+            mesh_,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE,
+            false
+        )
+    );
+
+    objPtr->writeObjects(obrTmp);
+
+    const auto* pointsPtr = obrTmp.lookupObjectPtr<vectorField>("position");
+
+    if (!pointsPtr)
+    {
+        // This should be impossible
+        return false;
+    }
+
+    // Total number of parcels on all processes
+    label nTotParcels = pointsPtr->size();
+    reduce(nTotParcels, sumOp<label>());
+
+    if (!nTotParcels)
+    {
+        return false;
+    }
+
+    std::ofstream os;
+    autoPtr<vtk::formatter> format;
+
+    // Header
+    if (Pstream::master())
+    {
+        os.open(outputName);
+        format = writeOpts_.newFormatter(os);
+
+        // XML (inline)
+        format()
+            .xmlHeader()
+            .xmlComment
+            (
+                "cloud=" + cloudName
+              + " time=" + time_.timeName()
+              + " index=" + Foam::name(time_.timeIndex())
+            )
+            .beginVTKFile(vtk::fileTag::POLY_DATA, "0.1");
+
+        // Begin piece
+        if (useVerts_)
+        {
+            format()
+                .openTag(vtk::fileTag::PIECE)
+                .xmlAttr(vtk::fileAttr::NUMBER_OF_POINTS, nTotParcels)
+                .xmlAttr(vtk::fileAttr::NUMBER_OF_VERTS, nTotParcels)
+                .closeTag();
+        }
+        else
+        {
+            format()
+                .openTag(vtk::fileTag::PIECE)
+                .xmlAttr(vtk::fileAttr::NUMBER_OF_POINTS, nTotParcels)
+                .closeTag();
+        }
+    }
+
+
+    // Points
+    if (Pstream::master())
+    {
+        const uint64_t payLoad = (nTotParcels * 3 * sizeof(float));
+
+        format().tag(vtk::fileTag::POINTS)
+            .openDataArray<float,3>(vtk::dataArrayAttr::POINTS)
+            .closeTag();
+
+        format().writeSize(payLoad);
+
+        // Master
+        vtk::writeList(format(), *pointsPtr);
+
+        // Slaves - recv
+        for (int slave=1; slave<Pstream::nProcs(); ++slave)
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+            pointList points(fromSlave);
+
+            vtk::writeList(format(), points);
+        }
+
+        format().flush();
+
+        format()
+            .endDataArray()
+            .endTag(vtk::fileTag::POINTS);
+
+        if (useVerts_)
+        {
+            writeVerts(format, nTotParcels);
+        }
+    }
+    else
+    {
+        // Slaves - send
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        toMaster
+            << *pointsPtr;
+    }
+
+
+    // Prevent any possible conversion of positions as a field
+    obrTmp.filterKeys
+    (
+        [](const word& k)
+        {
+            return k.startsWith("position")
+                || k.startsWith("coordinate");
+        },
+        true  // prune
+    );
+
+    // Restrict to specified fields
+    if (selectFields_.size())
+    {
+        obrTmp.filterKeys(selectFields_);
+    }
+
+
+    // Write fields
+    const vtk::fileTag dataType =
+    (
+        useVerts_
+      ? vtk::fileTag::CELL_DATA
+      : vtk::fileTag::POINT_DATA
+    );
+
+    if (Pstream::master())
+    {
+        format().tag(dataType);
+    }
+
+    writeFields<label>(format, obrTmp, nTotParcels);
+    writeFields<scalar>(format, obrTmp, nTotParcels);
+    writeFields<vector>(format, obrTmp, nTotParcels);
+
+    if (Pstream::master())
+    {
+        format().endTag(dataType);
+    }
+
+    // Footer
+    if (Pstream::master())
+    {
+        // slight cheat. </Piece> too
+        format().endTag(vtk::fileTag::PIECE);
+
+        format().endTag(vtk::fileTag::POLY_DATA)
+            .endVTKFile();
+    }
+
+    return true;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::vtkCloud::vtkCloud
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+:
+    fvMeshFunctionObject(name, runTime, dict),
+    writeOpts_(vtk::formatType::INLINE_BASE64),
+    printf_(),
+    useTimeName_(false),
+    useVerts_(false),
+    selectClouds_(),
+    selectFields_(),
+    dirName_("VTK")
+{
+    if (postProcess)
+    {
+        // Disable for post-process mode.
+        // Emit as FatalError for the try/catch in the caller.
+        FatalError
+            << type() << " disabled in post-process mode"
+            << exit(FatalError);
+    }
+
+    read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::vtkCloud::read(const dictionary& dict)
+{
+    fvMeshFunctionObject::read(dict);
+
+    //
+    // writer options - default is xml base64. Legacy is not desired.
+    //
+    writeOpts_ = vtk::formatType::INLINE_BASE64;
+
+    writeOpts_.ascii
+    (
+        dict.found("format")
+     && (IOstream::formatEnum(dict.lookup("format")) == IOstream::ASCII)
+    );
+
+    writeOpts_.append(false);  // No append supported
+    writeOpts_.legacy(false);  // Too messy to support legacy as well
+
+    writeOpts_.precision
+    (
+        dict.lookupOrDefault
+        (
+            "writePrecision",
+            IOstream::defaultPrecision()
+        )
+    );
+
+    // Info<< type() << " " << name() << " output-format: "
+    //     << writeOpts_.description() << nl;
+
+    int padWidth = dict.lookupOrDefault<int>("width", 8);
+
+    // Appropriate printf format - Enforce min/max sanity limits
+    if (padWidth < 1 || padWidth > 31)
+    {
+        printf_.clear();
+    }
+    else
+    {
+        printf_ = "%0" + std::to_string(padWidth) + "d";
+    }
+
+    useTimeName_ = dict.lookupOrDefault<bool>("timeName", false);
+
+    useVerts_ = dict.lookupOrDefault<bool>("cellData", false);
+
+
+    //
+    // other options
+    //
+    dict.readIfPresent("directory", dirName_);
+
+    selectClouds_.clear();
+    dict.readIfPresent("clouds", selectClouds_);
+
+    if (selectClouds_.empty())
+    {
+        selectClouds_.resize(1);
+        selectClouds_.first() =
+            dict.lookupOrDefault<word>("cloud", cloud::defaultName);
+    }
+
+    selectFields_.clear();
+    dict.readIfPresent("fields", selectFields_);
+
+    return true;
+}
+
+
+bool Foam::functionObjects::vtkCloud::execute()
+{
+    return true;
+}
+
+
+bool Foam::functionObjects::vtkCloud::write()
+{
+    const wordList cloudNames(mesh_.sortedNames<cloud>(selectClouds_));
+
+    if (cloudNames.empty())
+    {
+        return true;  // skip - not available
+    }
+
+    const word timeDesc =
+    (
+        useTimeName_
+      ? time_.timeName()
+      : printf_.empty()
+      ? Foam::name(time_.timeIndex())
+      : word::printf(printf_, time_.timeIndex())
+    );
+
+    fileName vtkDir(dirName_);
+    vtkDir.expand();
+    if (!vtkDir.isAbsolute())
+    {
+        vtkDir = stringOps::expand("<case>")/vtkDir;
+    }
+    mkDir(vtkDir);
+
+    Log << name() << " output Time: " << time_.timeName() << nl;
+
+    // Each cloud separately
+    for (const word& cloudName : cloudNames)
+    {
+        const word prefix(cloudName + "_");
+        const word suffix(".vtp");    // No legacy supported
+
+        const fileName outputName(vtkDir/prefix + timeDesc + suffix);
+
+        if (writeCloud(outputName, cloudName))
+        {
+            Log << "    cloud  : " << outputName << endl;
+
+            if (Pstream::master())
+            {
+                // Add to file-series and emit as JSON
+                // - misbehaves if vtkDir changes during the run,
+                // but that causes other issues too.
+
+                series_(cloudName).append({time_.value(), timeDesc});
+
+                OFstream os(vtkDir/cloudName + ".vtp.series", IOstream::ASCII);
+
+                vtk::writeSeries(os, prefix, suffix, series_[cloudName]);
+            }
+        }
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/lagrangian/vtkCloud/vtkCloud.H b/src/functionObjects/lagrangian/vtkCloud/vtkCloud.H
new file mode 100644
index 0000000000000000000000000000000000000000..651e43d81e703026ecfcef92495210439dcade96
--- /dev/null
+++ b/src/functionObjects/lagrangian/vtkCloud/vtkCloud.H
@@ -0,0 +1,213 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 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::functionObjects::vtkCloud
+
+Group
+    grpUtilitiesFunctionObjects
+
+Description
+    This functionObject writes cloud(s) in VTK format.
+
+    Example of function object specification:
+    \verbatim
+    vtkCloud1
+    {
+        type            vtkCloud;
+        libs            ("liblagrangianFunctionObjects.so");
+        writeControl    writeTime;
+        writeInterval   1;
+        format          binary;
+
+        cloud           myCloud;
+        width           12;
+        fields          (U p);
+    }
+    \endverbatim
+
+Usage
+    \table
+        Property     | Description                      | Required    | Default
+        type         | Type name: vtkCloud              | yes         |
+        writeControl | Output control                   | recommended | timeStep
+        cloud        |                                  | no  | defaultCloud
+        clouds       | wordRe list of clouds            | no          |
+        fields       | wordRe list of fields            | no          |
+        cellData     | Emit cellData instead of pointData | no        | false
+        directory    | The output directory name        | no          | VTK
+        width        | Padding width for file name      | no          | 8
+        timeName     | Use time-name instead of time-index | no       | false
+        format       | ascii or binary format           | no          | binary
+        writePrecision | write precision in ascii       | no | same as IOstream
+    \endtable
+
+See also
+    Foam::functionObjects::ensightWrite
+    Foam::functionObjects::vtkWrite
+    Foam::functionObjects::fvMeshFunctionObject
+    Foam::functionObjects::timeControl
+
+SourceFiles
+    vtkCloud.C
+    vtkCloudTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_vtkCloud_H
+#define functionObjects_vtkCloud_H
+
+#include "fvMeshFunctionObject.H"
+#include "foamVtkOutputOptions.H"
+#include "wordRes.H"
+#include "instant.H"
+#include "DynamicList.H"
+#include "HashTable.H"
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+                          Class vtkCloud Declaration
+\*---------------------------------------------------------------------------*/
+
+class vtkCloud
+:
+    public fvMeshFunctionObject
+{
+    // Private data
+
+        //- Writer options
+        vtk::outputOptions writeOpts_;
+
+        //- The printf format for zero-padding names
+        string printf_;
+
+        //- Use time-name instead of time-index
+        bool useTimeName_;
+
+        //- Write lagrangian as cell data (verts) instead of point data
+        bool useVerts_;
+
+        //- Requested names of clouds to process
+        wordRes selectClouds_;
+
+        //- Subset of cloud fields to process
+        wordRes selectFields_;
+
+        //- Output directory name
+        fileName dirName_;
+
+        //- Per cloud output for file series
+        HashTable<DynamicList<instant>> series_;
+
+
+    // Private Member Functions
+
+        //- Write a cloud
+        bool writeCloud
+        (
+            const fileName& outputName,
+            const word& cloudName
+        ) const;
+
+        //- Write VERTS connectivity
+        void writeVerts
+        (
+            autoPtr<vtk::formatter>& format,
+            const label nParcels
+        ) const;
+
+
+        //- Write fields of IOField<Type>
+        template<class Type>
+        label writeFields
+        (
+            autoPtr<vtk::formatter>& format,
+            const objectRegistry& obrTmp,
+            const label nTotParcels
+        ) const;
+
+
+        //- No copy construct
+        vtkCloud(const vtkCloud&) = delete;
+
+        //- No copy assignment
+        void operator=(const vtkCloud&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("vtkCloud");
+
+
+    // Constructors
+
+        //- Construct from Time and dictionary
+        vtkCloud
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~vtkCloud() = default;
+
+
+    // Member Functions
+
+        //- Read the vtkCloud specification
+        virtual bool read(const dictionary& dict);
+
+        //- Execute, currently does nothing
+        virtual bool execute();
+
+        //- Write fields
+        virtual bool write();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "vtkCloudTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/lagrangian/vtkCloud/vtkCloudTemplates.C b/src/functionObjects/lagrangian/vtkCloud/vtkCloudTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..38ad185e00e9bcc73befb7dc575b33fd19d6e22d
--- /dev/null
+++ b/src/functionObjects/lagrangian/vtkCloud/vtkCloudTemplates.C
@@ -0,0 +1,155 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 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 "IOField.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+Foam::label Foam::functionObjects::vtkCloud::writeFields
+(
+    autoPtr<vtk::formatter>& format,
+    const objectRegistry& obrTmp,
+    const label nTotParcels
+) const
+{
+    const int nCmpt(pTraits<Type>::nComponents);
+
+    const bool useIntField =
+        std::is_integral<typename pTraits<Type>::cmptType>();
+
+    // Fields are not always on all processors (eg, multi-component parcels).
+    // Thus need to resolve names between all processors.
+
+    wordList fieldNames(obrTmp.names<IOField<Type>>());
+    Pstream::combineGather(fieldNames, ListOps::uniqueEqOp<word>());
+    Pstream::combineScatter(fieldNames);
+
+    // Sort to get identical order of fields on all processors
+    Foam::sort(fieldNames);
+
+    for (const word& fieldName : fieldNames)
+    {
+        const auto* fldPtr = obrTmp.lookupObjectPtr<IOField<Type>>(fieldName);
+
+        if (Pstream::master())
+        {
+            if (useIntField)
+            {
+                const uint64_t payLoad(nTotParcels * nCmpt * sizeof(label));
+
+                format().openDataArray<label, nCmpt>(fieldName)
+                    .closeTag();
+
+                format().writeSize(payLoad);
+
+                if (fldPtr)
+                {
+                    // Data on master
+                    const auto& fld = *fldPtr;
+
+                    // Ensure consistent output width
+                    for (const Type& val : fld)
+                    {
+                        for (int cmpt=0; cmpt < nCmpt; ++cmpt)
+                        {
+                            format().write(label(component(val, cmpt)));
+                        }
+                    }
+                }
+
+                // Slaves - recv
+                for (int slave=1; slave<Pstream::nProcs(); ++slave)
+                {
+                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                    Field<Type> recv(fromSlave);
+
+                    for (const Type& val : recv)
+                    {
+                        for (int cmpt=0; cmpt < nCmpt; ++cmpt)
+                        {
+                            format().write(label(component(val, cmpt)));
+                        }
+                    }
+                }
+            }
+            else
+            {
+                const uint64_t payLoad(nTotParcels * nCmpt * sizeof(float));
+
+                format().openDataArray<float, nCmpt>(fieldName)
+                    .closeTag();
+
+                format().writeSize(payLoad);
+
+                if (fldPtr)
+                {
+                    // Data on master
+                    vtk::writeList(format(), *fldPtr);
+                }
+
+                // Slaves - recv
+                for (int slave=1; slave<Pstream::nProcs(); ++slave)
+                {
+                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                    Field<Type> recv(fromSlave);
+
+                    vtk::writeList(format(), recv);
+                }
+            }
+
+            format().flush();
+
+            format()
+                .endDataArray();
+        }
+        else
+        {
+            // Slaves - send
+
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            if (fldPtr)
+            {
+                toMaster
+                    << *fldPtr;
+            }
+            else
+            {
+                toMaster
+                    << Field<Type>();
+            }
+        }
+    }
+
+    return fieldNames.size();
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
index 267a92fc5aa3a4482dd95878a2afd529403311a3..973b0b23487a822409d5bc2c8dcfdebf278f6427 100644
--- a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
@@ -51,5 +51,9 @@ maxCo           1.0;
 
 maxDeltaT       1;
 
+functions
+{
+    #include "vtkCloud"
+}
 
 // ************************************************************************* //
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/vtkCloud b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/vtkCloud
new file mode 100644
index 0000000000000000000000000000000000000000..c134d6cfd2a2fcf09589adaf4d4f245eb7577521
--- /dev/null
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/vtkCloud
@@ -0,0 +1,31 @@
+// -*- C++ -*-
+// Minimal example of using the vtkCloud function object.
+vtkCloud
+{
+    type    vtkCloud;
+    libs    ("liblagrangianFunctionObjects.so");
+    log     true;
+
+    // Cloud name
+    // cloud   coalCloud1;
+    clouds  ( ".*" );
+
+    // Fields to output (words or regex)
+    fields  ( U T d "Y.*" );
+
+    //- Output format (ascii | binary) - Default=binary
+    // format  binary;
+
+    // format   ascii;
+    // writePrecision 12;
+
+    //- Output directory name - Default="VTK"
+    // directory       "VTK";
+
+    //- Write more frequent than fields
+    writeControl    adjustableRunTime;
+    writeInterval   0.001;
+}
+
+
+// ************************************************************************* //