diff --git a/doc/mesh.rst b/doc/mesh.rst
index 60de797efcd103c6029902d575d9c11bdcbb990a..ccfba1e768691515f8f90ed7358e1cd660e5f6e4 100644
--- a/doc/mesh.rst
+++ b/doc/mesh.rst
@@ -70,4 +70,9 @@ Mesh refinement
 
 .. automodule:: meshmode.mesh.refinement
 
+Mesh visualization
+---------------
+
+.. automodule:: meshmode.mesh.visualization
+
 .. vim: sw=4
diff --git a/meshmode/mesh/refinement/__init__.py b/meshmode/mesh/refinement/__init__.py
index 7e63a16416ea2872298ed91ccb5073cbf8ea79ed..6cfaa101f5fc5433a0d4b3f1e543cde3d18b057d 100644
--- a/meshmode/mesh/refinement/__init__.py
+++ b/meshmode/mesh/refinement/__init__.py
@@ -9,8 +9,10 @@ in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
+
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
+
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
diff --git a/meshmode/mesh/visualization.py b/meshmode/mesh/visualization.py
index a1b1470739b0f06e6f6f3c65f20b684f4006c8e9..2c97d2f6cd5cea2df7bb79c22ae5ea2d7c375e76 100644
--- a/meshmode/mesh/visualization.py
+++ b/meshmode/mesh/visualization.py
@@ -25,6 +25,12 @@ THE SOFTWARE.
 import numpy as np
 from six.moves import range
 
+__doc__ = """
+.. autofunction:: draw_2d_mesh
+.. autofunction:: draw_curve
+.. autofunction:: write_vertex_vtk_file
+"""
+
 
 # {{{ draw_2d_mesh
 
@@ -162,4 +168,69 @@ def draw_curve(mesh,
 
 # }}}
 
+
+# {{{ write_vtk_file
+
+def write_vertex_vtk_file(mesh, file_name, compressor=None):
+    from pyvisfile.vtk import (
+            UnstructuredGrid, DataArray,
+            AppendedDataXMLGenerator,
+            VF_LIST_OF_COMPONENTS)
+
+    # {{{ create cell_types
+
+    from pyvisfile.vtk import (
+            VTK_LINE, VTK_TRIANGLE, VTK_TETRA,
+            VTK_QUAD, VTK_HEXAHEDRON)
+
+    from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup
+
+    cell_types = np.empty(mesh.nelements, dtype=np.uint8)
+    cell_types.fill(255)
+    for egrp in mesh.groups:
+        if isinstance(egrp, SimplexElementGroup):
+            vtk_cell_type = {
+                    1: VTK_LINE,
+                    2: VTK_TRIANGLE,
+                    3: VTK_TETRA,
+                    }[egrp.dim]
+        elif isinstance(egrp, TensorProductElementGroup):
+            vtk_cell_type = {
+                    1: VTK_LINE,
+                    2: VTK_QUAD,
+                    3: VTK_HEXAHEDRON,
+                    }[egrp.dim]
+        else:
+            raise NotImplementedError("mesh vtk file writing for "
+                    "element group of type '%s'" % type(egrp).__name__)
+
+        cell_types[
+                egrp.element_nr_base:
+                egrp.element_nr_base + egrp.nelements] = \
+                        vtk_cell_type
+
+    assert (cell_types != 255).all()
+
+    # }}}
+
+    grid = UnstructuredGrid(
+            (mesh.nvertices,
+                DataArray("points",
+                    mesh.vertices,
+                    vector_format=VF_LIST_OF_COMPONENTS)),
+            cells=np.hstack([
+                vgrp.vertex_indices.reshape(-1)
+                for vgrp in mesh.groups]),
+            cell_types=cell_types)
+
+    from os.path import exists
+    if exists(file_name):
+        raise RuntimeError("output file '%s' already exists"
+                % file_name)
+
+    with open(file_name, "w") as outf:
+        AppendedDataXMLGenerator(compressor)(grid).write(outf)
+
+# }}}
+
 # vim: foldmethod=marker