diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,7 @@
+# 0.2.2
+
+- Add `getDirectoryContentsWithFilterRecursive` for recursively listing directory contents with commonly needed filtering
+
 # 0.2.1
 
 - Fix `listContentsRecFold` to not mask exceptions unnecessarily which could cause hangups. The `getDirectoryContentsRecursive` gets the fix as well
diff --git a/directory-ospath-streaming.cabal b/directory-ospath-streaming.cabal
--- a/directory-ospath-streaming.cabal
+++ b/directory-ospath-streaming.cabal
@@ -5,7 +5,7 @@
 name:
   directory-ospath-streaming
 version:
-  0.2.1
+  0.2.2
 synopsis:
   Stream directory entries in constant memory in vanilla IO
 description:
diff --git a/src/System/Directory/OsPath/Contents.hs b/src/System/Directory/OsPath/Contents.hs
--- a/src/System/Directory/OsPath/Contents.hs
+++ b/src/System/Directory/OsPath/Contents.hs
@@ -11,6 +11,7 @@
 
 module System.Directory.OsPath.Contents
   ( getDirectoryContentsRecursive
+  , getDirectoryContentsWithFilterRecursive
 
   , listContentsRecFold
   ) where
@@ -43,6 +44,41 @@
     (\_ _ (Relative path) _ ft -> pure (Just (path, ft)))
     (Just root)
 
+-- | Recursively list all the files and directories that satisfy given
+-- predicate in a directory and all subdirectories. Descending into
+-- some subdirectories may be avoided by filtering them out with a
+-- visiting predicate.
+--
+-- Not visited directory entry may still be reported depending on the
+-- collection predicate.
+--
+-- The directory structure is traversed depth-first.
+--
+-- The result is generated lazily so is not well defined if the source
+-- directory structure changes before the list is fully consumed.
+--
+-- Symlinks within directory structure may cause result to be infinitely long, but
+-- they can be filtered out with a suitable directory visiting predicate.
+getDirectoryContentsWithFilterRecursive
+  :: (Basename OsPath -> SymlinkType -> Bool) -- ^ Whether to visit a directory
+  -> (Basename OsPath ->                Bool) -- ^ Whether to collect given directory element, either file or directory.
+  -> OsPath
+  -> IO [(OsPath, FileType)]
+getDirectoryContentsWithFilterRecursive visitPred collectPred root =
+  listContentsRecFold'
+    Nothing
+    (\_ _ (Relative path) basename ft symlink cons prependSubdir rest ->
+       (if collectPred basename then cons (path, ft) else id) $
+         if visitPred basename symlink
+         then prependSubdir rest
+         else rest)
+    (\_ _ (Relative path) basename ft ->
+      pure $
+        if collectPred basename
+        then Just (path, ft)
+        else Nothing)
+    (Just root)
+
 {-# INLINE listContentsRecFold #-}
 -- | The most general form of gathering directory contents.
 --
@@ -90,7 +126,7 @@
 
 {-# INLINE listContentsRecFold' #-}
 -- Actual worker with slightly worse type signature that we don’t want to expose to the users.
--- But it’s better candidate for implementing getDirectoryContentsRecursive here that
+-- But it’s better candidate for implementing getDirectoryContentsRecursive here than
 -- listContentsRecFold.
 listContentsRecFold'
   :: forall f a b. (Foldable f, Coercible b OsPath)
diff --git a/src/System/Directory/OsPath/Streaming.hs b/src/System/Directory/OsPath/Streaming.hs
--- a/src/System/Directory/OsPath/Streaming.hs
+++ b/src/System/Directory/OsPath/Streaming.hs
@@ -23,6 +23,7 @@
 
   -- * Get directory contents
   , getDirectoryContentsRecursive
+  , getDirectoryContentsWithFilterRecursive
 
   , listContentsRecFold
 
diff --git a/test/TestMain.hs b/test/TestMain.hs
--- a/test/TestMain.hs
+++ b/test/TestMain.hs
@@ -52,17 +52,31 @@
           getFileType [osp|test|] >>= (@?= Directory Regular)
       ]
 
-  , testCase "getDirectoryContentsRecursive" $ do
-    res <- L.sort <$> getDirectoryContentsRecursive [osp|test/filesystem|]
-    res @?= [([osp|bar.txt|], File Regular), ([osp|baz.txt|], File Regular), ([osp|bin|], Directory Regular), ([osp|bin|] </> [osp|bin.txt|], File Regular), ([osp|foo.txt|], File Regular)]
+  , testGroup "contents"
+      [ testCase "getDirectoryContentsRecursive" $ do
+          res <- L.sort <$> getDirectoryContentsRecursive [osp|test/filesystem|]
+          res @?= [([osp|bar.txt|], File Regular), ([osp|baz.txt|], File Regular), ([osp|bin|], Directory Regular), ([osp|bin|] </> [osp|bin.txt|], File Regular), ([osp|foo.txt|], File Regular)]
 
+      , testCase "getDirectoryContentsWithFilterRecursive 1" $ do
+          res <- L.sort <$> getDirectoryContentsWithFilterRecursive (\_ _ -> True) (const True) [osp|test/filesystem|]
+          res @?= [([osp|bar.txt|], File Regular), ([osp|baz.txt|], File Regular), ([osp|bin|], Directory Regular), ([osp|bin|] </> [osp|bin.txt|], File Regular), ([osp|foo.txt|], File Regular)]
+
+      , testCase "getDirectoryContentsWithFilterRecursive 2" $ do
+          res <- L.sort <$> getDirectoryContentsWithFilterRecursive (\x _ -> x /= Basename [osp|bin|]) (const True) [osp|test/filesystem|]
+          res @?= [([osp|bar.txt|], File Regular), ([osp|baz.txt|], File Regular), ([osp|bin|], Directory Regular), ([osp|foo.txt|], File Regular)]
+
+      , testCase "getDirectoryContentsWithFilterRecursive 3" $ do
+          res <- L.sort <$> getDirectoryContentsWithFilterRecursive (\_ _ -> True) (`elem` [Basename [osp|foo.txt|], Basename [osp|bin|], Basename [osp|bin.txt|]]) [osp|test/filesystem|]
+          res @?= [([osp|bin|], Directory Regular), ([osp|bin|] </> [osp|bin.txt|], File Regular), ([osp|foo.txt|], File Regular)]
+      ]
+
 #ifndef mingw32_HOST_OS
   , withResource
       (do
         tmp <- getTemporaryDirectory >>= canonicalizePath
         createFreshTempDir tmp [osp|test|])
       removeDirectoryRecursive
-      $ \mkTmpDir -> testGroup "getFileType unix"
+    $ \mkTmpDir -> testGroup "getFileType unix"
         [ testCase "file symlink" $ do
             tmp     <- mkTmpDir
             currDir <- getCurrentDirectory
