diff --git a/Database/SQLite/Simple.hs b/Database/SQLite/Simple.hs
--- a/Database/SQLite/Simple.hs
+++ b/Database/SQLite/Simple.hs
@@ -75,6 +75,8 @@
   , execute_
   , executeNamed
   , field
+    -- * Transactions
+  , withTransaction
     -- * Low-level statement API for stream access and prepared statements
   , openStatement
   , closeStatement
@@ -329,6 +331,12 @@
 
 -- | A version of 'query' where the query parameters (placeholders)
 -- are named.
+--
+-- Example:
+--
+-- @
+-- r \<- 'queryNamed' c \"SELECT * FROM posts WHERE id=:id AND date>=:date\" [\":id\" ':=' postId, \":date\" ':=' afterDate]
+-- @
 queryNamed :: (FromRow r) => Connection -> Query -> [NamedParam] -> IO [r]
 queryNamed conn templ params =
   withStatementNamedParams conn templ params $ \stmt -> doFoldToList stmt
@@ -452,6 +460,22 @@
 lastInsertRowId :: Connection -> IO Int64
 lastInsertRowId (Connection c) = BaseD.lastInsertRowId c
 
+-- | Run an IO action inside a SQL transaction started with @BEGIN
+-- TRANSACTION@.  If the action throws any kind of an exception, the
+-- transaction will be rolled back with @ROLLBACK TRANSACTION@.
+-- Otherwise the results are committed with @COMMIT TRANSACTION@.
+withTransaction :: Connection -> IO a -> IO a
+withTransaction conn action =
+  mask $ \restore -> do
+    begin
+    r <- restore action `onException` rollback
+    commit
+    return r
+  where
+    begin    = execute_ conn "BEGIN TRANSACTION"
+    commit   = execute_ conn "COMMIT TRANSACTION"
+    rollback = execute_ conn "ROLLBACK TRANSACTION"
+
 fmtError :: Show v => String -> Query -> [v] -> a
 fmtError msg q xs =
   throw FormatError {
@@ -587,14 +611,14 @@
 -- Example:
 --
 -- @
--- r \<- 'queryNamed' c \"SELECT id,text FROM posts WHERE id = :id AND date >= :date\" [\":id\" ':=' postId, \":date\" := afterDate]
+-- r \<- 'queryNamed' c \"SELECT id,text FROM posts WHERE id = :id AND date >= :date\" [\":id\" ':=' postId, \":date\" ':=' afterDate]
 -- @
 --
 -- Note that you can mix different value types in the same list.
 -- E.g., the following is perfectly legal:
 --
 -- @
--- [\":id\" := (3 :: Int), \":str\" := (\"foo\" :: String)]
+-- [\":id\" ':=' (3 :: Int), \":str\" ':=' (\"foo\" :: String)]
 -- @
 --
 -- The parameter name (or key) in the 'NamedParam' must match exactly
diff --git a/Database/SQLite/Simple/ToRow.hs b/Database/SQLite/Simple/ToRow.hs
--- a/Database/SQLite/Simple/ToRow.hs
+++ b/Database/SQLite/Simple/ToRow.hs
@@ -26,11 +26,11 @@
 
 import Database.SQLite3 (SQLData(..))
 
--- | A collection type that can be turned into a list of SQLData
+-- | A collection type that can be turned into a list of 'SQLData'
 -- elements.
 class ToRow a where
     toRow :: a -> [SQLData]
-    -- ^ ToField a collection of values.
+    -- ^ 'ToField' a collection of values.
 
 instance ToRow () where
     toRow _ = []
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,6 @@
+0.4.7.0
+	* Add `withTransaction' for running IO actions inside SQL
+	  transactions with automated rollback if any exceptions are thrown.
 0.4.6.1
 	* Fix unit test build break with older bytestring versions
 
diff --git a/sqlite-simple.cabal b/sqlite-simple.cabal
--- a/sqlite-simple.cabal
+++ b/sqlite-simple.cabal
@@ -1,10 +1,10 @@
 Name:                sqlite-simple
-Version:             0.4.6.1
+Version:             0.4.7.0
 Synopsis:            Mid-Level SQLite client library
 Description:
     Mid-level SQLite client library, based on postgresql-simple.
     .
-    Main documentation (with examples): <docs/Database-SQLite-Simple.html Database.SQLite.Simple>
+    Main documentation (with examples): <sqlite-simple/docs/Database-SQLite-Simple.html Database.SQLite.Simple>
     .
     You can view the project page at <http://github.com/nurpax/sqlite-simple>
     for more information.
diff --git a/test/Errors.hs b/test/Errors.hs
--- a/test/Errors.hs
+++ b/test/Errors.hs
@@ -6,6 +6,7 @@
   , testErrorsInvalidNamedParams
   , testErrorsWithStatement
   , testErrorsColumnName
+  , testErrorsTransaction
   ) where
 
 import Prelude hiding (catch)
@@ -99,3 +100,34 @@
   assertOOBCaught $
     withStatement conn "SELECT id FROM invcolumn" $ \stmt ->
       columnName stmt (ColumnIndex (-1)) >> assertFailure "Error not detected"
+
+testErrorsTransaction :: TestEnv -> Test
+testErrorsTransaction TestEnv{..} = TestCase $ do
+  execute_ conn "CREATE TABLE trans (id INTEGER PRIMARY KEY, t TEXT)"
+  withTransaction conn $ do
+    executeNamed conn "INSERT INTO trans (t) VALUES (:txt)" [":txt" := ("foo" :: String)]
+  e <- rowExists
+  True @=? e
+  execute_ conn "DELETE FROM trans"
+  e <- rowExists
+  False @=? e
+  assertFormatErrorCaught
+    (withTransaction conn $ do
+        -- this execute should be automatically rolled back on error
+        executeNamed conn
+          "INSERT INTO trans (t) VALUES (:txt)" [":txt" := ("foo" :: String)]
+        -- intentional mistake here to hit an error & cause rollback of txn
+        executeNamed conn
+          "INSERT INTO trans (t) VALUES (:txt)" [":missing" := ("foo" :: String)])
+  e <- rowExists
+  False @=? e
+  where
+    rowExists = do
+      rows <- query_ conn "SELECT t FROM trans" :: IO [Only String]
+      case rows of
+        [Only txt] -> do
+          "foo" @=? txt
+          return True
+        [] ->
+          return False
+        _ -> error "should have only one row"
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -46,6 +46,7 @@
     , TestLabel "Errors"    . testErrorsInvalidNamedParams
     , TestLabel "Errors"    . testErrorsWithStatement
     , TestLabel "Errors"    . testErrorsColumnName
+    , TestLabel "Errors"    . testErrorsTransaction
     , TestLabel "Utf8"      . testUtf8Simplest
     , TestLabel "Utf8"      . testBlobs
     , TestLabel "Instances" . testUserFromField
