diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C
deleted file mode 100644
index cbe2d515bc7d73d301146692382ea10800b7fd40..0000000000000000000000000000000000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C
+++ /dev/null
@@ -1,286 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "foamVtkLagrangianWriter.H"
-#include "Cloud.H"
-#include "passiveParticle.H"
-
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-void Foam::vtk::lagrangianWriter::beginPiece()
-{
-    if (!legacy_)
-    {
-        if (useVerts_)
-        {
-            format()
-                .openTag(vtk::fileTag::PIECE)
-                .xmlAttr(fileAttr::NUMBER_OF_POINTS, nParcels_)
-                .xmlAttr(fileAttr::NUMBER_OF_VERTS,  nParcels_)
-                .closeTag();
-        }
-        else
-        {
-            format()
-                .openTag(vtk::fileTag::PIECE)
-                .xmlAttr(fileAttr::NUMBER_OF_POINTS, nParcels_)
-                .closeTag();
-        }
-    }
-}
-
-
-void Foam::vtk::lagrangianWriter::writePoints()
-{
-    Cloud<passiveParticle> parcels(mesh_, cloudName_, false);
-    nParcels_ = parcels.size();
-
-    const uint64_t payLoad = (nParcels_ * 3 * sizeof(float));
-
-    if (legacy_)
-    {
-        legacy::beginPoints(os_, nParcels_);
-    }
-    else
-    {
-        beginPiece();           // Tricky - hide begin piece in here
-        if (!nParcels_) return; // No parcels? ... skip everything else
-
-        format().tag(vtk::fileTag::POINTS)
-            .openDataArray<float,3>(vtk::dataArrayAttr::POINTS)
-            .closeTag();
-    }
-
-    format().writeSize(payLoad);
-
-    forAllConstIters(parcels, iter)
-    {
-        const point pt(iter().position());
-
-        vtk::write(format(), pt);
-    }
-    format().flush();
-
-    if (!legacy_)
-    {
-        format()
-            .endDataArray()
-            .endTag(vtk::fileTag::POINTS);
-    }
-}
-
-
-void Foam::vtk::lagrangianWriter::writeVertsLegacy()
-{
-    os_ << "VERTICES " << nParcels_ << ' ' << 2*nParcels_ << nl;
-
-    // legacy has cells + connectivity together
-    // count the number of vertices referenced
-
-    for (label i=0; i < nParcels_; ++i)
-    {
-        format().write(label(1)); // Number of vertices for this cell (==1)
-        format().write(i);
-    }
-    format().flush();
-}
-
-
-void Foam::vtk::lagrangianWriter::writeVerts()
-{
-    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);
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::vtk::lagrangianWriter::lagrangianWriter
-(
-    const fvMesh& mesh,
-    const word& cloudName,
-    const fileName& baseName,
-    const vtk::outputOptions outOpts,
-    const bool dummyCloud
-)
-:
-    mesh_(mesh),
-    legacy_(outOpts.legacy()),
-    useVerts_(false),
-    format_(),
-    cloudName_(cloudName),
-    os_(),
-    nParcels_(0)
-{
-
-    outputOptions opts(outOpts);
-    opts.append(false);  // No append supported
-
-    os_.open((baseName + (legacy_ ? ".vtk" : ".vtp")).c_str());
-    format_ = opts.newFormatter(os_);
-
-    const auto& title = mesh_.time().caseName();
-
-    if (legacy_)
-    {
-        legacy::fileHeader(format(), title, vtk::fileTag::POLY_DATA);
-
-        if (dummyCloud)
-        {
-            legacy::beginPoints(os_, nParcels_);
-        }
-        else
-        {
-            writePoints();
-            if (useVerts_) writeVertsLegacy();
-        }
-    }
-    else
-    {
-        // XML (inline)
-
-        format()
-            .xmlHeader()
-            .xmlComment(title)
-            .beginVTKFile(vtk::fileTag::POLY_DATA, "0.1");
-
-        if (dummyCloud)
-        {
-            beginPiece();
-        }
-        else
-        {
-            writePoints();
-            if (useVerts_) writeVerts();
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::vtk::lagrangianWriter::beginParcelData(label nFields)
-{
-    if (!nParcels_) return;   // Skip if there are no parcels
-
-    if (useVerts_)
-    {
-        if (legacy_)
-        {
-            legacy::beginCellData(format(), nParcels_, nFields);
-        }
-        else
-        {
-            format().beginCellData();
-        }
-    }
-    else
-    {
-        if (legacy_)
-        {
-            legacy::beginPointData(format(), nParcels_, nFields);
-        }
-        else
-        {
-            format().beginPointData();
-        }
-    }
-}
-
-
-void Foam::vtk::lagrangianWriter::endParcelData()
-{
-    if (!nParcels_) return;   // Skip if there are no parcels
-
-    if (!legacy_)
-    {
-
-        if (useVerts_)
-        {
-            format().endCellData();
-        }
-        else
-        {
-            format().endPointData();
-        }
-    }
-}
-
-
-void Foam::vtk::lagrangianWriter::writeFooter()
-{
-    if (!legacy_)
-    {
-        // slight cheat. </Piece> too
-        format().endTag(vtk::fileTag::PIECE);
-
-        format().endTag(vtk::fileTag::POLY_DATA)
-            .endVTKFile();
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H
deleted file mode 100644
index 05a9aeb8f7716ce1d0d837f5b7eecfdc18fbb5ac..0000000000000000000000000000000000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H
+++ /dev/null
@@ -1,168 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::vtk::lagrangianWriter
-
-Description
-    Write lagrangian positions and fields (clouds).
-
-SourceFiles
-    lagrangianWriter.C
-    lagrangianWriterTemplates.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef foamVtkLagrangianWriter_H
-#define foamVtkLagrangianWriter_H
-
-#include "Cloud.H"
-#include "volFields.H"
-#include "pointFields.H"
-#include "foamVtkOutputOptions.H"
-#include <fstream>
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-class volPointInterpolation;
-
-namespace vtk
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class lagrangianWriter Declaration
-\*---------------------------------------------------------------------------*/
-
-class lagrangianWriter
-{
-    // Private Member Data
-
-        //- Reference to the OpenFOAM mesh (or subset)
-        const fvMesh& mesh_;
-
-        //- Commonly used query
-        const bool legacy_;
-
-        //- Write lagrangian as cell data (verts) or point data?
-        const bool useVerts_;
-
-        autoPtr<vtk::formatter> format_;
-
-        const word cloudName_;
-
-        std::ofstream os_;
-
-        label nParcels_;
-
-
-    // Private Member Functions
-
-        //- Begin piece
-        void beginPiece();
-
-        //- Write positions
-        void writePoints();
-
-        //- Write vertex (cells)
-        void writeVertsLegacy();
-
-        //- Write vertex (cells)
-        void writeVerts();
-
-
-        //- No copy construct
-        lagrangianWriter(const lagrangianWriter&) = delete;
-
-        //- No copy assignment
-        void operator=(const lagrangianWriter&) = delete;
-
-
-public:
-
-    // Constructors
-
-        //- Construct from components
-        lagrangianWriter
-        (
-            const fvMesh& mesh,
-            const word& cloudName,
-            const fileName& baseName,
-            const vtk::outputOptions outOpts,
-            const bool dummyCloud = false
-        );
-
-
-    //- Destructor
-    ~lagrangianWriter() = default;
-
-
-    // Member Functions
-
-        inline std::ofstream& os()
-        {
-            return os_;
-        }
-
-        inline vtk::formatter& format()
-        {
-            return *format_;
-        }
-
-        inline label nParcels() const
-        {
-            return nParcels_;
-        }
-
-        //- Begin parcel data (point data).
-        //  The nFields parameter is only needed for legacy format.
-        void beginParcelData(label nFields);
-        void endParcelData();
-
-        //- Write file footer
-        void writeFooter();
-
-        //- Write IOFields
-        template<class Type>
-        void writeIOField(const wordList& fieldNames);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace vtk
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#ifdef NoRepository
-    #include "foamVtkLagrangianWriterTemplates.C"
-#endif
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C
deleted file mode 100644
index bd0e7202eade1a428d22febd3d462828fefbd9bf..0000000000000000000000000000000000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C
+++ /dev/null
@@ -1,128 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "foamVtkLagrangianWriter.H"
-#include "IOField.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-template<class Type>
-void Foam::vtk::lagrangianWriter::writeIOField(const wordList& fieldNames)
-{
-    const int nCmpt(pTraits<Type>::nComponents);
-
-    const bool useIntField =
-        std::is_integral<typename pTraits<Type>::cmptType>();
-
-    const fileName cloudDir(cloud::prefix/cloudName_);
-
-    for (const word& fldName : fieldNames)
-    {
-        // Globally the field is expected to exist (MUST_READ), but can
-        // be missing on a local processor.
-        //
-        // However, constructing IOField with MUST_READ and valid=false fails.
-        // Workaround: READ_IF_PRESENT and verify the header globally
-
-        IOobject fieldObject
-        (
-            fldName,
-            mesh_.time().timeName(),
-            cloudDir,
-            mesh_,
-            IOobject::READ_IF_PRESENT
-        );
-
-        // Check global existence - could make an error
-        const bool fieldExists =
-            returnReduce
-            (
-                fieldObject.typeHeaderOk<IOField<Type>>(false),
-                orOp<bool>()
-            );
-
-        if (!fieldExists)
-        {
-            continue;
-        }
-
-        IOField<Type> fld(fieldObject);
-
-        // NOTE: Could skip if there are no local parcels...
-
-        if (useIntField)
-        {
-            const uint64_t payLoad(fld.size() * nCmpt * sizeof(label));
-
-            if (legacy_)
-            {
-                legacy::intField<nCmpt>(format(), fldName, fld.size());
-            }
-            else
-            {
-                format().openDataArray<label, nCmpt>(fldName)
-                    .closeTag();
-            }
-
-            format().writeSize(payLoad);
-
-            // Ensure consistent output width
-            for (const Type& val : fld)
-            {
-                for (int cmpt=0; cmpt < nCmpt; ++cmpt)
-                {
-                    format().write(label(component(val, cmpt)));
-                }
-            }
-        }
-        else
-        {
-            const uint64_t payLoad(fld.size() * nCmpt * sizeof(float));
-
-            if (legacy_)
-            {
-                legacy::floatField<nCmpt>(format(), fldName, fld.size());
-            }
-            else
-            {
-                format().openDataArray<float, nCmpt>(fldName)
-                    .closeTag();
-            }
-
-            format().writeSize(payLoad);
-            vtk::writeList(format(), fld);
-        }
-
-        format().flush();
-
-        if (!legacy_)
-        {
-            format().endDataArray();
-        }
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/Make/files b/src/lagrangian/intermediate/Make/files
index b04a8f0f00face4ff93a439ac4eecc4ac398f7c0..0eb9db55d2495e0abebb486b1e52aec9f20ab571 100644
--- a/src/lagrangian/intermediate/Make/files
+++ b/src/lagrangian/intermediate/Make/files
@@ -118,5 +118,8 @@ clouds/Templates/KinematicCloud/cloudSolution/cloudSolution.C
 /* averaging methods */
 submodels/MPPIC/AveragingMethods/makeAveragingMethods.C
 
+/* conversion methods */
+conversion/vtk/foamVtkLagrangianWriter.C
+
 
 LIB = $(FOAM_LIBBIN)/liblagrangianIntermediate
diff --git a/src/lagrangian/intermediate/Make/options b/src/lagrangian/intermediate/Make/options
index 879f3ef6345aae57dc3d43c23b6d4407fc0aba41..c859546735be6ca725ecedb6346f1eaadd24c44d 100644
--- a/src/lagrangian/intermediate/Make/options
+++ b/src/lagrangian/intermediate/Make/options
@@ -16,6 +16,7 @@ EXE_INC = \
     -I$(LIB_SRC)/sampling/lnInclude \
     -I$(LIB_SRC)/surfMesh/lnInclude \
     -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
     -I$(LIB_SRC)/meshTools/lnInclude
 
 LIB_LIBS = \
@@ -35,4 +36,5 @@ LIB_LIBS = \
     -ldynamicFvMesh \
     -lsampling \
     -lfiniteVolume \
+    -lfileFormats \
     -lmeshTools
diff --git a/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.C b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.C
new file mode 100644
index 0000000000000000000000000000000000000000..792e3757bd8c2c05bc57b643714d482fd680dc76
--- /dev/null
+++ b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.C
@@ -0,0 +1,306 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016-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 "foamVtkLagrangianWriter.H"
+#include "Cloud.H"
+#include "passiveParticle.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::fileName Foam::vtk::lagrangianWriter::cloudDir() const
+{
+    return (cloud::prefix/cloudName_);
+}
+
+
+Foam::pointField Foam::vtk::lagrangianWriter::positions() const
+{
+    Cloud<passiveParticle> parcels(mesh_, cloudName_, false);
+
+    pointField pts(parcels.size());
+
+    auto outIter = pts.begin();
+
+    forAllConstIters(parcels, iter)
+    {
+        *outIter = iter().position();
+        ++outIter;
+    }
+
+    return pts;
+}
+
+
+void Foam::vtk::lagrangianWriter::writeVerts()
+{
+    // No collectives - can skip on slave processors
+    if (!format_) return;
+
+    const label nVerts = numberOfPoints_;
+
+    // Same payload for connectivity and offsets
+    const uint64_t payLoad = vtk::sizeofData<label>(nVerts);
+
+
+    format().tag(vtk::fileTag::VERTS);
+
+    //
+    // 'connectivity'
+    // = linear mapping onto points
+    //
+    {
+        format().beginDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY);
+        format().writeSize(payLoad);
+
+        for (label i=0; i < nVerts; ++i)
+        {
+            format().write(i);
+        }
+        format().flush();
+        format().endDataArray();
+    }
+
+    //
+    // 'offsets' (connectivity offsets)
+    // = linear mapping onto points (with 1 offset)
+    //
+    {
+        format().beginDataArray<label>(vtk::dataArrayAttr::OFFSETS);
+        format().writeSize(payLoad);
+
+        for (label i=0; i < nVerts; ++i)
+        {
+            format().write(i+1);
+        }
+        format().flush();
+        format().endDataArray();
+    }
+
+    format().endTag(vtk::fileTag::VERTS);
+}
+
+
+bool Foam::vtk::lagrangianWriter::beginCellData(label nFields)
+{
+    return enter_CellData(numberOfPoints_, nFields);
+}
+
+
+bool Foam::vtk::lagrangianWriter::beginPointData(label nFields)
+{
+    return enter_PointData(numberOfPoints_, nFields);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::vtk::lagrangianWriter::lagrangianWriter
+(
+    const fvMesh& mesh,
+    const word& cloudName,
+    const vtk::outputOptions opts,
+    bool useVerts
+)
+:
+    vtk::fileWriter(vtk::fileTag::POLY_DATA, opts),
+    mesh_(mesh),
+    cloudName_(cloudName),
+    numberOfPoints_(0),
+    useVerts_(useVerts)
+{
+    opts_.append(false); // No append mode (horrible for streaming)
+    opts_.legacy(false); // Disallow legacy (see notes)
+}
+
+
+Foam::vtk::lagrangianWriter::lagrangianWriter
+(
+    const fvMesh& mesh,
+    const word& cloudName,
+    const fileName& file,
+    bool parallel
+)
+:
+    lagrangianWriter(mesh, cloudName)
+{
+    open(file, parallel);
+}
+
+
+Foam::vtk::lagrangianWriter::lagrangianWriter
+(
+    const fvMesh& mesh,
+    const word& cloudName,
+    const vtk::outputOptions opts,
+    const fileName& file,
+    bool parallel
+)
+:
+    lagrangianWriter(mesh, cloudName, opts)
+{
+    open(file, parallel);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::vtk::lagrangianWriter::beginFile(std::string title)
+{
+    if (title.size())
+    {
+        return vtk::fileWriter::beginFile(title);
+    }
+
+    // Provide default title
+
+    // XML (inline)
+
+    return vtk::fileWriter::beginFile
+    (
+        "case='" + mesh_.time().globalCaseName()
+      + "' cloud='" + cloudName_
+      + "' time='" + mesh_.time().timeName()
+      + "' index='" + Foam::name(mesh_.time().timeIndex())
+      + "'"
+    );
+}
+
+
+bool Foam::vtk::lagrangianWriter::writeGeometry()
+{
+    enter_Piece();
+
+    if (isState(outputState::CELL_DATA))
+    {
+        ++nCellData_;
+    }
+    else if (isState(outputState::POINT_DATA))
+    {
+        ++nPointData_;
+    }
+
+    // Transcribe cloud into pointField
+    pointField cloudPoints(positions());
+
+    numberOfPoints_ = cloudPoints.size();
+
+    if (parallel_)
+    {
+        reduce(numberOfPoints_, sumOp<label>());
+    }
+
+
+    // beginPiece()
+    if (format_)
+    {
+        if (useVerts_)
+        {
+            format()
+                .tag
+                (
+                    vtk::fileTag::PIECE,
+                    fileAttr::NUMBER_OF_POINTS, numberOfPoints_,
+                    fileAttr::NUMBER_OF_VERTS,  numberOfPoints_
+                );
+        }
+        else
+        {
+            format()
+                .tag
+                (
+                    vtk::fileTag::PIECE,
+                    fileAttr::NUMBER_OF_POINTS, numberOfPoints_
+                );
+        }
+    }
+
+
+    // writePoints()
+    {
+        if (format_)
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<float,3>(numberOfPoints_);
+
+            format().tag(vtk::fileTag::POINTS)
+                .beginDataArray<float,3>(vtk::dataArrayAttr::POINTS);
+
+            format().writeSize(payLoad);
+        }
+
+        if (parallel_)
+        {
+            vtk::writeListParallel(format_.ref(), cloudPoints);
+        }
+        else
+        {
+            vtk::writeList(format(), cloudPoints);
+        }
+
+
+        if (format_)
+        {
+            format().flush();
+            format().endDataArray();
+
+            // Non-legacy
+            format()
+                .endTag(vtk::fileTag::POINTS);
+        }
+    }
+
+    if (useVerts_) writeVerts();
+
+    return true;
+}
+
+
+bool Foam::vtk::lagrangianWriter::beginParcelData()
+{
+    if (useVerts_)
+    {
+        return beginCellData();
+    }
+    else
+    {
+        return beginPointData();
+    }
+}
+
+
+bool Foam::vtk::lagrangianWriter::endParcelData()
+{
+    if (useVerts_)
+    {
+        return endCellData();
+    }
+    else
+    {
+        return endPointData();
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.H b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.H
new file mode 100644
index 0000000000000000000000000000000000000000..519527fb941622612c50585ddd6cd83bd560f8c1
--- /dev/null
+++ b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriter.H
@@ -0,0 +1,238 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016-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::vtk::lagrangianWriter
+
+Description
+    Write lagrangian (cloud) positions and fields (as PointData) in
+    VTP format. Legacy VTK format is intentionally not supported since
+    the VTP format provides much better field selection in ParaView, and for
+    consistency with the Foam::functionObjects::vtkCloud function object.
+
+    The file output states are managed by the Foam::vtk::fileWriter class.
+    FieldData (eg, TimeValue) must appear before any geometry pieces.
+
+Note
+    If fields should be CellData instead of PointData (default), this
+    must be decided at contruction time.
+
+SourceFiles
+    lagrangianWriter.C
+    lagrangianWriterTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foamVtkLagrangianWriter_H
+#define foamVtkLagrangianWriter_H
+
+#include "fvMesh.H"
+#include "pointField.H"
+#include "foamVtkFileWriter.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class IOobjectList;
+template<class Type> class IOField;
+
+namespace vtk
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class vtk::lagrangianWriter Declaration
+\*---------------------------------------------------------------------------*/
+
+class lagrangianWriter
+:
+    public vtk::fileWriter
+{
+    // Private Member Data
+
+        //- Reference to the OpenFOAM mesh (or subset)
+        const fvMesh& mesh_;
+
+        //- The cloud name
+        const word cloudName_;
+
+        //- The numer of field points for the current Piece
+        label numberOfPoints_;
+
+        //- Write as CellData (verts) instead of as PointData.
+        const bool useVerts_;
+
+
+    // Private Member Functions
+
+        //- The cloud directory for the current cloud name.
+        fileName cloudDir() const;
+
+        //- Transcribe the cloud into pointField
+        pointField positions() const;
+
+        //- Write vertex (cells)
+        void writeVerts();
+
+
+        //- No copy construct
+        lagrangianWriter(const lagrangianWriter&) = delete;
+
+        //- No copy assignment
+        void operator=(const lagrangianWriter&) = delete;
+
+
+protected:
+
+    // Protected Member Functions
+
+        //- Begin CellData output section for specified number of fields.
+        //  Must be called prior to writing any cell data fields.
+        //  \param nFields is the number of fields, which is required for
+        //      legacy format.
+        //  \note Expected calling states: (PIECE | POINT_DATA).
+        //
+        //  \return True if the state changed
+        virtual bool beginCellData(label nFields=0);
+
+        //- Begin PointData for specified number of fields.
+        //  Must be called prior to writing any point data fields.
+        //  \param nFields is the number of fields, which is required for
+        //      legacy format.
+        //  \note Expected calling states: (PIECE | CELL_DATA).
+        //
+        //  \return True if the state changed
+        virtual bool beginPointData(label nFields=0);
+
+
+public:
+
+    // Constructors
+
+        //- Construct from components (default format INLINE_BASE64)
+        //  \param useVerts Define VERTS and use CellData instead of PointData.
+        lagrangianWriter
+        (
+            const fvMesh& mesh,
+            const word& cloudName,
+            const vtk::outputOptions opts = vtk::formatType::INLINE_BASE64,
+            bool useVerts = false
+        );
+
+        //- Construct from components (default format INLINE_BASE64),
+        //- and open the file for writing.
+        //  The file name is with/without an extension.
+        lagrangianWriter
+        (
+            const fvMesh& mesh,
+            const word& cloudName,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+        //- Construct from components and open the file for writing.
+        //  The file name is with/without an extension.
+        lagrangianWriter
+        (
+            const fvMesh& mesh,
+            const word& cloudName,
+            const vtk::outputOptions opts,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+
+    //- Destructor
+    virtual ~lagrangianWriter() = default;
+
+
+    // Member Functions
+
+        //- File extension for current format type.
+        using vtk::fileWriter::ext;
+
+        //- File extension for given output type. Always ".vtp"
+        inline static word ext(vtk::outputOptions)
+        {
+            // No legacy
+            return vtk::fileExtension[vtk::fileTag::POLY_DATA];
+        }
+
+
+        //- Write file header (non-collective)
+        //  \note Expected calling states: (OPENED).
+        virtual bool beginFile(std::string title = "");
+
+        //- Write cloud positions
+        //  Also writes the file header if not previously written.
+        //  \note Must be called prior to writing CellData or PointData
+        virtual bool writeGeometry();
+
+
+        //- Begin parcel (PointData) output section
+        //  Must be called prior to writing data fields.
+        //  \note Expected calling states: (PIECE).
+        //
+        //  \return True if the state changed
+        bool beginParcelData();
+
+        //- Explicitly end parcel (PointData) output and switch to PIECE state
+        //  Ignored (no-op) if not currently in the parcel state.
+        bool endParcelData();
+
+
+    // Write
+
+        //- Write the IOField
+        template<class Type>
+        void write(const IOField<Type>& field);
+
+        //- Write IOFields
+        template<class Type>
+        label writeFields(const wordList& fieldNames, bool verbose=true);
+
+        //- Write IOFields
+        template<class Type>
+        label writeFields(const IOobjectList& objects, bool verbose=true);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace vtk
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "foamVtkLagrangianWriterTemplates.C"
+#endif
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriterTemplates.C b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriterTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..fda8ea72542fafcade1268d2f1d795c759f45408
--- /dev/null
+++ b/src/lagrangian/intermediate/conversion/vtk/foamVtkLagrangianWriterTemplates.C
@@ -0,0 +1,207 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016-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 "foamVtkLagrangianWriter.H"
+#include "IOobjectList.H"
+#include "IOField.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template<class Type>
+void Foam::vtk::lagrangianWriter::write(const IOField<Type>& field)
+{
+    if (isState(outputState::CELL_DATA))
+    {
+        ++nCellData_;
+    }
+    else if (isState(outputState::POINT_DATA))
+    {
+        ++nPointData_;
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Bad writer state (" << stateNames[state_]
+            << ") - should be (" << stateNames[outputState::CELL_DATA]
+            << ") or (" << stateNames[outputState::POINT_DATA]
+            << ") for field " << field.name() << nl << endl
+            << exit(FatalError);
+    }
+
+    static_assert
+    (
+        (
+            std::is_same<typename pTraits<Type>::cmptType,label>::value
+         || std::is_floating_point<typename pTraits<Type>::cmptType>::value
+        ),
+        "Label and Floating-point vector space only"
+    );
+
+
+    // Other integral types (eg, bool etc) would need cast/convert to label.
+    // Similarly for labelVector etc.
+
+    // // Ensure consistent output width
+    // for (const Type& val : field)
+    // {
+    //     for (int cmpt=0; cmpt < nCmpt; ++cmpt)
+    //     {
+    //         format().write(label(component(val, cmpt)));
+    //     }
+    // }
+
+    const bool isLabel =
+        std::is_same<typename pTraits<Type>::cmptType(), label>::value;
+
+
+    const direction nCmpt(pTraits<Type>::nComponents);
+
+    label nVals = field.size();
+
+    if (parallel_)
+    {
+        reduce(nVals, sumOp<label>());
+    }
+
+    if (format_)
+    {
+        // Non-legacy
+
+        if (isLabel)
+        {
+            const uint64_t payLoad = vtk::sizeofData<label, nCmpt>(nVals);
+
+            format().beginDataArray<label, nCmpt>(field.name());
+            format().writeSize(payLoad);
+        }
+        else
+        {
+            const uint64_t payLoad = vtk::sizeofData<float, nCmpt>(nVals);
+
+            format().beginDataArray<float, nCmpt>(field.name());
+            format().writeSize(payLoad);
+        }
+    }
+
+    if (parallel_)
+    {
+        vtk::writeListParallel(format_.ref(), field);
+    }
+    else
+    {
+        vtk::writeList(format(), field);
+    }
+
+
+    if (format_)
+    {
+        format().flush();
+        format().endDataArray();
+    }
+}
+
+
+template<class Type>
+Foam::label Foam::vtk::lagrangianWriter::writeFields
+(
+    const wordList& fieldNames,
+    bool verbose
+)
+{
+    const fileName localDir(cloudDir());
+
+    label nFields = 0;
+
+    for (const word& fieldName : fieldNames)
+    {
+        // Globally the field is expected to exist (MUST_READ), but can
+        // be missing on a local processor.
+        //
+        // However, constructing IOField with MUST_READ and valid=false fails.
+        // Workaround: READ_IF_PRESENT and verify the header globally
+
+        IOobject io
+        (
+            fieldName,
+            mesh_.time().timeName(),
+            localDir,
+            mesh_,
+            IOobject::READ_IF_PRESENT
+        );
+
+        // Check global existence to avoid any errors
+        const bool ok =
+            returnReduce
+            (
+                io.typeHeaderOk<IOField<Type>>(false),
+                orOp<bool>()
+            );
+
+        if (!ok)
+        {
+            continue;
+        }
+
+        if (verbose)
+        {
+            if (!nFields)
+            {
+                Info<< "        " << pTraits<Type>::typeName << ":";
+            }
+
+            Info<< " " << fieldName;
+        }
+
+        IOField<Type> field(io);
+        this->write<Type>(field);
+
+        ++nFields;
+    }
+
+    if (verbose && nFields)
+    {
+        Info << endl;
+    }
+
+    return nFields;
+}
+
+
+template<class Type>
+Foam::label Foam::vtk::lagrangianWriter::writeFields
+(
+    const IOobjectList& objects,
+    const bool verbose
+)
+{
+    return writeFields<Type>
+    (
+        objects.allNames<IOField<Type>>(),  // These are in sorted order
+        verbose
+    );
+}
+
+
+// ************************************************************************* //