diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py
index df7d233..00a6303 100644
--- a/Lib/ufo2ft/__init__.py
+++ b/Lib/ufo2ft/__init__.py
@@ -51,6 +51,7 @@ def compileOTF(
     inplace=False,
     layerName=None,
     skipExportGlyphs=None,
+    debugFeatureFile=None,
     _tables=None,
 ):
     """Create FontTools CFF font from a UFO.
@@ -130,6 +131,7 @@ def compileOTF(
             glyphSet=glyphSet,
             featureWriters=featureWriters,
             featureCompilerClass=featureCompilerClass,
+            debugFeatureFile=debugFeatureFile,
         )
 
     postProcessor = PostProcessor(otf, ufo, glyphSet=glyphSet)
@@ -157,6 +159,7 @@ def compileTTF(
     inplace=False,
     layerName=None,
     skipExportGlyphs=None,
+    debugFeatureFile=None,
 ):
     """Create FontTools TrueType font from a UFO.
 
@@ -208,6 +211,7 @@ def compileTTF(
             glyphSet=glyphSet,
             featureWriters=featureWriters,
             featureCompilerClass=featureCompilerClass,
+            debugFeatureFile=debugFeatureFile,
         )
 
     postProcessor = PostProcessor(otf, ufo, glyphSet=glyphSet)
@@ -229,6 +233,7 @@ def compileInterpolatableTTFs(
     inplace=False,
     layerNames=None,
     skipExportGlyphs=None,
+    debugFeatureFile=None,
 ):
     """Create FontTools TrueType fonts from a list of UFOs with interpolatable
     outlines. Cubic curves are converted compatibly to quadratic curves using
@@ -291,12 +296,15 @@ def compileInterpolatableTTFs(
         # Only the default layer is likely to have all glyphs used in feature
         # code.
         if layerName is None:
+            if debugFeatureFile:
+                debugFeatureFile.write("\n### %s ###\n" % fontName)
             compileFeatures(
                 ufo,
                 ttf,
                 glyphSet=glyphSet,
                 featureWriters=featureWriters,
                 featureCompilerClass=featureCompilerClass,
+                debugFeatureFile=debugFeatureFile,
             )
 
         postProcessor = PostProcessor(ttf, ufo, glyphSet=glyphSet)
@@ -327,6 +335,7 @@ def compileInterpolatableTTFsFromDS(
     cubicConversionError=None,
     reverseDirection=True,
     inplace=False,
+    debugFeatureFile=None,
 ):
     """Create FontTools TrueType fonts from the DesignSpaceDocument UFO sources
     with interpolatable outlines. Cubic curves are converted compatibly to
@@ -378,6 +387,7 @@ def compileInterpolatableTTFsFromDS(
         inplace=inplace,
         layerNames=layerNames,
         skipExportGlyphs=skipExportGlyphs,
+        debugFeatureFile=debugFeatureFile,
     )
 
     if inplace:
@@ -400,6 +410,7 @@ def compileInterpolatableOTFsFromDS(
     useProductionNames=None,
     roundTolerance=None,
     inplace=False,
+    debugFeatureFile=None,
 ):
     """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources
     with interpolatable outlines.
@@ -454,6 +465,7 @@ def compileInterpolatableOTFsFromDS(
                 overlapsBackend=None,
                 inplace=inplace,
                 skipExportGlyphs=skipExportGlyphs,
+                debugFeatureFile=debugFeatureFile,
                 _tables=SPARSE_OTF_MASTER_TABLES if source.layerName else None,
             )
         )
@@ -471,7 +483,12 @@ def compileInterpolatableOTFsFromDS(
 
 
 def compileFeatures(
-    ufo, ttFont=None, glyphSet=None, featureWriters=None, featureCompilerClass=None
+    ufo,
+    ttFont=None,
+    glyphSet=None,
+    featureWriters=None,
+    featureCompilerClass=None,
+    debugFeatureFile=None,
 ):
     """ Compile OpenType Layout features from `ufo` into FontTools OTL tables.
     If `ttFont` is None, a new TTFont object is created containing the new
@@ -485,6 +502,10 @@ def compileFeatures(
     If skipExportGlyphs is provided (see description in the ``compile*``
     functions), the feature compiler will prune groups (removing them if empty)
     and kerning of the UFO of these glyphs. The feature file is left untouched.
+
+    `debugFeatureFile` can be a file or file-like object opened in text mode,
+    in which to dump the text content of the feature file, useful for debugging
+    auto-generated OpenType features like kern, mark, mkmk etc.
     """
     if featureCompilerClass is None:
         if any(
@@ -497,7 +518,13 @@ def compileFeatures(
     featureCompiler = featureCompilerClass(
         ufo, ttFont, glyphSet=glyphSet, featureWriters=featureWriters
     )
-    return featureCompiler.compile()
+    otFont = featureCompiler.compile()
+
+    if debugFeatureFile:
+        if hasattr(featureCompiler, "writeFeatures"):
+            featureCompiler.writeFeatures(debugFeatureFile)
+
+    return otFont
 
 
 def compileVariableTTF(
@@ -513,6 +540,7 @@ def compileVariableTTF(
     excludeVariationTables=(),
     optimizeGvar=True,
     inplace=False,
+    debugFeatureFile=None,
 ):
     """Create FontTools TrueType variable font from the DesignSpaceDocument UFO sources
     with interpolatable outlines, using fontTools.varLib.build.
@@ -540,6 +568,7 @@ def compileVariableTTF(
         cubicConversionError=cubicConversionError,
         reverseDirection=reverseDirection,
         inplace=inplace,
+        debugFeatureFile=debugFeatureFile,
     )
 
     logger.info("Building variable TTF font")
@@ -565,6 +594,7 @@ def compileVariableCFF2(
     roundTolerance=None,
     excludeVariationTables=(),
     inplace=False,
+    debugFeatureFile=None,
 ):
     """Create FontTools CFF2 variable font from the DesignSpaceDocument UFO sources
     with interpolatable outlines, using fontTools.varLib.build.
@@ -588,6 +618,7 @@ def compileVariableCFF2(
         useProductionNames=False,  # will rename glyphs after varfont is built
         roundTolerance=roundTolerance,
         inplace=inplace,
+        debugFeatureFile=debugFeatureFile,
     )
 
     logger.info("Building variable CFF2 font")
diff --git a/Lib/ufo2ft/featureCompiler.py b/Lib/ufo2ft/featureCompiler.py
index a1d8876..8fbc2e7 100644
--- a/Lib/ufo2ft/featureCompiler.py
+++ b/Lib/ufo2ft/featureCompiler.py
@@ -223,6 +223,10 @@ class FeatureCompiler(BaseFeatureCompiler):
             # no featureWriters, simply read existing features' text
             self.features = tounicode(self.ufo.features.text or "", "utf-8")
 
+    def writeFeatures(self, outfile):
+        if hasattr(self, "features"):
+            outfile.write(self.features)
+
     def buildTables(self):
         """
         Compile OpenType feature tables from the source.

Support writing the final autogenerated feature file somewhere
Useful for debugging. Maybe a new parameter on all `compile*` functions that is a IOBuffer that the feature file is written to. Then fontmake can be wired up to pass something in.
diff --git a/tests/integration_test.py b/tests/integration_test.py
index 83bfcb8..4b6de3d 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -1,4 +1,5 @@
 from __future__ import print_function, division, absolute_import, unicode_literals
+import io
 from fontTools.misc.py23 import *
 from ufo2ft import (
     compileOTF,
@@ -157,6 +158,14 @@ class IntegrationTest(object):
             ),
         )
 
+    def test_debugFeatureFile(self, designspace):
+        tmp = io.StringIO()
+
+        varfont = compileVariableTTF(designspace, debugFeatureFile=tmp)
+
+        assert "### LayerFont-Regular ###" in tmp.getvalue()
+        assert "### LayerFont-Bold ###" in tmp.getvalue()
+
 
 if __name__ == "__main__":
     sys.exit(pytest.main(sys.argv))

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest>=2.8 tox>=2.3
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
attrs==22.2.0
booleanOperations==0.8.2
certifi==2021.5.30
compreffor==0.4.6.post1
cu2qu==1.6.5
defcon==0.6.0
distlib==0.3.9
filelock==3.4.1
fonttools==3.44.0
fs==2.4.16
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
lxml==4.9.4
packaging==21.3
platformdirs==2.4.0
pluggy==1.0.0
py==1.11.0
pyclipper==1.3.0.post6
pyparsing==3.1.4
pytest==7.0.1
six==1.17.0
toml==0.10.2
tomli==1.2.3
tox==3.28.0
typing_extensions==4.1.1
-e git+https://github.com/googlefonts/ufo2ft.git@de173acf4c160395bf192d8364bef1f2325e7b13#egg=ufo2ft
ufoLib2==0.3.2.post2
unicodedata2==16.0.0
virtualenv==20.17.1
zipp==3.6.0

name: ufo2ft
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - attrs==22.2.0
      - booleanoperations==0.8.2
      - compreffor==0.4.6.post1
      - cu2qu==1.6.5
      - defcon==0.6.0
      - distlib==0.3.9
      - filelock==3.4.1
      - fonttools==3.44.0
      - fs==2.4.16
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - lxml==4.9.4
      - packaging==21.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyclipper==1.3.0.post6
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==1.2.3
      - tox==3.28.0
      - typing-extensions==4.1.1
      - ufolib2==0.3.2.post2
      - unicodedata2==16.0.0
      - virtualenv==20.17.1
      - zipp==3.6.0
prefix: /opt/conda/envs/ufo2ft

tests/integration_test.py::IntegrationTest::test_debugFeatureFile[defcon] tests/integration_test.py::IntegrationTest::test_debugFeatureFile[ufoLib2]
tests/integration_test.py::IntegrationTest::test_removeOverlaps_CFF_pathops[defcon] tests/integration_test.py::IntegrationTest::test_removeOverlaps_pathops[defcon] tests/integration_test.py::IntegrationTest::test_removeOverlaps_CFF_pathops[ufoLib2] tests/integration_test.py::IntegrationTest::test_removeOverlaps_pathops[ufoLib2]
tests/integration_test.py::IntegrationTest::test_TestFont_TTF[defcon] tests/integration_test.py::IntegrationTest::test_TestFont_CFF[defcon] tests/integration_test.py::IntegrationTest::test_included_features[defcon] tests/integration_test.py::IntegrationTest::test_mti_features[defcon] tests/integration_test.py::IntegrationTest::test_removeOverlaps_CFF[defcon] tests/integration_test.py::IntegrationTest::test_removeOverlaps[defcon] tests/integration_test.py::IntegrationTest::test_interpolatableTTFs_lazy[defcon] tests/integration_test.py::IntegrationTest::test_optimizeCFF_none[defcon] tests/integration_test.py::IntegrationTest::test_optimizeCFF_specialize[defcon] tests/integration_test.py::IntegrationTest::test_optimizeCFF_subroutinize[defcon] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[defcon-None] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[defcon-True] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[defcon-False] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[defcon-None] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[defcon-True] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[defcon-False] tests/integration_test.py::IntegrationTest::test_TestFont_TTF[ufoLib2] tests/integration_test.py::IntegrationTest::test_TestFont_CFF[ufoLib2] tests/integration_test.py::IntegrationTest::test_included_features[ufoLib2] tests/integration_test.py::IntegrationTest::test_mti_features[ufoLib2] tests/integration_test.py::IntegrationTest::test_removeOverlaps_CFF[ufoLib2] tests/integration_test.py::IntegrationTest::test_removeOverlaps[ufoLib2] tests/integration_test.py::IntegrationTest::test_interpolatableTTFs_lazy[ufoLib2] tests/integration_test.py::IntegrationTest::test_optimizeCFF_none[ufoLib2] tests/integration_test.py::IntegrationTest::test_optimizeCFF_specialize[ufoLib2] tests/integration_test.py::IntegrationTest::test_optimizeCFF_subroutinize[ufoLib2] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[ufoLib2-None] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[ufoLib2-True] tests/integration_test.py::IntegrationTest::test_compileVariableTTF[ufoLib2-False] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[ufoLib2-None] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[ufoLib2-True] tests/integration_test.py::IntegrationTest::test_compileVariableCFF2[ufoLib2-False]
Lib/ufo2ft/featureCompiler.py Lib/ufo2ft/__init__.py
Lib/ufo2ft/featureCompiler.py Lib/ufo2ft/__init__.py