diff --git a/pipes-bytestring.cabal b/pipes-bytestring.cabal
--- a/pipes-bytestring.cabal
+++ b/pipes-bytestring.cabal
@@ -1,5 +1,5 @@
 Name: pipes-bytestring
-Version: 2.1.5
+Version: 2.1.6
 Cabal-Version: >=1.8.0.2
 Build-Type: Simple
 License: BSD3
@@ -23,6 +23,7 @@
         pipes        >= 4.0     && < 4.4 ,
         pipes-group  >= 1.0.0   && < 1.1 ,
         pipes-parse  >= 3.0.0   && < 3.1 ,
+        stringsearch >= 0.3.0   && < 0.4 ,
         transformers >= 0.2.0.0 && < 0.6
     Exposed-Modules: Pipes.ByteString
     GHC-Options: -O2 -Wall
diff --git a/src/Pipes/ByteString.hs b/src/Pipes/ByteString.hs
--- a/src/Pipes/ByteString.hs
+++ b/src/Pipes/ByteString.hs
@@ -110,6 +110,7 @@
     , splitAt
     , span
     , break
+    , breakOn
     , groupBy
     , group
     , word
@@ -127,6 +128,7 @@
     , chunksOf
     , splitsWith
     , splits
+    , splitOn
     , groupsBy
     , groups
     , lines
@@ -150,9 +152,11 @@
 import Data.ByteString (ByteString)
 import Data.ByteString.Internal (isSpaceWord8)
 import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Search
 import Data.ByteString.Lazy.Internal (foldrChunks, defaultChunkSize)
 import Data.ByteString.Unsafe (unsafeTake)
 import Data.Char (ord)
+import Data.Monoid (mempty, (<>))
 import Data.Functor.Constant (Constant(Constant, getConstant))
 import Data.Functor.Identity (Identity)
 import qualified Data.List as List
@@ -709,6 +713,42 @@
 break predicate = span (not . predicate)
 {-# INLINABLE break #-}
 
+{-| Improper lens that splits at the first occurrence of the pattern.
+-}
+breakOn
+    :: Monad m
+    => ByteString
+    -> Lens' (Producer ByteString m x)
+             (Producer ByteString m (Producer ByteString m x))
+breakOn needle k p0 =
+    fmap join (k (go mempty p0))
+  where
+    len0 = BS.length needle
+
+    go leftovers p =
+        if BS.length leftovers < len0
+        then do
+            x <- lift (next p)
+            case x of
+                Left   r          -> do
+                    yield leftovers
+                    return (return r)
+                Right (bytes, p') -> do
+                    go (leftovers <> bytes) p'
+        else do
+            let (prefix, suffix) = Data.ByteString.Search.breakOn needle leftovers
+            if BS.null suffix
+                then do
+                    let len = BS.length leftovers
+                    let (output, leftovers') =
+                            BS.splitAt (len + 1 - len0) leftovers
+                    yield output
+                    go leftovers' p
+                else do
+                    yield prefix
+                    return (yield suffix >> p)
+{-# INLINABLE breakOn #-}
+
 {-| Improper lens that splits after the first group of matching bytes, as
     defined by the given equality predicate
 -}
@@ -909,6 +949,26 @@
 splits w8 k p =
     fmap (PG.intercalates (yield (BS.singleton w8))) (k (splitsWith (w8 ==) p))
 {-# INLINABLE splits #-}
+
+-- | Split a byte stream into groups separated by the given `ByteString`
+splitOn
+    :: Monad m
+    => ByteString
+    -> Lens' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+splitOn needle k p0 =
+    fmap
+        (PG.intercalates (yield needle))
+        (k (go p0))
+  where
+    len0 = BS.length needle
+    go p = PG.FreeT $ do
+        x <- next p
+        return $ case x of
+            Left   r       -> PG.Pure r
+            Right (bs, p') -> PG.Free $ do
+                p'' <- (yield bs >> p')^.(breakOn needle)
+                return (go (drop len0 p''))
+{-# INLINABLE splitOn #-}
 
 {-| Isomorphism between a byte stream and groups of identical bytes using the
     supplied equality predicate
