diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,11 @@
 # Revision history for reflex-fsnotify
 
+## 0.2.0.0
+
+* Deprecate `watchDirectory`
+* Add `watchDir` and `watchTree` corresponding to the functions in `System.FSNotify`
+* Add `watchDirectoryTree`, an alternative to `watchTree` that tries to break symlink cycles
+
 ## 0.1.0.0
 
 * Initial release containing `watchDirectory`.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
 # reflex-fsnotify
 
+[![hackage](https://img.shields.io/hackage/v/reflex-fsnotify.svg)](https://hackage.haskell.org/package/reflex-fsnotify) [![hackage-ci](https://matrix.hackage.haskell.org/api/v2/packages/reflex-fsnotify/badge)](https://matrix.hackage.haskell.org/#/package/reflex-fsnotify) [![travis-ci](https://api.travis-ci.org/reflex-frp/reflex-fsnotify.svg?branch=develop)](https://travis-ci.org/reflex-frp/reflex-fsnotify)
+
 Watch directories for changes using a functional-reactive interface!
diff --git a/reflex-fsnotify.cabal b/reflex-fsnotify.cabal
--- a/reflex-fsnotify.cabal
+++ b/reflex-fsnotify.cabal
@@ -1,6 +1,6 @@
 cabal-version: >=1.10
 name: reflex-fsnotify
-version: 0.1.0.0
+version: 0.2.0.0
 synopsis: reflex-frp interface for watching files
 description: Receive FRP events whenever files change.
 bug-reports: https://github.com/reflex-frp/reflex-fsnotify/issues
@@ -13,11 +13,14 @@
 build-type: Simple
 extra-source-files: ChangeLog.md
                     README.md
-tested-with: GHC ==8.6.5 || ==8.4.4
+tested-with: GHC ==8.6.5
 
 library
   exposed-modules: Reflex.FSNotify
   build-depends: base >=4.10 && <4.13
+               , containers >= 0.6 && < 0.7
+               , directory >= 1.3 && < 1.4
+               , filepath >= 1.4 && < 1.5
                , fsnotify >= 0.3 && < 0.4
                , reflex >= 0.5 && < 0.7
   hs-source-dirs: src
diff --git a/src/Reflex/FSNotify.hs b/src/Reflex/FSNotify.hs
--- a/src/Reflex/FSNotify.hs
+++ b/src/Reflex/FSNotify.hs
@@ -4,7 +4,14 @@
 -}
 
 {-# LANGUAGE FlexibleContexts #-}
-module Reflex.FSNotify where
+module Reflex.FSNotify
+  ( watchDirectory
+  , watchDir
+  , watchTree
+  , wrapWatch
+  , listDirectories
+  , watchDirectoryTree
+  ) where
 
 import Control.Concurrent
 import Control.Monad
@@ -12,16 +19,78 @@
 import Reflex
 import qualified System.FSNotify as FS
 
+import Data.Set (Set)
+import qualified Data.Set as Set
+import System.Directory
+import System.FilePath ((</>))
+
+-- A type synonym to disambiguate Reflex 'Event's from 'System.FSNotify.Event'
+type FSEvent = FS.Event
+
 -- | Watch a directory for changes
+{-# DEPRECATED watchDirectory "Use `watchDir cfg path (const True)` instead" #-}
 watchDirectory
   :: (Reflex t, TriggerEvent t m, PerformEvent t m, MonadIO (Performable m))
   => FS.WatchConfig
   -> Event t FilePath
   -> m (Event t FS.Event)
-watchDirectory cfg path =
+watchDirectory cfg path = watchDir cfg path (const True)
+
+wrapWatch
+  :: (Reflex t, TriggerEvent t m, PerformEvent t m, MonadIO (Performable m))
+  => (FS.WatchManager -> FilePath -> FS.Action -> IO a)
+  -> FS.WatchConfig
+  -> Event t FilePath
+  -> m (Event t FSEvent)
+wrapWatch f cfg path =
   performEventAsync $ ffor path $ \p cb -> liftIO $ void $ forkIO $
     FS.withManagerConf cfg $ \mgr -> do
-      _ <- FS.watchTree mgr p (const True) cb
+      _ <- f mgr p cb -- FS.watchTree mgr p (const True) cb
       forever $ threadDelay 1000000
 
+watchDir
+  :: (Reflex t, TriggerEvent t m, PerformEvent t m, MonadIO (Performable m))
+  => FS.WatchConfig
+  -> Event t FilePath
+  -> FS.ActionPredicate
+  -> m (Event t FSEvent)
+watchDir cfg path evFilter = wrapWatch (\mgr p -> FS.watchDir mgr p evFilter) cfg path
 
+watchTree
+  :: (Reflex t, TriggerEvent t m, PerformEvent t m, MonadIO (Performable m))
+  => FS.WatchConfig
+  -> Event t FilePath
+  -> FS.ActionPredicate
+  -> m (Event t FSEvent)
+watchTree cfg path evFilter = wrapWatch (\mgr p -> FS.watchTree mgr p evFilter) cfg path
+
+listDirectories
+  :: FilePath
+  -> IO (Set FilePath)
+listDirectories start = do
+  start' <- canonicalizePath start
+  Set.insert start' <$> listDirectories' Set.empty start'
+  where
+    listDirectories' :: Set FilePath -> FilePath -> IO (Set FilePath)
+    listDirectories' seen dir0 = do
+      let canonicalize p = canonicalizePath $ dir0 </> p
+      contents <- mapM canonicalize =<< listDirectory dir0
+      dirs <- filterM doesDirectoryExist contents
+      let newDirs = filter (not . flip Set.member seen) dirs
+          newSeen = Set.union seen $ Set.fromList newDirs
+      allDirs <- mapM (listDirectories' newSeen) newDirs
+      return $ Set.unions $ Set.fromList dirs : allDirs
+
+-- | Like 'watchTree' except that it tries to avoid symlink loops and calls
+-- 'watchDir' on each directory found
+watchDirectoryTree
+  :: (Reflex t, TriggerEvent t m, PerformEvent t m, MonadIO (Performable m))
+  => FS.WatchConfig
+  -> Event t FilePath
+  -> FS.ActionPredicate
+  -> m (Event t FSEvent)
+watchDirectoryTree cfg root evFilter =
+  let f mgr p cb = do
+        dirs <- listDirectories p
+        mapM_ (\dir -> FS.watchDir mgr dir evFilter cb) dirs
+  in wrapWatch f cfg root
