diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,8 +1,14 @@
 # Changelog for prairie
 
+## 0.0.3.0
+
+- [#8](https://github.com/parsonsmatt/prairie/pull/8)
+    - The `Prairie.Fold` module is introduced, allowing you to fold records.
+    - The `Prairie.Traverse` module is introduced, allowing you to traverse over records.
+
 ## 0.0.2.1
 
-- [#3](https://github.com/parsonsmatt/prairie/pull/6)
+- [#6](https://github.com/parsonsmatt/prairie/pull/6)
     - Bump upper bound for `TemplateHaskell`, supporting up to GHC 9.8
     - Fix string literals in docs that were quoted as module names
 
diff --git a/prairie.cabal b/prairie.cabal
--- a/prairie.cabal
+++ b/prairie.cabal
@@ -1,7 +1,7 @@
 cabal-version: 1.12
 
 name:           prairie
-version:        0.0.2.1
+version:        0.0.3.0
 description:    Please see the README on GitHub at <https://github.com/parsonsmatt/prairie#readme>
 homepage:       https://github.com/parsonsmatt/prairie#readme
 bug-reports:    https://github.com/parsonsmatt/prairie/issues
@@ -27,6 +27,8 @@
       Prairie.Class
       Prairie.Update
       Prairie.Diff
+      Prairie.Fold
+      Prairie.Traverse
       Prairie.TH
 
   build-depends:
@@ -87,4 +89,5 @@
     , prairie
     , aeson
     , lens
+    , hspec
   default-language: Haskell2010
diff --git a/src/Prairie.hs b/src/Prairie.hs
--- a/src/Prairie.hs
+++ b/src/Prairie.hs
@@ -5,10 +5,14 @@
     ( module Prairie.Class
     , module Prairie.Update
     , module Prairie.Diff
+    , module Prairie.Fold
+    , module Prairie.Traverse
     , module Prairie.TH
     ) where
 
 import Prairie.Class
-import Prairie.Update
 import Prairie.Diff
+import Prairie.Fold
 import Prairie.TH
+import Prairie.Traverse
+import Prairie.Update
diff --git a/src/Prairie/Fold.hs b/src/Prairie/Fold.hs
new file mode 100644
--- /dev/null
+++ b/src/Prairie/Fold.hs
@@ -0,0 +1,108 @@
+-- | This module shows you how to fold a 'Record'.
+--
+-- These utilities are based on 'recordToFieldList', which converts
+-- a 'Record' into a 'SomeFieldWithValue'. Then, 'foldRecord' unpacks that
+-- GADT for you, which allows you to know the type of the field and combine
+-- them.
+--
+-- @since 0.0.3.0
+module Prairie.Fold where
+
+import Control.Monad (foldM)
+import Prairie.Class
+import Data.List (foldl')
+
+-- | A datatype containing a 'Field' along with a value for that field.
+--
+-- @since 0.0.3.0
+data SomeFieldWithValue rec where
+    SomeFieldWithValue :: Field rec a -> a -> SomeFieldWithValue rec
+
+-- | Convert a 'Record' into a list of the records fields paired with the
+-- value for that record.
+--
+-- @since 0.0.3.0
+recordToFieldList :: forall rec. (Record rec) => rec -> [SomeFieldWithValue rec]
+recordToFieldList rec =
+    fmap
+        (\(SomeField field) ->
+            SomeFieldWithValue field (getRecordField field rec)
+        )
+        (allFields @rec)
+
+-- | Fold over the fields of a record to produce a final result.
+--
+-- The function parameter accepts the @'Field' rec ty@, a value in the
+-- record, and the accumulator. Example:
+--
+-- @
+-- 'foldRecord'
+--      (\\ val acc field ->
+--          case field of
+--              UserName ->
+--                  length val + acc
+--              UserAge ->
+--                  val + acc
+--      )
+--      0
+--      User { userName = \"Matt\", userAge = 35 }
+-- @
+--
+-- The paramater list is given to enable 'LambdaCase' nicety:
+--
+-- @
+-- 'foldRecord'
+--      (\\val acc -> \\case
+--          UserName ->
+--              length val + acc
+--          UserAge ->
+--              val + acc
+--      )
+-- @
+--
+-- @since 0.0.3.0
+foldRecord
+    :: forall rec r
+     . (Record rec)
+    => (forall ty. ty -> r -> Field rec ty -> r)
+    -> r
+    -> rec
+    -> r
+foldRecord k init rec =
+    foldl'
+        (\acc (SomeFieldWithValue field value) ->
+            k value acc field
+        )
+        init
+        (recordToFieldList rec)
+
+-- | Fold over a 'Record' with a monadic action.
+--
+-- @since 0.0.3.0
+foldMRecord
+    :: forall rec m r
+     . (Record rec, Monad m)
+    => (forall ty. ty -> r -> Field rec ty -> m r)
+    -> r
+    -> rec
+    -> m r
+foldMRecord k init rec =
+    foldM
+        (\acc (SomeFieldWithValue field value) ->
+            k value acc field
+        )
+        init
+        (recordToFieldList rec)
+
+-- | Convert each field of a 'Record' into a monoidal value and combine
+-- them together using 'mappend'.
+--
+-- @since 0.0.3.0
+foldMapRecord
+    :: forall rec m
+    . (Record rec, Monoid m)
+    => (forall ty. ty -> Field rec ty -> m)
+    -> rec
+    -> m
+foldMapRecord k rec =
+    foldMap (\(SomeFieldWithValue f v) -> k v f) (recordToFieldList rec)
diff --git a/src/Prairie/Traverse.hs b/src/Prairie/Traverse.hs
new file mode 100644
--- /dev/null
+++ b/src/Prairie/Traverse.hs
@@ -0,0 +1,66 @@
+-- | This module shows how to 'traverse' over a 'Record', allowing you to
+-- perform side-effects for each field and return a transformed value.
+--
+-- @since 0.0.3.0
+module Prairie.Traverse where
+
+import Data.List (foldl')
+import Prairie.Class
+import Control.Applicative (liftA2)
+import Prairie.Fold
+
+-- | Apply an effectful function over each field of a 'Record', producing
+-- a new 'Record' in the process.
+--
+-- This example use increments a @User@s age and requests a new name.
+--
+-- @
+-- happyBirthday :: User -> IO User
+-- happyBirthday =
+--     traverseRecord
+--          (\\val field ->
+--              case field of
+--                  UserName -> do
+--                      putStrLn $ "Current name is: " <> val
+--                      putStrLn "Please input a new name: "
+--                      getLine
+--                  UserAge -> do
+--                      putStrLn $ "Current age is " <> show val
+--                      pure (val + 1)
+--          )
+-- @
+--
+-- If you only want to target a single field, you can use a wildcard match
+-- and `pure`. This example also uses @LambdaCase@.
+--
+-- @
+-- nameAtLeastOneCharacter :: User -> 'Maybe' User
+-- nameAtLeastOneCharacter =
+--      'traverseRecord'
+--          (\val ->
+--              \case
+--                  UserName -> do
+--                      'guard' ('length' val >= 1)
+--                      'pure' val
+--                  _ ->
+--                      'pure' val
+--          )
+-- @
+--
+-- @since 0.0.3.0
+traverseRecord
+    :: forall rec f. (Record rec, Applicative f)
+    => (forall ty. ty -> Field rec ty -> f ty)
+    -> rec
+    -> f rec
+traverseRecord f init =
+    foldl' k (pure init) (recordToFieldList init)
+  where
+    f' :: Field rec ty -> ty -> f (SomeFieldWithValue rec)
+    f' field val =
+        SomeFieldWithValue field <$> f val field
+    k frec (SomeFieldWithValue field val) =
+        liftA2
+            (\(SomeFieldWithValue field' val') rec -> setRecordField field' val' rec)
+            (f' field val)
+            frec
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,23 +1,40 @@
-{-# language PolyKinds, LambdaCase, TypeApplications, RankNTypes, StandaloneDeriving, ConstraintKinds, TemplateHaskell, DataKinds, OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, TypeFamilies, GADTs #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 {-# options_ghc -Wall #-}
+
 module Main where
 
 import Prairie
 
-import Data.Aeson
-import Control.Monad
 import Control.Lens
+import Control.Monad
+import Data.Aeson
 import Data.Kind (Type)
+import Data.Monoid
+import Test.Hspec
 
 data User = User { name :: String, age :: Int }
-  deriving Eq
+  deriving (Show, Eq)
 
 mkRecord ''User
 
 deriving instance Eq (Field User a)
+deriving instance Show (Field User a)
 
-example = User "Alice" 30
+exampleUser = User "Alice" 30
 
 data T a = T { x :: a, y :: Int }
 
@@ -48,44 +65,135 @@
     FieldLens (Field (t x) x) y f = LensLike f (t x) (t y) x y
     FieldLens (Field (t x) y) y f = LensLike f (t x) (t x) y y
 
-assert :: String -> Bool -> IO ()
-assert message success = unless success (error message)
-
 main :: IO ()
-main = do
-    assert "getField" $ getRecordField UserName example == "Alice"
-    assert "setField" $ setRecordField UserAge 32 example == User "Alice" 32
-    assert "label" $ recordFieldLabel UserAge == "age"
-    assert "label" $ recordFieldLabel UserName == "name"
+main = hspec $ do
+    describe "Prairie" $ do
+        it "getField" $ do
+            getRecordField UserName exampleUser `shouldBe` "Alice"
+        it "setField" $ do
+            setRecordField UserAge 32 exampleUser `shouldBe` User { name = "Alice", age = 32 }
+        it "label" $ do
+            recordFieldLabel UserAge `shouldBe` "age"
+        it "label" $ do
+            recordFieldLabel UserName `shouldBe` "name"
 
-    let t :: T Int
-        t = T 3 2
+        let t :: T Int
+            t = T 3 2
 
-        t' :: T Char
-        t' = t & polyLens TX .~ 'a'
+            t' :: T Char
+            t' = t & polyLens TX .~ 'a'
 
-    assert "update json" $
-        encode (diffRecord example (setRecordField UserName "Bob" example))
-        ==
-        "[{\"field\":\"name\",\"value\":\"Bob\"}]"
+        it "update json" $
+            encode (diffRecord exampleUser (setRecordField UserName "Bob" exampleUser))
+            `shouldBe`
+            "[{\"field\":\"name\",\"value\":\"Bob\"}]"
 
-    assert "decode update" $
-      decode "[{\"field\":\"name\",\"value\":\"Bob\"}]"
-      ==
-      Just [SetField UserName "Bob"]
+        it "decode update" $
+          decode "[{\"field\":\"name\",\"value\":\"Bob\"}]"
+          `shouldBe`
+          Just [SetField UserName "Bob"]
 
-    user' <-
-      tabulateRecordA $ \case
-          UserName ->
-              print 10 >> pure "Matt"
-          UserAge ->
-              print 20 >> pure 33
-    assert "tabulateRecordA" $
-        user'
-        ==
-        User
-            { name = "Matt"
-            , age = 33
-            }
+        it "tabulateRecordA" $ do
+            user' <-
+              tabulateRecordA $ \case
+                  UserName ->
+                      print 10 >> pure "Matt"
+                  UserAge ->
+                      print 20 >> pure 33
+            user'
+                `shouldBe`
+                User
+                    { name = "Matt"
+                    , age = 33
+                    }
 
+        describe "Fold" $ do
+            describe "foldRecord" $ do
+                it "can count the fields" $ do
+                    foldRecord (\_val acc _field -> acc + 1) 0 exampleUser
+                        `shouldBe`
+                            2
+                it "can distinguish fields" $ do
+                    foldRecord
+                        (\val acc field ->
+                            case field of
+                                UserName ->
+                                    length val + acc
+                                UserAge ->
+                                    val + acc
+                        )
+                        (0 :: Int)
+                        exampleUser
+                        `shouldBe`
+                            35
 
+            describe "foldMapRecord" $ do
+                it "can count the fields" $ do
+                    foldMapRecord (\_ _ -> Sum 1) exampleUser
+                        `shouldBe`
+                            Sum 2
+                it "can combine strings" $ do
+                    foldMapRecord
+                        (\val ->
+                            \case
+                                UserName -> val
+                                UserAge -> show val
+                        )
+                        exampleUser
+                        `shouldBe`
+                            ("Alice30" :: String)
+
+        describe "Traverse" $ do
+            it "can validate a record" $ do
+                let validateField :: ty -> Field User ty -> Maybe ty
+                    validateField val =
+                        \case
+                            UserName -> do
+                                guard (length val >= 1)
+                                pure val
+                            UserAge -> do
+                                guard (val >= 18)
+                                pure val
+                traverseRecord validateField exampleUser
+                    `shouldBe`
+                        Just User { name = "Alice", age = 30 }
+
+                traverseRecord validateField User { name = "", age = 1 }
+                    `shouldBe`
+                        Nothing
+
+
+            it "can do either" $ do
+                let validateField :: ty -> Field User ty -> Either String ty
+                    validateField val =
+                        \case
+                            UserName -> do
+                                if length val >= 1
+                                    then pure val
+                                    else Left "Name must be at least one character"
+                            UserAge -> do
+                                if val >= 18
+                                    then pure val
+                                    else Left "Age must be at least 18"
+
+                traverseRecord validateField exampleUser
+                    `shouldBe`
+                        Right User { name = "Alice", age = 30 }
+
+                traverseRecord validateField User { name = "", age = 1 }
+                    `shouldBe`
+                        Left "Age must be at least 18"
+
+            it "can target one field" $ do
+                traverseRecord
+                    (\val ->
+                        \case
+                            UserName -> do
+                                guard (length val >= 1)
+                                pure val
+                            _ ->
+                                pure val
+                    )
+                    User { name = "", age = 30 }
+                    `shouldBe`
+                        Nothing
