diff --git a/CodeGen.hs b/CodeGen.hs
--- a/CodeGen.hs
+++ b/CodeGen.hs
@@ -5,14 +5,16 @@
 
 import Language.Haskell.TH.Syntax
 import Text.ParserCombinators.Parsec
-import qualified System.IO.UTF8 as U
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Text.Lazy as LT
+import qualified Data.Text.Lazy.Encoding as LT
 
 data Token = VarToken String | LitToken String | EmptyToken
 
 codegen :: FilePath -> Q Exp
 codegen fp = do
-    s' <- qRunIO $ U.readFile $ "scaffold/" ++ fp ++ ".cg"
-    let s = init s'
+    s' <- qRunIO $ L.readFile $ "scaffold/" ++ fp ++ ".cg"
+    let s = init $ LT.unpack $ LT.decodeUtf8 s'
     case parse (many parseToken) s s of
         Left e -> error $ show e
         Right tokens' -> do
diff --git a/Yesod.hs b/Yesod.hs
--- a/Yesod.hs
+++ b/Yesod.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE CPP #-}
-{-# LANGUAGE PackageImports #-}
+-- | This module simply re-exports from other modules for your convenience.
 module Yesod
     ( module Yesod.Request
     , module Yesod.Content
@@ -13,8 +13,10 @@
     , Application
     , lift
     , liftIO
-    , MonadCatchIO
+    , MonadInvertIO
     , mempty
+    , showIntegral
+    , readIntegral
     ) where
 
 #if TEST
@@ -36,7 +38,16 @@
 import Yesod.Widget
 import Network.Wai (Application)
 import Yesod.Hamlet
-import "transformers" Control.Monad.Trans.Class (lift)
-import "transformers" Control.Monad.IO.Class (liftIO)
-import "MonadCatchIO-transformers" Control.Monad.CatchIO (MonadCatchIO)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.IO.Class (liftIO)
 import Data.Monoid (mempty)
+import Control.Monad.Invert (MonadInvertIO)
+
+showIntegral :: Integral a => a -> String
+showIntegral x = show (fromIntegral x :: Integer)
+
+readIntegral :: Num a => String -> Maybe a
+readIntegral s =
+    case reads s of
+        (i, _):_ -> Just $ fromInteger i
+        [] -> Nothing
diff --git a/Yesod/Content.hs b/Yesod/Content.hs
--- a/Yesod/Content.hs
+++ b/Yesod/Content.hs
@@ -63,7 +63,6 @@
 
 import qualified Data.Text.Encoding
 import qualified Data.Text.Lazy.Encoding
-import qualified Data.ByteString.Lazy.UTF8
 
 #if TEST
 import Test.Framework (testGroup, Test)
@@ -94,7 +93,7 @@
 instance ToContent Text where
     toContent = W.ResponseLBS . Data.Text.Lazy.Encoding.encodeUtf8
 instance ToContent String where
-    toContent = W.ResponseLBS . Data.ByteString.Lazy.UTF8.fromString
+    toContent = toContent . T.pack
 
 -- | A function which gives targetted representations of content based on the
 -- content-types the user accepts.
diff --git a/Yesod/Dispatch.hs b/Yesod/Dispatch.hs
--- a/Yesod/Dispatch.hs
+++ b/Yesod/Dispatch.hs
@@ -39,7 +39,6 @@
 import Web.Routes.Quasi.Parse
 import Web.Routes.Quasi.TH
 import Language.Haskell.TH.Syntax
-import Yesod.WebRoutes
 
 import qualified Network.Wai as W
 import Network.Wai.Middleware.CleanPath (cleanPathFunc)
@@ -52,8 +51,6 @@
 
 import qualified Data.ByteString.Char8 as B
 
-import qualified Data.ByteString.UTF8 as S
-
 import Control.Concurrent.MVar
 import Control.Arrow ((***))
 
@@ -70,7 +67,12 @@
 import Network.Wai.Parse hiding (FileInfo)
 import qualified Network.Wai.Parse as NWP
 import Data.String (fromString)
+import Web.Routes
+import Control.Arrow (first)
+import System.Random (randomR, newStdGen)
 
+import qualified Data.Map as Map
+
 #if TEST
 import Test.Framework (testGroup, Test)
 import Test.Framework.Providers.QuickCheck2 (testProperty)
@@ -225,7 +227,7 @@
     now <- getCurrentTime
     let getExpires m = fromIntegral (m * 60) `addUTCTime` now
     let exp' = getExpires $ clientSessionDuration y
-    let host = W.remoteHost env
+    let host = if sessionIpAddress y then W.remoteHost env else ""
     let session' = fromMaybe [] $ do
             raw <- lookup "Cookie" $ W.requestHeaders env
             val <- lookup (B.pack sessionName) $ parseCookies raw
@@ -265,13 +267,17 @@
     let eurl' = either (const Nothing) Just eurl
     let eh er = runHandler (errorHandler' er) render eurl' id y id
     let ya = runHandler h render eurl' id y id
-    (s, hs, ct, c, sessionFinal) <- unYesodApp ya eh rr types
-    let sessionVal = encodeSession key' exp' host sessionFinal
+    let sessionMap = Map.fromList
+                   $ filter (\(x, _) -> x /= nonceKey) session'
+    (s, hs, ct, c, sessionFinal) <- unYesodApp ya eh rr types sessionMap
+    let sessionVal = encodeSession key' exp' host
+                   $ Map.toList
+                   $ Map.insert nonceKey (reqNonce rr) sessionFinal
     let hs' = AddCookie (clientSessionDuration y) sessionName
-                                                  (S.toString sessionVal)
+                                                  (bsToChars sessionVal)
             : hs
         hs'' = map (headerToPair getExpires) hs'
-        hs''' = ("Content-Type", S.fromString ct) : hs''
+        hs''' = ("Content-Type", charsToBs ct) : hs''
     return $ W.Response s hs''' c
 
 httpAccept :: W.Request -> [ContentType]
@@ -313,13 +319,13 @@
                 -> [(String, String)] -- ^ session
                 -> IO Request
 parseWaiRequest env session' = do
-    let gets' = map (S.toString *** S.toString)
+    let gets' = map (bsToChars *** bsToChars)
               $ parseQueryString $ W.queryString env
     let reqCookie = fromMaybe B.empty $ lookup "Cookie"
                   $ W.requestHeaders env
-        cookies' = map (S.toString *** S.toString) $ parseCookies reqCookie
+        cookies' = map (bsToChars *** bsToChars) $ parseCookies reqCookie
         acceptLang = lookup "Accept-Language" $ W.requestHeaders env
-        langs = map S.toString $ maybe [] parseHttpAccept acceptLang
+        langs = map bsToChars $ maybe [] parseHttpAccept acceptLang
         langs' = case lookup langKey session' of
                     Nothing -> langs
                     Just x -> x : langs
@@ -330,13 +336,33 @@
                      Nothing -> langs''
                      Just x -> x : langs''
     rbthunk <- iothunk $ rbHelper env
-    return $ Request gets' cookies' session' rbthunk env langs'''
+    nonce <- case lookup nonceKey session' of
+                Just x -> return x
+                Nothing -> do
+                    g <- newStdGen
+                    return $ fst $ randomString 10 g
+    return $ Request gets' cookies' rbthunk env langs''' nonce
+  where
+    randomString len =
+        first (map toChar) . sequence' (replicate len (randomR (0, 61)))
+    sequence' [] g = ([], g)
+    sequence' (f:fs) g =
+        let (f', g') = f g
+            (fs', g'') = sequence' fs g'
+         in (f' : fs', g'')
+    toChar i
+        | i < 26 = toEnum $ i + fromEnum 'A'
+        | i < 52 = toEnum $ i + fromEnum 'a' - 26
+        | otherwise = toEnum $ i + fromEnum '0' - 52
 
+nonceKey :: String
+nonceKey = "_NONCE"
+
 rbHelper :: W.Request -> IO RequestBodyContents
 rbHelper = fmap (fix1 *** map fix2) . parseRequestBody lbsSink where
-    fix1 = map (S.toString *** S.toString)
+    fix1 = map (bsToChars *** bsToChars)
     fix2 (x, NWP.FileInfo a b c) =
-        (S.toString x, FileInfo (S.toString a) (S.toString b) c)
+        (bsToChars x, FileInfo (bsToChars a) (bsToChars b) c)
 
 -- | Produces a \"compute on demand\" value. The computation will be run once
 -- it is requested, and then the result will be stored. This will happen only
@@ -357,14 +383,14 @@
              -> (W.ResponseHeader, B.ByteString)
 headerToPair getExpires (AddCookie minutes key value) =
     let expires = getExpires minutes
-     in ("Set-Cookie", S.fromString
+     in ("Set-Cookie", charsToBs
                             $ key ++ "=" ++ value ++"; path=/; expires="
                               ++ formatW3 expires)
 headerToPair _ (DeleteCookie key) =
-    ("Set-Cookie", S.fromString $
+    ("Set-Cookie", charsToBs $
      key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
 headerToPair _ (Header key value) =
-    (fromString key, S.fromString value)
+    (fromString key, charsToBs value)
 
 encodeSession :: CS.Key
               -> UTCTime -- ^ expire time
diff --git a/Yesod/Form.hs b/Yesod/Form.hs
--- a/Yesod/Form.hs
+++ b/Yesod/Form.hs
@@ -11,6 +11,9 @@
     , Enctype (..)
     , FormFieldSettings (..)
     , Textarea (..)
+    , FieldInfo (..)
+      -- ** Utilities
+    , formFailures
       -- * Type synonyms
     , Form
     , Formlet
@@ -18,19 +21,24 @@
     , FormletField
     , FormInput
       -- * Unwrapping functions
+    , generateForm
     , runFormGet
+    , runFormMonadGet
     , runFormPost
+    , runFormPostNoNonce
+    , runFormMonadPost
     , runFormGet'
     , runFormPost'
       -- * Field/form helpers
     , fieldsToTable
+    , fieldsToDivs
     , fieldsToPlain
     , checkForm
+      -- * Type classes
+    , module Yesod.Form.Class
       -- * Template Haskell
     , mkToForm
-      -- * Re-exports
     , module Yesod.Form.Fields
-    , module Yesod.Form.Class
     ) where
 
 import Yesod.Form.Core
@@ -45,12 +53,9 @@
 import Data.Maybe (fromMaybe, mapMaybe)
 import "transformers" Control.Monad.IO.Class
 import Control.Monad ((<=<))
-import Control.Monad.Trans.State
-import Control.Monad.Trans.Reader
 import Language.Haskell.TH.Syntax
-import Database.Persist.Base (EntityDef (..))
+import Database.Persist.Base (EntityDef (..), PersistEntity (entityDef))
 import Data.Char (toUpper, isUpper)
-import Yesod.Widget
 import Control.Arrow ((&&&))
 import Data.List (group, sort)
 
@@ -63,37 +68,81 @@
 fieldsToTable :: FormField sub y a -> Form sub y a
 fieldsToTable = mapFormXml $ mapM_ go
   where
-    go fi = do
-        wrapWidget (fiInput fi) $ \w -> [$hamlet|
-%tr
+    go fi = [$hamlet|
+%tr.$clazz.fi$
     %td
         %label!for=$fiIdent.fi$ $fiLabel.fi$
         .tooltip $fiTooltip.fi$
     %td
-        ^w^
+        ^fiInput.fi^
     $maybe fiErrors.fi err
         %td.errors $err$
 |]
+    clazz fi = if fiRequired fi then "required" else "optional"
 
-runFormGeneric :: Env
-               -> FileEnv
-               -> GForm sub y xml a
-               -> GHandler sub y (FormResult a, xml, Enctype)
-runFormGeneric env fe (GForm f) =
-    runReaderT (runReaderT (evalStateT f $ IntSingle 1) env) fe
+-- | Display the label, tooltip, input code and errors in a single div.
+fieldsToDivs :: FormField sub y a -> Form sub y a
+fieldsToDivs = mapFormXml $ mapM_ go
+  where
+    go fi = [$hamlet|
+.$clazz.fi$
+    %label!for=$fiIdent.fi$ $fiLabel.fi$
+        .tooltip $fiTooltip.fi$
+    ^fiInput.fi^
+    $maybe fiErrors.fi err
+        %div.errors $err$
+|]
+    clazz fi = if fiRequired fi then "required" else "optional"
 
+-- | Run a form against POST parameters, without CSRF protection.
+runFormPostNoNonce :: GForm s m xml a -> GHandler s m (FormResult a, xml, Enctype)
+runFormPostNoNonce f = do
+    rr <- getRequest
+    (pp, files) <- liftIO $ reqRequestBody rr
+    runFormGeneric pp files f
+
 -- | Run a form against POST parameters.
-runFormPost :: GForm sub y xml a
-            -> GHandler sub y (FormResult a, xml, Enctype)
+--
+-- This function includes CSRF protection by checking a nonce value. You must
+-- therefore embed this nonce in the form as a hidden field; that is the
+-- meaning of the fourth element in the tuple.
+runFormPost :: GForm s m xml a -> GHandler s m (FormResult a, xml, Enctype, Html)
 runFormPost f = do
     rr <- getRequest
     (pp, files) <- liftIO $ reqRequestBody rr
+    nonce <- fmap reqNonce getRequest
+    (res, xml, enctype) <- runFormGeneric pp files f
+    let res' =
+            case res of
+                FormSuccess x ->
+                    if lookup nonceName pp == Just nonce
+                        then FormSuccess x
+                        else FormFailure ["As a protection against cross-site request forgery attacks, please confirm your form submission."]
+                _ -> res
+    return (res', xml, enctype, hidden nonce)
+  where
+    hidden nonce = [$hamlet|%input!type=hidden!name=$nonceName$!value=$nonce$|]
+
+nonceName :: String
+nonceName = "_nonce"
+
+-- | Run a form against POST parameters. Please note that this does not provide
+-- CSRF protection.
+runFormMonadPost :: GFormMonad s m a -> GHandler s m (a, Enctype)
+runFormMonadPost f = do
+    rr <- getRequest
+    (pp, files) <- liftIO $ reqRequestBody rr
     runFormGeneric pp files f
 
 -- | Run a form against POST parameters, disregarding the resulting HTML and
--- returning an error response on invalid input.
+-- returning an error response on invalid input. Note: this does /not/ perform
+-- CSRF protection.
 runFormPost' :: GForm sub y xml a -> GHandler sub y a
-runFormPost' = helper <=< runFormPost
+runFormPost' f = do
+    rr <- getRequest
+    (pp, files) <- liftIO $ reqRequestBody rr
+    x <- runFormGeneric pp files f
+    helper x
 
 -- | Run a form against GET parameters, disregarding the resulting HTML and
 -- returning an error response on invalid input.
@@ -105,16 +154,29 @@
 helper (FormFailure e, _, _) = invalidArgs e
 helper (FormMissing, _, _) = invalidArgs ["No input found"]
 
+-- | Generate a form, feeding it no data. The third element in the result tuple
+-- is a nonce hidden field.
+generateForm :: GForm s m xml a -> GHandler s m (xml, Enctype, Html)
+generateForm f = do
+    (_, b, c) <- runFormGeneric [] [] f
+    nonce <- fmap reqNonce getRequest
+    return (b, c, [$hamlet|%input!type=hidden!name=$nonceName$!value=$nonce$|])
+
 -- | Run a form against GET parameters.
-runFormGet :: GForm sub y xml a
-           -> GHandler sub y (FormResult a, xml, Enctype)
+runFormGet :: GForm s m xml a -> GHandler s m (FormResult a, xml, Enctype)
 runFormGet f = do
     gs <- reqGetParams `fmap` getRequest
     runFormGeneric gs [] f
 
--- | Create 'ToForm' instances for the entities given. In addition to regular 'EntityDef' attributes understood by persistent, it also understands label= and tooltip=.
-mkToForm :: [EntityDef] -> Q [Dec]
-mkToForm = mapM derive
+runFormMonadGet :: GFormMonad s m a -> GHandler s m (a, Enctype)
+runFormMonadGet f = do
+    gs <- reqGetParams `fmap` getRequest
+    runFormGeneric gs [] f
+
+-- | Create 'ToForm' instances for the given entity. In addition to regular 'EntityDef' attributes understood by persistent, it also understands label= and tooltip=.
+mkToForm :: PersistEntity v => v -> Q [Dec]
+mkToForm =
+    fmap return . derive . entityDef
   where
     afterPeriod s =
         case dropWhile (/= '.') s of
@@ -181,7 +243,7 @@
         let x = foldl (ap' ap) just' $ map (go' ffs' stm string') a
          in ftt `AppE` x
     go' ffs' stm string' (((theId, name), ((label, tooltip), tff)), ex) =
-        let label' = string' `AppE` LitE (StringL label)
+        let label' = LitE $ StringL label
             tooltip' = string' `AppE` LitE (StringL tooltip)
             ffs = ffs' `AppE`
                   label' `AppE`
@@ -199,3 +261,7 @@
     go (c:cs)
         | isUpper c = ' ' : c : go cs
         | otherwise = c : go cs
+
+formFailures :: FormResult a -> Maybe [String]
+formFailures (FormFailure x) = Just x
+formFailures _ = Nothing
diff --git a/Yesod/Form/Core.hs b/Yesod/Form/Core.hs
--- a/Yesod/Form/Core.hs
+++ b/Yesod/Form/Core.hs
@@ -1,4 +1,9 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+-- | Users of the forms library should not need to use this module in general.
+-- It is intended only for writing custom forms and form fields.
 module Yesod.Form.Core
     ( FormResult (..)
     , GForm (..)
@@ -18,6 +23,9 @@
     , askParams
     , askFiles
     , liftForm
+    , IsForm (..)
+    , RunForm (..)
+    , GFormMonad
       -- * Data types
     , FieldInfo (..)
     , FormFieldSettings (..)
@@ -32,6 +40,7 @@
 
 import Control.Monad.Trans.State
 import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Writer
 import Control.Monad.Trans.Class (lift)
 import Yesod.Handler
 import Yesod.Widget
@@ -90,14 +99,19 @@
 
 -- | A generic form, allowing you to specifying the subsite datatype, master
 -- site datatype, a datatype for the form XML and the return type.
-newtype GForm sub y xml a = GForm
-    { deform :: StateT Ints (
-                ReaderT Env (
-                ReaderT FileEnv (
-                (GHandler sub y)
-                ))) (FormResult a, xml, Enctype)
+newtype GForm s m xml a = GForm
+    { deform :: FormInner s m (FormResult a, xml, Enctype)
     }
 
+type GFormMonad s m a = WriterT Enctype (FormInner s m) a
+
+type FormInner s m =
+    StateT Ints (
+    ReaderT Env (
+    ReaderT FileEnv (
+    GHandler s m
+    )))
+
 type Env = [(String, String)]
 type FileEnv = [(String, FileInfo)]
 
@@ -134,10 +148,14 @@
         return (f1 <*> g1, f2 `mappend` g2, f3 `mappend` g3)
 
 -- | Create a required field (ie, one that cannot be blank) from a
--- 'FieldProfile'.ngs
-requiredFieldHelper :: FieldProfile sub y a -> FormFieldSettings
-                    -> Maybe a -> FormField sub y a
-requiredFieldHelper (FieldProfile parse render mkWidget) ffs orig = GForm $ do
+-- 'FieldProfile'.
+requiredFieldHelper
+    :: IsForm f
+    => FieldProfile (FormSub f) (FormMaster f) (FormType f)
+    -> FormFieldSettings
+    -> Maybe (FormType f)
+    -> f
+requiredFieldHelper (FieldProfile parse render mkWidget) ffs orig = toForm $ do
     env <- lift ask
     let (FormFieldSettings label tooltip theId' name') = ffs
     name <- maybe newFormIdent return name'
@@ -153,21 +171,79 @@
                                 Left e -> (FormFailure [e], x)
                                 Right y -> (FormSuccess y, x)
     let fi = FieldInfo
-            { fiLabel = label
+            { fiLabel = string label
             , fiTooltip = tooltip
             , fiIdent = theId
             , fiInput = mkWidget theId name val True
             , fiErrors = case res of
                             FormFailure [x] -> Just $ string x
                             _ -> Nothing
+            , fiRequired = True
             }
-    return (res, [fi], UrlEncoded)
+    let res' = case res of
+                FormFailure [e] -> FormFailure [label ++ ": " ++ e]
+                _ -> res
+    return (res', fi, UrlEncoded)
 
+class IsForm f where
+    type FormSub f
+    type FormMaster f
+    type FormType f
+    toForm :: FormInner
+                (FormSub f)
+                (FormMaster f)
+                (FormResult (FormType f),
+                 FieldInfo (FormSub f) (FormMaster f),
+                 Enctype) -> f
+instance IsForm (FormField s m a) where
+    type FormSub (FormField s m a) = s
+    type FormMaster (FormField s m a) = m
+    type FormType (FormField s m a) = a
+    toForm x = GForm $ do
+        (a, b, c) <- x
+        return (a, [b], c)
+instance IsForm (GFormMonad s m (FormResult a, FieldInfo s m)) where
+    type FormSub (GFormMonad s m (FormResult a, FieldInfo s m)) = s
+    type FormMaster (GFormMonad s m (FormResult a, FieldInfo s m)) = m
+    type FormType (GFormMonad s m (FormResult a, FieldInfo s m)) = a
+    toForm x = do
+        (res, fi, enctype) <- lift x
+        tell enctype
+        return (res, fi)
+
+class RunForm f where
+    type RunFormSub f
+    type RunFormMaster f
+    type RunFormType f
+    runFormGeneric :: Env -> FileEnv -> f
+                   -> GHandler (RunFormSub f)
+                               (RunFormMaster f)
+                               (RunFormType f)
+
+instance RunForm (GForm s m xml a) where
+    type RunFormSub (GForm s m xml a) = s
+    type RunFormMaster (GForm s m xml a) = m
+    type RunFormType (GForm s m xml a) =
+        (FormResult a, xml, Enctype)
+    runFormGeneric env fe (GForm f) =
+        runReaderT (runReaderT (evalStateT f $ IntSingle 1) env) fe
+
+instance RunForm (GFormMonad s m a) where
+    type RunFormSub (GFormMonad s m a) = s
+    type RunFormMaster (GFormMonad s m a) = m
+    type RunFormType (GFormMonad s m a) = (a, Enctype)
+    runFormGeneric e fe f =
+        runReaderT (runReaderT (evalStateT (runWriterT f) $ IntSingle 1) e) fe
+
 -- | Create an optional field (ie, one that can be blank) from a
 -- 'FieldProfile'.
-optionalFieldHelper :: FieldProfile sub y a -> FormFieldSettings
-                    -> FormletField sub y (Maybe a)
-optionalFieldHelper (FieldProfile parse render mkWidget) ffs orig' = GForm $ do
+optionalFieldHelper
+    :: (IsForm f, Maybe b ~ FormType f)
+    => FieldProfile (FormSub f) (FormMaster f) b
+    -> FormFieldSettings
+    -> Maybe (Maybe b)
+    -> f
+optionalFieldHelper (FieldProfile parse render mkWidget) ffs orig' = toForm $ do
     env <- lift ask
     let (FormFieldSettings label tooltip theId' name') = ffs
     let orig = join orig'
@@ -184,15 +260,19 @@
                                 Left e -> (FormFailure [e], x)
                                 Right y -> (FormSuccess $ Just y, x)
     let fi = FieldInfo
-            { fiLabel = label
+            { fiLabel = string label
             , fiTooltip = tooltip
             , fiIdent = theId
             , fiInput = mkWidget theId name val False
             , fiErrors = case res of
                             FormFailure x -> Just $ string $ unlines x
                             _ -> Nothing
+            , fiRequired = False
             }
-    return (res, [fi], UrlEncoded)
+    let res' = case res of
+                FormFailure [e] -> FormFailure [label ++ ": " ++ e]
+                _ -> res
+    return (res', fi, UrlEncoded)
 
 fieldsToInput :: [FieldInfo sub y] -> [GWidget sub y ()]
 fieldsToInput = map fiInput
@@ -212,16 +292,17 @@
     , fiIdent :: String
     , fiInput :: GWidget sub y ()
     , fiErrors :: Maybe Html
+    , fiRequired :: Bool
     }
 
 data FormFieldSettings = FormFieldSettings
-    { ffsLabel :: Html
+    { ffsLabel :: String
     , ffsTooltip :: Html
     , ffsId :: Maybe String
     , ffsName :: Maybe String
     }
 instance IsString FormFieldSettings where
-    fromString s = FormFieldSettings (string s) mempty Nothing Nothing
+    fromString s = FormFieldSettings s mempty Nothing Nothing
 
 -- | A generic definition of a form field that can be used for generating both
 -- required and optional fields. See 'requiredFieldHelper and
diff --git a/Yesod/Form/Fields.hs b/Yesod/Form/Fields.hs
--- a/Yesod/Form/Fields.hs
+++ b/Yesod/Form/Fields.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
 module Yesod.Form.Fields
     ( -- * Fields
       -- ** Required
@@ -42,17 +44,18 @@
 
 import Yesod.Form.Core
 import Yesod.Form.Profiles
-import Yesod.Widget
 import Data.Time (Day, TimeOfDay)
 import Text.Hamlet
 import Data.Monoid
 import Control.Monad (join)
 import Data.Maybe (fromMaybe)
 
-stringField :: FormFieldSettings -> FormletField sub y String
+stringField :: (IsForm f, FormType f ~ String)
+            => FormFieldSettings -> Maybe String -> f
 stringField = requiredFieldHelper stringFieldProfile
 
-maybeStringField :: FormFieldSettings -> FormletField sub y (Maybe String)
+maybeStringField :: (IsForm f, FormType f ~ Maybe String)
+                 => FormFieldSettings -> Maybe (Maybe String) -> f
 maybeStringField = optionalFieldHelper stringFieldProfile
 
 intInput :: Integral i => String -> FormInput sub master i
@@ -65,32 +68,41 @@
     mapFormXml fieldsToInput $
     optionalFieldHelper intFieldProfile (nameSettings n) Nothing
 
-intField :: Integral i => FormFieldSettings -> FormletField sub y i
+intField :: (Integral (FormType f), IsForm f)
+         => FormFieldSettings -> Maybe (FormType f) -> f
 intField = requiredFieldHelper intFieldProfile
 
-maybeIntField :: Integral i => FormFieldSettings -> FormletField sub y (Maybe i)
+maybeIntField :: (Integral i, FormType f ~ Maybe i, IsForm f)
+              => FormFieldSettings -> Maybe (FormType f) -> f
 maybeIntField = optionalFieldHelper intFieldProfile
 
-doubleField :: FormFieldSettings -> FormletField sub y Double
+doubleField :: (IsForm f, FormType f ~ Double)
+            => FormFieldSettings -> Maybe Double -> f
 doubleField = requiredFieldHelper doubleFieldProfile
 
-maybeDoubleField :: FormFieldSettings -> FormletField sub y (Maybe Double)
+maybeDoubleField :: (IsForm f, FormType f ~ Maybe Double)
+                 => FormFieldSettings -> Maybe (Maybe Double) -> f
 maybeDoubleField = optionalFieldHelper doubleFieldProfile
 
-dayField :: FormFieldSettings -> FormletField sub y Day
+dayField :: (IsForm f, FormType f ~ Day)
+         => FormFieldSettings -> Maybe Day -> f
 dayField = requiredFieldHelper dayFieldProfile
 
-maybeDayField :: FormFieldSettings -> FormletField sub y (Maybe Day)
+maybeDayField :: (IsForm f, FormType f ~ Maybe Day)
+              => FormFieldSettings -> Maybe (Maybe Day) -> f
 maybeDayField = optionalFieldHelper dayFieldProfile
 
-timeField :: FormFieldSettings -> FormletField sub y TimeOfDay
+timeField :: (IsForm f, FormType f ~ TimeOfDay)
+          => FormFieldSettings -> Maybe TimeOfDay -> f
 timeField = requiredFieldHelper timeFieldProfile
 
-maybeTimeField :: FormFieldSettings -> FormletField sub y (Maybe TimeOfDay)
+maybeTimeField :: (IsForm f, FormType f ~ Maybe TimeOfDay)
+               => FormFieldSettings -> Maybe (Maybe TimeOfDay) -> f
 maybeTimeField = optionalFieldHelper timeFieldProfile
 
-boolField :: FormFieldSettings -> Maybe Bool -> FormField sub y Bool
-boolField ffs orig = GForm $ do
+boolField :: (IsForm f, FormType f ~ Bool)
+          => FormFieldSettings -> Maybe Bool -> f
+boolField ffs orig = toForm $ do
     env <- askParams
     let label = ffsLabel ffs
         tooltip = ffsTooltip ffs
@@ -105,28 +117,33 @@
                         Just "false" -> (FormSuccess False, False)
                         Just _ -> (FormSuccess True, True)
     let fi = FieldInfo
-            { fiLabel = label
+            { fiLabel = string label
             , fiTooltip = tooltip
             , fiIdent = theId
-            , fiInput = addBody [$hamlet|
+            , fiInput = [$hamlet|
 %input#$theId$!type=checkbox!name=$name$!:val:checked
 |]
             , fiErrors = case res of
                             FormFailure [x] -> Just $ string x
                             _ -> Nothing
+            , fiRequired = True
             }
-    return (res, [fi], UrlEncoded)
+    return (res, fi, UrlEncoded)
 
-htmlField :: FormFieldSettings -> FormletField sub y Html
+htmlField :: (IsForm f, FormType f ~ Html)
+          => FormFieldSettings -> Maybe Html -> f
 htmlField = requiredFieldHelper htmlFieldProfile
 
-maybeHtmlField :: FormFieldSettings -> FormletField sub y (Maybe Html)
+maybeHtmlField :: (IsForm f, FormType f ~ Maybe Html)
+               => FormFieldSettings -> Maybe (Maybe Html) -> f
 maybeHtmlField = optionalFieldHelper htmlFieldProfile
 
-selectField :: Eq x => [(x, String)]
+selectField :: (Eq x, IsForm f, FormType f ~ x)
+            => [(x, String)]
             -> FormFieldSettings
-            -> Maybe x -> FormField sub master x
-selectField pairs ffs initial = GForm $ do
+            -> Maybe x
+            -> f
+selectField pairs ffs initial = toForm $ do
     env <- askParams
     let label = ffsLabel ffs
         tooltip = ffsTooltip ffs
@@ -154,20 +171,23 @@
         %option!value=$show.fst.pair$!:isSelected.fst.snd.pair:selected $snd.snd.pair$
 |]
     let fi = FieldInfo
-            { fiLabel = label
+            { fiLabel = string label
             , fiTooltip = tooltip
             , fiIdent = theId
-            , fiInput = addBody input
+            , fiInput = input
             , fiErrors = case res of
                             FormFailure [x] -> Just $ string x
                             _ -> Nothing
+            , fiRequired = True
             }
-    return (res, [fi], UrlEncoded)
+    return (res, fi, UrlEncoded)
 
-maybeSelectField :: Eq x => [(x, String)]
+maybeSelectField :: (Eq x, IsForm f, Maybe x ~ FormType f)
+                 => [(x, String)]
                  -> FormFieldSettings
-                 -> FormletField sub master (Maybe x)
-maybeSelectField pairs ffs initial' = GForm $ do
+                 -> Maybe (FormType f)
+                 -> f
+maybeSelectField pairs ffs initial' = toForm $ do
     env <- askParams
     let initial = join initial'
         label = ffsLabel ffs
@@ -196,15 +216,16 @@
         %option!value=$show.fst.pair$!:isSelected.fst.snd.pair:selected $snd.snd.pair$
 |]
     let fi = FieldInfo
-            { fiLabel = label
+            { fiLabel = string label
             , fiTooltip = tooltip
             , fiIdent = theId
-            , fiInput = addBody input
+            , fiInput = input
             , fiErrors = case res of
                             FormFailure [x] -> Just $ string x
                             _ -> Nothing
+            , fiRequired = False
             }
-    return (res, [fi], UrlEncoded)
+    return (res, fi, UrlEncoded)
 
 stringInput :: String -> FormInput sub master String
 stringInput n =
@@ -224,9 +245,7 @@
                 Just "" -> FormSuccess False
                 Just "false" -> FormSuccess False
                 Just _ -> FormSuccess True
-    let xml = addBody [$hamlet|
-%input#$n$!type=checkbox!name=$n$
-|]
+    let xml = [$hamlet|%input#$n$!type=checkbox!name=$n$|]
     return (res, [xml], UrlEncoded)
 
 dayInput :: String -> FormInput sub master Day
@@ -242,10 +261,12 @@
 nameSettings :: String -> FormFieldSettings
 nameSettings n = FormFieldSettings mempty mempty (Just n) (Just n)
 
-urlField :: FormFieldSettings -> FormletField sub y String
+urlField :: (IsForm f, FormType f ~ String)
+         => FormFieldSettings -> Maybe String -> f
 urlField = requiredFieldHelper urlFieldProfile
 
-maybeUrlField :: FormFieldSettings -> FormletField sub y (Maybe String)
+maybeUrlField :: (IsForm f, FormType f ~ Maybe String)
+               => FormFieldSettings -> Maybe (Maybe String) -> f
 maybeUrlField = optionalFieldHelper urlFieldProfile
 
 urlInput :: String -> FormInput sub master String
@@ -253,10 +274,12 @@
     mapFormXml fieldsToInput $
     requiredFieldHelper urlFieldProfile (nameSettings n) Nothing
 
-emailField :: FormFieldSettings -> FormletField sub y String
+emailField :: (IsForm f, FormType f ~ String)
+           => FormFieldSettings -> Maybe String -> f
 emailField = requiredFieldHelper emailFieldProfile
 
-maybeEmailField :: FormFieldSettings -> FormletField sub y (Maybe String)
+maybeEmailField :: (IsForm f, FormType f ~ Maybe String)
+                => FormFieldSettings -> Maybe (Maybe String) -> f
 maybeEmailField = optionalFieldHelper emailFieldProfile
 
 emailInput :: String -> FormInput sub master String
@@ -264,14 +287,17 @@
     mapFormXml fieldsToInput $
     requiredFieldHelper emailFieldProfile (nameSettings n) Nothing
 
-textareaField :: FormFieldSettings -> FormletField sub y Textarea
+textareaField :: (IsForm f, FormType f ~ Textarea)
+              => FormFieldSettings -> Maybe Textarea -> f
 textareaField = requiredFieldHelper textareaFieldProfile
 
 maybeTextareaField :: FormFieldSettings -> FormletField sub y (Maybe Textarea)
 maybeTextareaField = optionalFieldHelper textareaFieldProfile
 
-hiddenField :: FormFieldSettings -> FormletField sub y String
+hiddenField :: (IsForm f, FormType f ~ String)
+            => FormFieldSettings -> Maybe String -> f
 hiddenField = requiredFieldHelper hiddenFieldProfile
 
-maybeHiddenField :: FormFieldSettings -> FormletField sub y (Maybe String)
+maybeHiddenField :: (IsForm f, FormType f ~ Maybe String)
+                 => FormFieldSettings -> Maybe (Maybe String) -> f
 maybeHiddenField = optionalFieldHelper hiddenFieldProfile
diff --git a/Yesod/Form/Jquery.hs b/Yesod/Form/Jquery.hs
--- a/Yesod/Form/Jquery.hs
+++ b/Yesod/Form/Jquery.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
+-- | Some fields spiced up with jQuery UI.
 module Yesod.Form.Jquery
     ( YesodJquery (..)
     , jqueryDayField
@@ -9,6 +12,8 @@
     , maybeJqueryAutocompleteField
     , jqueryDayFieldProfile
     , googleHostedJqueryUiCss
+    , JqueryDaySettings (..)
+    , Default (..)
     ) where
 
 import Yesod.Handler
@@ -19,6 +24,7 @@
                   timeToTimeOfDay)
 import Yesod.Hamlet
 import Data.Char (isSpace)
+import Data.Default
 
 -- | Gets the Google hosted jQuery UI 1.8 CSS file with the given theme.
 googleHostedJqueryUiCss :: String -> String
@@ -45,30 +51,57 @@
     urlJqueryUiDateTimePicker :: a -> Either (Route a) String
     urlJqueryUiDateTimePicker _ = Right "http://github.com/gregwebs/jquery.ui.datetimepicker/raw/master/jquery.ui.datetimepicker.js"
 
-jqueryDayField :: YesodJquery y => FormFieldSettings -> FormletField sub y Day
-jqueryDayField = requiredFieldHelper jqueryDayFieldProfile
+jqueryDayField :: (IsForm f, FormType f ~ Day, YesodJquery (FormMaster f))
+               => JqueryDaySettings
+               -> FormFieldSettings
+               -> Maybe (FormType f)
+               -> f
+jqueryDayField = requiredFieldHelper . jqueryDayFieldProfile
 
-maybeJqueryDayField :: YesodJquery y => FormFieldSettings -> FormletField sub y (Maybe Day)
-maybeJqueryDayField = optionalFieldHelper jqueryDayFieldProfile
+maybeJqueryDayField
+    :: (IsForm f, FormType f ~ Maybe Day, YesodJquery (FormMaster f))
+    => JqueryDaySettings
+    -> FormFieldSettings
+    -> Maybe (FormType f)
+    -> f
+maybeJqueryDayField = optionalFieldHelper . jqueryDayFieldProfile
 
-jqueryDayFieldProfile :: YesodJquery y => FieldProfile sub y Day
-jqueryDayFieldProfile = FieldProfile
+jqueryDayFieldProfile :: YesodJquery y
+                      => JqueryDaySettings -> FieldProfile sub y Day
+jqueryDayFieldProfile jds = FieldProfile
     { fpParse = maybe
                   (Left "Invalid day, must be in YYYY-MM-DD format")
                   Right
               . readMay
     , fpRender = show
     , fpWidget = \theId name val isReq -> do
-        addBody [$hamlet|
+        addHtml [$hamlet|
 %input#$theId$!name=$name$!type=date!:isReq:required!value=$val$
 |]
         addScript' urlJqueryJs
         addScript' urlJqueryUiJs
         addStylesheet' urlJqueryUiCss
-        addJavascript [$julius|
-$(function(){$("#%theId%").datepicker({dateFormat:'yy-mm-dd'})});
+        addJulius [$julius|
+$(function(){$("#%theId%").datepicker({
+    dateFormat:'yy-mm-dd',
+    changeMonth:%jsBool.jdsChangeMonth.jds%,
+    changeYear:%jsBool.jdsChangeYear.jds%,
+    numberOfMonths:%mos.jdsNumberOfMonths.jds%,
+    yearRange:"%jdsYearRange.jds%"
+})});
 |]
     }
+  where
+    jsBool True = "true"
+    jsBool False = "false"
+    mos (Left i) = show i
+    mos (Right (x, y)) = concat
+        [ "["
+        , show x
+        , ","
+        , show y
+        , "]"
+        ]
 
 ifRight :: Either a b -> (b -> c) -> Either a c
 ifRight e f = case e of
@@ -78,7 +111,11 @@
 showLeadingZero :: (Show a) => a -> String
 showLeadingZero time = let t = show time in if length t == 1 then "0" ++ t else t
 
-jqueryDayTimeField :: YesodJquery y => FormFieldSettings -> FormletField sub y UTCTime
+jqueryDayTimeField
+    :: (IsForm f, FormType f ~ UTCTime, YesodJquery (FormMaster f))
+    => FormFieldSettings
+    -> Maybe (FormType f)
+    -> f
 jqueryDayTimeField = requiredFieldHelper jqueryDayTimeFieldProfile
 
 -- use A.M/P.M and drop seconds and "UTC" (as opposed to normal UTCTime show)
@@ -96,14 +133,14 @@
     { fpParse  = parseUTCTime
     , fpRender = jqueryDayTimeUTCTime
     , fpWidget = \theId name val isReq -> do
-        addBody [$hamlet|
+        addHtml [$hamlet|
 %input#$theId$!name=$name$!:isReq:required!value=$val$
 |]
         addScript' urlJqueryJs
         addScript' urlJqueryUiJs
         addScript' urlJqueryUiDateTimePicker
         addStylesheet' urlJqueryUiCss
-        addJavascript [$julius|
+        addJulius [$julius|
 $(function(){$("#%theId%").datetimepicker({dateFormat : "yyyy/mm/dd h:MM TT"})});
 |]
     }
@@ -118,12 +155,20 @@
                 ifRight (parseTime timeS)
                     (UTCTime date . timeOfDayToTime)
 
-jqueryAutocompleteField :: YesodJquery y =>
-    Route y -> FormFieldSettings -> FormletField sub y String
+jqueryAutocompleteField
+    :: (IsForm f, FormType f ~ String, YesodJquery (FormMaster f))
+    => Route (FormMaster f)
+    -> FormFieldSettings
+    -> Maybe (FormType f)
+    -> f
 jqueryAutocompleteField = requiredFieldHelper . jqueryAutocompleteFieldProfile
 
-maybeJqueryAutocompleteField :: YesodJquery y =>
-    Route y -> FormFieldSettings -> FormletField sub y (Maybe String)
+maybeJqueryAutocompleteField
+    :: (IsForm f, FormType f ~ Maybe String, YesodJquery (FormMaster f))
+    => Route (FormMaster f)
+    -> FormFieldSettings
+    -> Maybe (FormType f)
+    -> f
 maybeJqueryAutocompleteField src =
     optionalFieldHelper $ jqueryAutocompleteFieldProfile src
 
@@ -132,13 +177,13 @@
     { fpParse = Right
     , fpRender = id
     , fpWidget = \theId name val isReq -> do
-        addBody [$hamlet|
+        addHtml [$hamlet|
 %input.autocomplete#$theId$!name=$name$!type=text!:isReq:required!value=$val$
 |]
         addScript' urlJqueryJs
         addScript' urlJqueryUiJs
         addStylesheet' urlJqueryUiCss
-        addJavascript [$julius|
+        addJulius [$julius|
 $(function(){$("#%theId%").autocomplete({source:"@src@",minLength:2})});
 |]
     }
@@ -162,3 +207,18 @@
 -- from http://hackage.haskell.org/packages/archive/cgi/3001.1.7.1/doc/html/src/Network-CGI-Protocol.html#replace
 replace :: Eq a => a -> a -> [a] -> [a]
 replace x y = map (\z -> if z == x then y else z)
+
+data JqueryDaySettings = JqueryDaySettings
+    { jdsChangeMonth :: Bool
+    , jdsChangeYear :: Bool
+    , jdsYearRange :: String
+    , jdsNumberOfMonths :: Either Int (Int, Int)
+    }
+
+instance Default JqueryDaySettings where
+    def = JqueryDaySettings
+        { jdsChangeMonth = False
+        , jdsChangeYear = False
+        , jdsYearRange = "c-10:c+10"
+        , jdsNumberOfMonths = Left 1
+        }
diff --git a/Yesod/Form/Nic.hs b/Yesod/Form/Nic.hs
--- a/Yesod/Form/Nic.hs
+++ b/Yesod/Form/Nic.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
+-- | Provide the user with a rich text editor.
 module Yesod.Form.Nic
     ( YesodNic (..)
     , nicHtmlField
@@ -9,28 +12,32 @@
 import Yesod.Form.Core
 import Yesod.Hamlet
 import Yesod.Widget
-import qualified Data.ByteString.Lazy.UTF8 as U
 import Text.HTML.SanitizeXSS (sanitizeXSS)
 
+import Yesod.Internal (lbsToChars)
+
 class YesodNic a where
-    -- | NIC Editor.
+    -- | NIC Editor Javascript file.
     urlNicEdit :: a -> Either (Route a) String
     urlNicEdit _ = Right "http://js.nicedit.com/nicEdit-latest.js"
 
-nicHtmlField :: YesodNic y => FormFieldSettings -> FormletField sub y Html
+nicHtmlField :: (IsForm f, FormType f ~ Html, YesodNic (FormMaster f))
+             => FormFieldSettings -> Maybe Html -> f
 nicHtmlField = requiredFieldHelper nicHtmlFieldProfile
 
-maybeNicHtmlField :: YesodNic y => FormFieldSettings -> FormletField sub y (Maybe Html)
+maybeNicHtmlField
+    :: (IsForm f, FormType f ~ Maybe Html, YesodNic (FormMaster f))
+    => FormFieldSettings -> Maybe (FormType f) -> f
 maybeNicHtmlField = optionalFieldHelper nicHtmlFieldProfile
 
 nicHtmlFieldProfile :: YesodNic y => FieldProfile sub y Html
 nicHtmlFieldProfile = FieldProfile
     { fpParse = Right . preEscapedString . sanitizeXSS
-    , fpRender = U.toString . renderHtml
+    , fpRender = lbsToChars . renderHtml
     , fpWidget = \theId name val _isReq -> do
-        addBody [$hamlet|%textarea.html#$theId$!name=$name$ $val$|]
+        addHtml [$hamlet|%textarea.html#$theId$!name=$name$ $val$|]
         addScript' urlNicEdit
-        addJavascript [$julius|bkLib.onDomLoaded(function(){new nicEditor({fullPanel:true}).panelInstance("%theId%")});|]
+        addJulius [$julius|bkLib.onDomLoaded(function(){new nicEditor({fullPanel:true}).panelInstance("%theId%")});|]
     }
 
 addScript' :: (y -> Either (Route y) String) -> GWidget sub y ()
diff --git a/Yesod/Form/Profiles.hs b/Yesod/Form/Profiles.hs
--- a/Yesod/Form/Profiles.hs
+++ b/Yesod/Form/Profiles.hs
@@ -21,7 +21,6 @@
 import Yesod.Widget
 import Text.Hamlet
 import Data.Time (Day, TimeOfDay(..))
-import qualified Data.ByteString.Lazy.UTF8 as U
 import qualified Text.Email.Validate as Email
 import Network.URI (parseURI)
 import Database.Persist (PersistField)
@@ -30,11 +29,13 @@
 import Text.Blaze.Builder.Utf8 (writeChar)
 import Text.Blaze.Builder.Core (writeList, writeByteString)
 
+import Yesod.Internal (lbsToChars)
+
 intFieldProfile :: Integral i => FieldProfile sub y i
 intFieldProfile = FieldProfile
     { fpParse = maybe (Left "Invalid integer") Right . readMayI
     , fpRender = showI
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=number!:isReq:required!value=$val$
 |]
     }
@@ -48,7 +49,7 @@
 doubleFieldProfile = FieldProfile
     { fpParse = maybe (Left "Invalid number") Right . readMay
     , fpRender = show
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=text!:isReq:required!value=$val$
 |]
     }
@@ -57,7 +58,7 @@
 dayFieldProfile = FieldProfile
     { fpParse = parseDate
     , fpRender = show
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=date!:isReq:required!value=$val$
 |]
     }
@@ -66,7 +67,7 @@
 timeFieldProfile = FieldProfile
     { fpParse = parseTime
     , fpRender = show
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!:isReq:required!value=$val$
 |]
     }
@@ -74,8 +75,8 @@
 htmlFieldProfile :: FieldProfile sub y Html
 htmlFieldProfile = FieldProfile
     { fpParse = Right . preEscapedString . sanitizeXSS
-    , fpRender = U.toString . renderHtml
-    , fpWidget = \theId name val _isReq -> addBody [$hamlet|
+    , fpRender = lbsToChars . renderHtml
+    , fpWidget = \theId name val _isReq -> addHamlet [$hamlet|
 %textarea.html#$theId$!name=$name$ $val$
 |]
     }
@@ -101,7 +102,7 @@
 textareaFieldProfile = FieldProfile
     { fpParse = Right . Textarea
     , fpRender = unTextarea
-    , fpWidget = \theId name val _isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val _isReq -> addHamlet [$hamlet|
 %textarea#$theId$!name=$name$ $val$
 |]
     }
@@ -110,7 +111,7 @@
 hiddenFieldProfile = FieldProfile
     { fpParse = Right
     , fpRender = id
-    , fpWidget = \theId name val _isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val _isReq -> addHamlet [$hamlet|
 %input!type=hidden#$theId$!name=$name$!value=$val$
 |]
     }
@@ -119,7 +120,7 @@
 stringFieldProfile = FieldProfile
     { fpParse = Right
     , fpRender = id
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=text!:isReq:required!value=$val$
 |]
     }
@@ -168,7 +169,7 @@
                         then Right s
                         else Left "Invalid e-mail address"
     , fpRender = id
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=email!:isReq:required!value=$val$
 |]
     }
@@ -179,7 +180,7 @@
                         Nothing -> Left "Invalid URL"
                         Just _ -> Right s
     , fpRender = id
-    , fpWidget = \theId name val isReq -> addBody [$hamlet|
+    , fpWidget = \theId name val isReq -> addHamlet [$hamlet|
 %input#$theId$!name=$name$!type=url!:isReq:required!value=$val$
 |]
     }
diff --git a/Yesod/Handler.hs b/Yesod/Handler.hs
--- a/Yesod/Handler.hs
+++ b/Yesod/Handler.hs
@@ -58,6 +58,9 @@
     , alreadyExpired
     , expiresAt
       -- * Session
+    , SessionMap
+    , lookupSession
+    , getSession
     , setSession
     , deleteSession
       -- ** Ultimate destination
@@ -73,7 +76,7 @@
     , YesodApp (..)
     , toMasterHandler
     , localNoCurrent
-    , finallyHandler
+    , HandlerData
 #if TEST
     , testSuite
 #endif
@@ -82,7 +85,6 @@
 import Prelude hiding (catch)
 import Yesod.Request
 import Yesod.Internal
-import Data.List (foldl')
 import Data.Neither
 import Data.Time (UTCTime)
 
@@ -94,17 +96,18 @@
 import Control.Monad.Trans.Class
 import Control.Monad.Trans.Writer
 import Control.Monad.Trans.Reader
-import "MonadCatchIO-transformers" Control.Monad.CatchIO (MonadCatchIO)
-import qualified "MonadCatchIO-transformers" Control.Monad.CatchIO as C
+import Control.Monad.Trans.State
 
 import System.IO
 import qualified Network.Wai as W
-import Control.Monad.Attempt
-import Data.ByteString.UTF8 (toString)
-import qualified Data.ByteString.Lazy.UTF8 as L
+import Control.Failure (Failure (failure))
 
 import Text.Hamlet
 
+import Control.Monad.Invert (MonadInvertIO (..))
+import Control.Monad (liftM)
+import qualified Data.Map as Map
+
 #if TEST
 import Test.Framework (testGroup, Test)
 import Test.Framework.Providers.HUnit (testCase)
@@ -153,16 +156,31 @@
 -- 'WriterT' for headers and session, and an 'MEitherT' monad for handling
 -- special responses. It is declared as a newtype to make compiler errors more
 -- readable.
-newtype GHandler sub master a = GHandler { unGHandler ::
-    ReaderT (HandlerData sub master) (
+newtype GHandler sub master a =
+    GHandler
+        { unGHandler :: GHInner sub master a
+        }
+    deriving (Functor, Applicative, Monad, MonadIO)
+
+type GHInner s m =
+    ReaderT (HandlerData s m) (
     MEitherT HandlerContents (
     WriterT (Endo [Header]) (
-    WriterT (Endo [(String, Maybe String)]) (
+    StateT SessionMap ( -- session
     IO
-    )))) a
-}
-    deriving (Functor, Applicative, Monad, MonadIO, MonadCatchIO)
+    ))))
 
+type SessionMap = Map.Map String String
+
+instance MonadInvertIO (GHandler s m) where
+    newtype InvertedIO (GHandler s m) a =
+        InvGHandlerIO
+            { runInvGHandlerIO :: InvertedIO (GHInner s m) a
+            }
+    type InvertedArg (GHandler s m) = (HandlerData s m, (SessionMap, ()))
+    invertIO = liftM (fmap InvGHandlerIO) . invertIO . unGHandler
+    revertIO f = GHandler $ revertIO $ liftM runInvGHandlerIO . f
+
 type Endo a = a -> a
 
 -- | An extension of the basic WAI 'W.Application' datatype to provide extra
@@ -173,7 +191,8 @@
     :: (ErrorResponse -> YesodApp)
     -> Request
     -> [ContentType]
-    -> IO (W.Status, [Header], ContentType, Content, [(String, String)])
+    -> SessionMap
+    -> IO (W.Status, [Header], ContentType, Content, SessionMap)
     }
 
 data HandlerContents =
@@ -215,16 +234,6 @@
 getRouteToMaster :: GHandler sub master (Route sub -> Route master)
 getRouteToMaster = handlerToMaster <$> GHandler ask
 
-modifySession :: [(String, String)] -> (String, Maybe String)
-              -> [(String, String)]
-modifySession orig (k, v) =
-    case v of
-        Nothing -> dropKeys k orig
-        Just v' -> (k, v') : dropKeys k orig
-
-dropKeys :: String -> [(String, x)] -> [(String, x)]
-dropKeys k = filter $ \(x, _) -> x /= k
-
 -- | Function used internally by Yesod in the process of converting a
 -- 'GHandler' into an 'W.Application'. Should not be needed by users.
 runHandler :: HasReps c
@@ -235,7 +244,8 @@
            -> master
            -> (master -> sub)
            -> YesodApp
-runHandler handler mrender sroute tomr ma tosa = YesodApp $ \eh rr cts -> do
+runHandler handler mrender sroute tomr ma tosa =
+  YesodApp $ \eh rr cts initSession -> do
     let toErrorHandler =
             InternalError
           . (show :: Control.Exception.SomeException -> String)
@@ -247,17 +257,16 @@
             , handlerRender = mrender
             , handlerToMaster = tomr
             }
-    ((contents', headers), session') <- E.catch (
-        runWriterT
+    ((contents', headers), finalSession) <- E.catch (
+        flip runStateT initSession
       $ runWriterT
       $ runMEitherT
       $ flip runReaderT hd
       $ unGHandler handler
-        ) (\e -> return ((MLeft $ HCError $ toErrorHandler e, id), id))
+        ) (\e -> return ((MLeft $ HCError $ toErrorHandler e, id), initSession))
     let contents = meither id (HCContent . chooseRep) contents'
-    let finalSession = foldl' modifySession (reqSession rr) $ session' []
     let handleError e = do
-            (_, hs, ct, c, sess) <- unYesodApp (eh e) safeEh rr cts
+            (_, hs, ct, c, sess) <- unYesodApp (eh e) safeEh rr cts finalSession
             let hs' = headers hs
             return (getStatus e, hs', ct, c, sess)
     let sendFile' ct fp =
@@ -276,9 +285,10 @@
             (handleError . toErrorHandler)
 
 safeEh :: ErrorResponse -> YesodApp
-safeEh er = YesodApp $ \_ _ _ -> do
+safeEh er = YesodApp $ \_ _ _ session -> do
     liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
-    return (W.status500, [], typePlain, toContent "Internal Server Error", [])
+    return (W.status500, [], typePlain, toContent "Internal Server Error",
+            session)
 
 -- | Redirect to the given route.
 redirect :: RedirectType -> Route master -> GHandler sub master a
@@ -322,9 +332,9 @@
         Nothing -> return ()
         Just r -> do
             tm <- getRouteToMaster
-            gets <- reqGetParams <$> getRequest
+            gets' <- reqGetParams <$> getRequest
             render <- getUrlRenderParams
-            setUltDestString $ render (tm r) gets
+            setUltDestString $ render (tm r) gets'
 
 -- | Redirect to the ultimate destination in the user's session. Clear the
 -- value from the session.
@@ -343,12 +353,9 @@
 
 -- | Sets a message in the user's session.
 --
--- The message set here will not be visible within the current request;
--- instead, it will only appear in the next request.
---
 -- See 'getMessage'.
 setMessage :: Html -> GHandler sub master ()
-setMessage = setSession msgKey . L.toString . renderHtml
+setMessage = setSession msgKey . lbsToChars . renderHtml
 
 -- | Gets the message in the user's session, if available, and then clears the
 -- variable.
@@ -378,7 +385,7 @@
 badMethod :: (RequestReader m, Failure ErrorResponse m) => m a
 badMethod = do
     w <- waiRequest
-    failure $ BadMethod $ toString $ W.requestMethod w
+    failure $ BadMethod $ bsToChars $ W.requestMethod w
 
 -- | Return a 403 permission denied page.
 permissionDenied :: Failure ErrorResponse m => String -> m a
@@ -400,7 +407,8 @@
 deleteCookie :: String -> GHandler sub master ()
 deleteCookie = addHeader . DeleteCookie
 
--- | Set the language in the user session. Will show up in 'languages'.
+-- | Set the language in the user session. Will show up in 'languages' on the
+-- next request.
 setLanguage :: String -> GHandler sub master ()
 setLanguage = setSession langKey
 
@@ -439,11 +447,11 @@
 setSession :: String -- ^ key
            -> String -- ^ value
            -> GHandler sub master ()
-setSession k v = GHandler . lift . lift . lift . tell $ (:) (k, Just v)
+setSession k = GHandler . lift . lift . lift . modify . Map.insert k
 
 -- | Unsets a session variable. See 'setSession'.
 deleteSession :: String -> GHandler sub master ()
-deleteSession k = GHandler . lift . lift . lift . tell $ (:) (k, Nothing)
+deleteSession = GHandler . lift . lift . lift . modify . Map.delete
 
 -- | Internal use only, not to be confused with 'setHeader'.
 addHeader :: Header -> GHandler sub master ()
@@ -471,28 +479,21 @@
 localNoCurrent =
     GHandler . local (\hd -> hd { handlerRoute = Nothing }) . unGHandler
 
+-- | Lookup for session data.
+lookupSession :: ParamName -> GHandler s m (Maybe ParamValue)
+lookupSession n = GHandler $ do
+    m <- lift $ lift $ lift get
+    return $ Map.lookup n m
+
+-- | Get all session variables.
+getSession :: GHandler s m SessionMap
+getSession = GHandler $ lift $ lift $ lift get
+
 #if TEST
 
 testSuite :: Test
 testSuite = testGroup "Yesod.Handler"
-    [ testCase "finally" caseFinally
+    [
     ]
 
-caseFinally :: Assertion
-caseFinally = do
-    i <- newIORef (1 :: Int)
-    let h = finallyHandler (do
-                liftIO $ writeIORef i 2
-                () <- redirectString RedirectTemporary ""
-                return ()) $ liftIO $ writeIORef i 3
-    let y = runHandler h undefined undefined undefined undefined undefined
-    _ <- unYesodApp y undefined undefined undefined
-    j <- readIORef i
-    j @?= 3
-
 #endif
-
--- | A version of 'finally' which works correctly with short-circuiting.
-finallyHandler :: GHandler s m a -> GHandler s m b -> GHandler s m a
-finallyHandler (GHandler (ReaderT thing)) (GHandler (ReaderT after)) =
-    GHandler $ ReaderT $ \hd -> mapMEitherT (`C.finally` runMEitherT (after hd)) (thing hd)
diff --git a/Yesod/Helpers/Auth.hs b/Yesod/Helpers/Auth.hs
deleted file mode 100644
--- a/Yesod/Helpers/Auth.hs
+++ /dev/null
@@ -1,578 +0,0 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ExistentialQuantification #-}
-{-# LANGUAGE Rank2Types #-}
-{-# LANGUAGE OverloadedStrings #-}
----------------------------------------------------------
---
--- Module        : Yesod.Helpers.Auth
--- Copyright     : Michael Snoyman
--- License       : BSD3
---
--- Maintainer    : Michael Snoyman <michael@snoyman.com>
--- Stability     : Stable
--- Portability   : portable
---
--- Authentication through the authentication package.
---
----------------------------------------------------------
-module Yesod.Helpers.Auth
-    ( -- * Subsite
-      Auth (..)
-    , getAuth
-    , AuthRoute (..)
-      -- * Settings
-    , YesodAuth (..)
-    , Creds (..)
-    , EmailCreds (..)
-    , AuthType (..)
-    , RpxnowSettings (..)
-    , EmailSettings (..)
-    , FacebookSettings (..)
-    , getFacebookUrl
-      -- * Functions
-    , maybeAuth
-    , maybeAuthId
-    , requireAuth
-    , requireAuthId
-    ) where
-
-import qualified Web.Authenticate.Rpxnow as Rpxnow
-import qualified Web.Authenticate.OpenId as OpenId
-import qualified Web.Authenticate.Facebook as Facebook
-
-import Yesod
-import Yesod.Mail (randomString)
-
-import Data.Maybe
-import Data.Int (Int64)
-import Control.Monad
-import System.Random
-import Data.Digest.Pure.MD5
-import Control.Applicative
-import Control.Monad.Attempt
-import Data.ByteString.Lazy.UTF8 (fromString)
-import Data.Object
-import Language.Haskell.TH.Syntax
-
-type AuthId m = Key (AuthEntity m)
-type AuthEmailId m = Key (AuthEmailEntity m)
-
-class ( Yesod master
-      , PersistEntity (AuthEntity master)
-      , PersistEntity (AuthEmailEntity master)
-      ) => YesodAuth master where
-    type AuthEntity master
-    type AuthEmailEntity master
-
-    -- | Default destination on successful login or logout, if no other
-    -- destination exists.
-    defaultDest :: master -> Route master
-
-    getAuthId :: Creds master -> [(String, String)]
-              -> GHandler s master (Maybe (AuthId master))
-
-    -- | Generate a random alphanumeric string.
-    --
-    -- This is used for verify string in email authentication.
-    randomKey :: master -> IO String
-    randomKey _ = do
-        stdgen <- newStdGen
-        return $ fst $ randomString 10 stdgen
-
-    openIdEnabled :: master -> Bool
-    openIdEnabled _ = False
-
-    rpxnowSettings :: master -> Maybe RpxnowSettings
-    rpxnowSettings _ = Nothing
-
-    emailSettings :: master -> Maybe (EmailSettings master)
-    emailSettings _ = Nothing
-
-    -- | client id, secret and requested permissions
-    facebookSettings :: master -> Maybe FacebookSettings
-    facebookSettings _ = Nothing
-
-data Auth = Auth
-
-getAuth :: a -> Auth
-getAuth = const Auth
-
--- | Which subsystem authenticated the user.
-data AuthType = AuthOpenId | AuthRpxnow | AuthEmail | AuthFacebook
-    deriving (Show, Read, Eq)
-
-type Email = String
-type VerKey = String
-type VerUrl = String
-type SaltedPass = String
-type VerStatus = Bool
-
--- | Data stored in a database for each e-mail address.
-data EmailCreds m = EmailCreds
-    { emailCredsId :: AuthEmailId m
-    , emailCredsAuthId :: Maybe (AuthId m)
-    , emailCredsStatus :: VerStatus
-    , emailCredsVerkey :: Maybe VerKey
-    }
-
-data RpxnowSettings = RpxnowSettings
-    { rpxnowApp :: String
-    , rpxnowKey :: String
-    }
-
-data EmailSettings m = EmailSettings
-    { addUnverified :: Email -> VerKey -> GHandler Auth m (AuthEmailId m)
-    , sendVerifyEmail :: Email -> VerKey -> VerUrl -> GHandler Auth m ()
-    , getVerifyKey :: AuthEmailId m -> GHandler Auth m (Maybe VerKey)
-    , setVerifyKey :: AuthEmailId m -> VerKey -> GHandler Auth m ()
-    , verifyAccount :: AuthEmailId m -> GHandler Auth m (Maybe (AuthId m))
-    , getPassword :: AuthId m -> GHandler Auth m (Maybe SaltedPass)
-    , setPassword :: AuthId m -> SaltedPass -> GHandler Auth m ()
-    , getEmailCreds :: Email -> GHandler Auth m (Maybe (EmailCreds m))
-    , getEmail :: AuthEmailId m -> GHandler Auth m (Maybe Email)
-    }
-
-data FacebookSettings = FacebookSettings
-    { fbAppId :: String
-    , fbSecret :: String
-    , fbPerms :: [String]
-    }
-
--- | User credentials
-data Creds m = Creds
-    { credsIdent :: String -- ^ Identifier. Exact meaning depends on 'credsAuthType'.
-    , credsAuthType :: AuthType -- ^ How the user was authenticated
-    , credsEmail :: Maybe String -- ^ Verified e-mail address.
-    , credsDisplayName :: Maybe String -- ^ Display name.
-    , credsId :: Maybe (AuthId m) -- ^ Numeric ID, if used.
-    , credsFacebookToken :: Maybe Facebook.AccessToken
-    }
-
-credsKey :: String
-credsKey = "_ID"
-
-setCreds :: YesodAuth master
-         => Creds master -> [(String, String)] -> GHandler Auth master ()
-setCreds creds extra = do
-    maid <- getAuthId creds extra
-    case maid of
-        Nothing -> return ()
-        Just aid -> setSession credsKey $ show $ fromPersistKey aid
-
--- | Retrieves user credentials, if user is authenticated.
-maybeAuthId :: YesodAuth m => GHandler s m (Maybe (AuthId m))
-maybeAuthId = do
-    ms <- lookupSession credsKey
-    case ms of
-        Nothing -> return Nothing
-        Just s -> case reads s of
-                    [] -> return Nothing
-                    (i, _):_ -> return $ Just $ toPersistKey i
-
-maybeAuth :: ( PersistBackend (YesodDB m (GHandler s m))
-             , YesodPersist m
-             , YesodAuth m
-             ) => GHandler s m (Maybe (AuthId m, AuthEntity m))
-maybeAuth = do
-    maid <- maybeAuthId
-    case maid of
-        Nothing -> return Nothing
-        Just aid -> do
-            ma <- runDB $ get aid
-            case ma of
-                Nothing -> return Nothing
-                Just a -> return $ Just (aid, a)
-
-mkYesodSub "Auth"
-    [ ClassP ''YesodAuth [VarT $ mkName "master"]
-    ]
-    [$parseRoutes|
-/check                   CheckR             GET
-/logout                  LogoutR            GET
-/openid/forward          OpenIdForwardR     GET
-/openid/complete         OpenIdCompleteR    GET
-/login/rpxnow            RpxnowR
-
-/facebook                FacebookR          GET
-
-/register                EmailRegisterR     GET POST
-/verify/#Int64/#String   EmailVerifyR       GET
-/email-login             EmailLoginR        POST
-/set-password            EmailPasswordR     GET POST
-
-/login                   LoginR             GET
-|]
-
-testOpenId :: YesodAuth master => GHandler Auth master ()
-testOpenId = do
-    a <- getYesod
-    unless (openIdEnabled a) notFound
-
-getOpenIdForwardR :: YesodAuth master => GHandler Auth master ()
-getOpenIdForwardR = do
-    testOpenId
-    oid <- runFormGet' $ stringInput "openid"
-    render <- getUrlRender
-    toMaster <- getRouteToMaster
-    let complete = render $ toMaster OpenIdCompleteR
-    res <- runAttemptT $ OpenId.getForwardUrl oid complete
-    attempt
-      (\err -> do
-            setMessage $ string $ show err
-            redirect RedirectTemporary $ toMaster LoginR)
-      (redirectString RedirectTemporary)
-      res
-
-getOpenIdCompleteR :: YesodAuth master => GHandler Auth master ()
-getOpenIdCompleteR = do
-    testOpenId
-    rr <- getRequest
-    let gets' = reqGetParams rr
-    res <- runAttemptT $ OpenId.authenticate gets'
-    toMaster <- getRouteToMaster
-    let onFailure err = do
-        setMessage $ string $ show err
-        redirect RedirectTemporary $ toMaster LoginR
-    let onSuccess (OpenId.Identifier ident) = do
-        y <- getYesod
-        setCreds (Creds ident AuthOpenId Nothing Nothing Nothing Nothing) []
-        redirectUltDest RedirectTemporary $ defaultDest y
-    attempt onFailure onSuccess res
-
-handleRpxnowR :: YesodAuth master => GHandler Auth master ()
-handleRpxnowR = do
-    ay <- getYesod
-    auth <- getYesod
-    apiKey <- case rpxnowKey <$> rpxnowSettings auth of
-                Just x -> return x
-                Nothing -> notFound
-    token1 <- lookupGetParam "token"
-    token2 <- lookupPostParam "token"
-    let token = case token1 `mplus` token2 of
-                    Nothing -> invalidArgs ["token: Value not supplied"]
-                    Just x -> x
-    Rpxnow.Identifier ident extra <- liftIO $ Rpxnow.authenticate apiKey token
-    let creds = Creds
-                    ident
-                    AuthRpxnow
-                    (lookup "verifiedEmail" extra)
-                    (getDisplayName extra)
-                    Nothing
-                    Nothing
-    setCreds creds extra
-    dest1 <- lookupPostParam "dest"
-    dest2 <- lookupGetParam "dest"
-    either (redirect RedirectTemporary) (redirectString RedirectTemporary) $
-        case dest1 `mplus` dest2 of
-            Just "" -> Left $ defaultDest ay
-            Nothing -> Left $ defaultDest ay
-            Just ('#':d) -> Right d
-            Just d -> Right d
-
--- | Get some form of a display name.
-getDisplayName :: [(String, String)] -> Maybe String
-getDisplayName extra =
-    foldr (\x -> mplus (lookup x extra)) Nothing choices
-  where
-    choices = ["verifiedEmail", "email", "displayName", "preferredUsername"]
-
-getCheckR :: YesodAuth master => GHandler Auth master RepHtmlJson
-getCheckR = do
-    creds <- maybeAuthId
-    defaultLayoutJson (do
-        setTitle "Authentication Status"
-        addBody $ html creds) (json creds)
-  where
-    html creds = [$hamlet|
-%h1 Authentication Status
-$if isNothing.creds
-    %p Not logged in.
-$maybe creds _
-    %p Logged in.
-|]
-    json creds =
-        jsonMap
-            [ ("logged_in", jsonScalar $ maybe "false" (const "true") creds)
-            ]
-
-getLogoutR :: YesodAuth master => GHandler Auth master ()
-getLogoutR = do
-    y <- getYesod
-    deleteSession credsKey
-    redirectUltDest RedirectTemporary $ defaultDest y
-
--- | Retrieve user credentials. If user is not logged in, redirects to the
--- 'authRoute'. Sets ultimate destination to current route, so user
--- should be sent back here after authenticating.
-requireAuthId :: YesodAuth m => GHandler sub m (AuthId m)
-requireAuthId = maybeAuthId >>= maybe redirectLogin return
-
-requireAuth :: ( PersistBackend (YesodDB m (GHandler s m))
-               , YesodPersist m
-               , YesodAuth m
-               ) => GHandler s m (AuthId m, AuthEntity m)
-requireAuth = maybeAuth >>= maybe redirectLogin return
-
-redirectLogin :: Yesod m => GHandler s m a
-redirectLogin = do
-    y <- getYesod
-    setUltDest'
-    case authRoute y of
-        Just z -> redirect RedirectTemporary z
-        Nothing -> permissionDenied "Please configure authRoute"
-
-getEmailSettings :: YesodAuth master
-                     => GHandler Auth master (EmailSettings master)
-getEmailSettings = getYesod >>= maybe notFound return . emailSettings
-
-getEmailRegisterR :: YesodAuth master => GHandler Auth master RepHtml
-getEmailRegisterR = do
-    _ae <- getEmailSettings
-    toMaster <- getRouteToMaster
-    defaultLayout $ setTitle "Register a new account" >> addBody [$hamlet|
-%p Enter your e-mail address below, and a confirmation e-mail will be sent to you.
-%form!method=post!action=@toMaster.EmailRegisterR@
-    %label!for=email E-mail
-    %input#email!type=email!name=email!width=150
-    %input!type=submit!value=Register
-|]
-
-postEmailRegisterR :: YesodAuth master => GHandler Auth master RepHtml
-postEmailRegisterR = do
-    ae <- getEmailSettings
-    email <- runFormPost' $ emailInput "email"
-    mecreds <- getEmailCreds ae email
-    (lid, verKey) <-
-        case mecreds of
-            Just (EmailCreds lid _ _ (Just key)) -> return (lid, key)
-            Just (EmailCreds lid _ _ Nothing) -> do
-                y <- getYesod
-                key <- liftIO $ randomKey y
-                setVerifyKey ae lid key
-                return (lid, key)
-            Nothing -> do
-                y <- getYesod
-                key <- liftIO $ randomKey y
-                lid <- addUnverified ae email key
-                return (lid, key)
-    render <- getUrlRender
-    tm <- getRouteToMaster
-    let verUrl = render $ tm $ EmailVerifyR (fromPersistKey lid) verKey
-    sendVerifyEmail ae email verKey verUrl
-    defaultLayout $ setTitle "Confirmation e-mail sent" >> addBody [$hamlet|
-%p A confirmation e-mail has been sent to $email$.
-|]
-
-getEmailVerifyR :: YesodAuth master
-           => Int64 -> String -> GHandler Auth master RepHtml
-getEmailVerifyR lid' key = do
-    let lid = toPersistKey lid'
-    ae <- getEmailSettings
-    realKey <- getVerifyKey ae lid
-    memail <- getEmail ae lid
-    case (realKey == Just key, memail) of
-        (True, Just email) -> do
-            muid <- verifyAccount ae lid
-            case muid of
-                Nothing -> return ()
-                Just uid -> do
-                    setCreds (Creds email AuthEmail (Just email) Nothing (Just uid)
-                              Nothing) []
-                    toMaster <- getRouteToMaster
-                    redirect RedirectTemporary $ toMaster EmailPasswordR
-        _ -> return ()
-    defaultLayout $ do
-        setTitle "Invalid verification key"
-        addBody [$hamlet|
-%p I'm sorry, but that was an invalid verification key.
-|]
-
-postEmailLoginR :: YesodAuth master => GHandler Auth master ()
-postEmailLoginR = do
-    ae <- getEmailSettings
-    (email, pass) <- runFormPost' $ (,)
-        <$> emailInput "email"
-        <*> stringInput "password"
-    y <- getYesod
-    mecreds <- getEmailCreds ae email
-    maid <-
-        case (mecreds >>= emailCredsAuthId, fmap emailCredsStatus mecreds) of
-            (Just aid, Just True) -> do
-                mrealpass <- getPassword ae aid
-                case mrealpass of
-                    Nothing -> return Nothing
-                    Just realpass -> return $
-                        if isValidPass pass realpass
-                            then Just aid
-                            else Nothing
-            _ -> return Nothing
-    case maid of
-        Just aid -> do
-            setCreds (Creds email AuthEmail (Just email) Nothing (Just aid)
-                      Nothing) []
-            redirectUltDest RedirectTemporary $ defaultDest y
-        Nothing -> do
-            setMessage $ string "Invalid email/password combination"
-            toMaster <- getRouteToMaster
-            redirect RedirectTemporary $ toMaster LoginR
-
-getEmailPasswordR :: YesodAuth master => GHandler Auth master RepHtml
-getEmailPasswordR = do
-    _ae <- getEmailSettings
-    toMaster <- getRouteToMaster
-    maid <- maybeAuthId
-    case maid of
-        Just _ -> return ()
-        Nothing -> do
-            setMessage $ string "You must be logged in to set a password"
-            redirect RedirectTemporary $ toMaster EmailLoginR
-    defaultLayout $ do
-        setTitle "Set password"
-        addBody [$hamlet|
-%h3 Set a new password
-%form!method=post!action=@toMaster.EmailPasswordR@
-    %table
-        %tr
-            %th New password
-            %td
-                %input!type=password!name=new
-        %tr
-            %th Confirm
-            %td
-                %input!type=password!name=confirm
-        %tr
-            %td!colspan=2
-                %input!type=submit!value=Submit
-|]
-
-postEmailPasswordR :: YesodAuth master => GHandler Auth master ()
-postEmailPasswordR = do
-    ae <- getEmailSettings
-    (new, confirm) <- runFormPost' $ (,)
-        <$> stringInput "new"
-        <*> stringInput "confirm"
-    toMaster <- getRouteToMaster
-    when (new /= confirm) $ do
-        setMessage $ string "Passwords did not match, please try again"
-        redirect RedirectTemporary $ toMaster EmailPasswordR
-    maid <- maybeAuthId
-    aid <- case maid of
-            Nothing -> do
-                setMessage $ string "You must be logged in to set a password"
-                redirect RedirectTemporary $ toMaster EmailLoginR
-            Just aid -> return aid
-    salted <- liftIO $ saltPass new
-    setPassword ae aid salted
-    setMessage $ string "Password updated"
-    y <- getYesod
-    redirect RedirectTemporary $ defaultDest y
-
-saltLength :: Int
-saltLength = 5
-
-isValidPass :: String -- ^ cleartext password
-            -> String -- ^ salted password
-            -> Bool
-isValidPass clear salted =
-    let salt = take saltLength salted
-     in salted == saltPass' salt clear
-
-saltPass :: String -> IO String
-saltPass pass = do
-    stdgen <- newStdGen
-    let salt = take saltLength $ randomRs ('A', 'Z') stdgen
-    return $ saltPass' salt pass
-
-saltPass' :: String -> String -> String
-saltPass' salt pass = salt ++ show (md5 $ fromString $ salt ++ pass)
-
-getFacebookR :: YesodAuth master => GHandler Auth master ()
-getFacebookR = do
-    y <- getYesod
-    a <- facebookSettings <$> getYesod
-    case a of
-        Nothing -> notFound
-        Just (FacebookSettings cid secret _) -> do
-            render <- getUrlRender
-            tm <- getRouteToMaster
-            let fb = Facebook.Facebook cid secret $ render $ tm FacebookR
-            code <- runFormGet' $ stringInput "code"
-            at <- liftIO $ Facebook.getAccessToken fb code
-            so <- liftIO $ Facebook.getGraphData at "me"
-            let c = fromMaybe (error "Invalid response from Facebook") $ do
-                m <- fromMapping so
-                id' <- lookupScalar "id" m
-                let name = lookupScalar "name" m
-                let email = lookupScalar "email" m
-                let id'' = "http://graph.facebook.com/" ++ id'
-                return $ Creds id'' AuthFacebook email name Nothing $ Just at
-            setCreds c []
-            redirectUltDest RedirectTemporary $ defaultDest y
-
-getFacebookUrl :: YesodAuth m
-               => (AuthRoute -> Route m) -> GHandler s m (Maybe String)
-getFacebookUrl tm = do
-    y <- getYesod
-    render <- getUrlRender
-    case facebookSettings y of
-        Nothing -> return Nothing
-        Just f -> do
-            let fb =
-                    Facebook.Facebook
-                        (fbAppId f)
-                        (fbSecret f)
-                        (render $ tm FacebookR)
-            return $ Just $ Facebook.getForwardUrl fb $ fbPerms f
-
-getLoginR :: YesodAuth master => GHandler Auth master RepHtml
-getLoginR = do
-    lookupGetParam "dest" >>= maybe (return ()) setUltDestString
-    tm <- getRouteToMaster
-    y <- getYesod
-    fb <- getFacebookUrl tm
-    defaultLayout $ do
-        setTitle "Login"
-        addStyle [$cassius|
-#openid
-    background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%;
-    padding-left: 18px;
-|]
-        addBody [$hamlet|
-$maybe emailSettings.y _
-    %h3 Email
-    %form!method=post!action=@tm.EmailLoginR@
-        %table
-            %tr
-                %th E-mail
-                %td
-                    %input!type=email!name=email
-            %tr
-                %th Password
-                %td
-                    %input!type=password!name=password
-            %tr
-                %td!colspan=2
-                    %input!type=submit!value="Login via email"
-                    %a!href=@tm.EmailRegisterR@ I don't have an account
-$if openIdEnabled.y
-    %h3 OpenID
-    %form!action=@tm.OpenIdForwardR@
-        %label!for=openid OpenID: $
-        %input#openid!type=text!name=openid
-        %input!type=submit!value="Login via OpenID"
-$maybe fb f
-    %h3 Facebook
-    %p
-        %a!href=$f$ Login via Facebook
-$maybe rpxnowSettings.y r
-    %h3 Rpxnow
-    %p
-        %a!onclick="return false;"!href="https://$rpxnowApp.r$.rpxnow.com/openid/v2/signin?token_url=@tm.RpxnowR@"
-            Login via Rpxnow
-|]
diff --git a/Yesod/Helpers/Crud.hs b/Yesod/Helpers/Crud.hs
--- a/Yesod/Helpers/Crud.hs
+++ b/Yesod/Helpers/Crud.hs
@@ -55,7 +55,7 @@
     toMaster <- getRouteToMaster
     defaultLayout $ do
         setTitle "Items"
-        addBody [$hamlet|
+        addWidget [$hamlet|
 %h1 Items
 %ul
     $forall items item
@@ -115,7 +115,7 @@
     toMaster <- getRouteToMaster
     defaultLayout $ do
         setTitle "Confirm delete"
-        addBody [$hamlet|
+        addWidget [$hamlet|
 %form!method=post!action=@toMaster.CrudDeleteR.s@
     %h1 Really delete?
     %p Do you really want to delete $itemTitle.item$?
@@ -143,7 +143,7 @@
     -> GHandler (Crud master a) master RepHtml
 crudHelper title me isPost = do
     crud <- getYesodSub
-    (errs, form, enctype) <- runFormPost $ toForm $ fmap snd me
+    (errs, form, enctype, hidden) <- runFormPost $ toForm $ fmap snd me
     toMaster <- getRouteToMaster
     case (isPost, errs) of
         (True, FormSuccess a) -> do
@@ -156,10 +156,8 @@
                                        $ toSinglePiece eid
         _ -> return ()
     defaultLayout $ do
-        wrapWidget form (wrapForm toMaster enctype)
         setTitle $ string title
-  where
-    wrapForm toMaster enctype form = [$hamlet|
+        addWidget [$hamlet|
 %p
     %a!href=@toMaster.CrudListR@ Return to list
 %h1 $title$
@@ -168,6 +166,7 @@
         ^form^
         %tr
             %td!colspan=2
+                $hidden$
                 %input!type=submit
                 $maybe me e
                     \ $
diff --git a/Yesod/Helpers/Static.hs b/Yesod/Helpers/Static.hs
--- a/Yesod/Helpers/Static.hs
+++ b/Yesod/Helpers/Static.hs
@@ -45,13 +45,13 @@
 import Yesod hiding (lift)
 import Data.List (intercalate)
 import Language.Haskell.TH.Syntax
+import Web.Routes
 
 import qualified Data.ByteString.Lazy as L
 import Data.Digest.Pure.MD5
-import qualified Codec.Binary.Base64Url
-import qualified Data.ByteString as S
+import qualified Data.ByteString.Base64
+import qualified Data.ByteString.Char8 as S8
 import qualified Data.Serialize
-import Yesod.WebRoutes
 
 #if TEST
 import Test.Framework (testGroup, Test)
@@ -127,6 +127,7 @@
 
 notHidden :: FilePath -> Bool
 notHidden ('.':_) = False
+notHidden "tmp" = False
 notHidden _ = True
 
 getFileList :: FilePath -> IO [[String]]
@@ -187,8 +188,13 @@
 --
 -- This function returns the first 8 characters of the hash.
 base64md5 :: L.ByteString -> String
-base64md5 = take 8
-          . Codec.Binary.Base64Url.encode
-          . S.unpack
+base64md5 = map go
+          . take 8
+          . S8.unpack
+          . Data.ByteString.Base64.encode
           . Data.Serialize.encode
           . md5
+  where
+    go '+' = '-'
+    go '/' = '_'
+    go c   = c
diff --git a/Yesod/Internal.hs b/Yesod/Internal.hs
--- a/Yesod/Internal.hs
+++ b/Yesod/Internal.hs
@@ -19,12 +19,26 @@
     , locationToHamlet
     , runUniqueList
     , toUnique
+      -- * UTF8 helpers
+    , bsToChars
+    , lbsToChars
+    , charsToBs
     ) where
 
 import Text.Hamlet (Hamlet, hamlet, Html)
 import Data.Monoid (Monoid (..))
 import Data.List (nub)
 
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as L
+
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.Encoding.Error as T
+
+import qualified Data.Text.Lazy as LT
+import qualified Data.Text.Lazy.Encoding as LT
+
 -- | Responses to indicate some form of an error occurred. These are different
 -- from 'SpecialResponse' in that they allow for custom error pages.
 data ErrorResponse =
@@ -71,3 +85,12 @@
     deriving Monoid
 newtype Body url = Body (Hamlet url)
     deriving Monoid
+
+lbsToChars :: L.ByteString -> String
+lbsToChars = LT.unpack . LT.decodeUtf8With T.lenientDecode
+
+bsToChars :: S.ByteString -> String
+bsToChars = T.unpack . T.decodeUtf8With T.lenientDecode
+
+charsToBs :: String -> S.ByteString
+charsToBs = T.encodeUtf8 . T.pack
diff --git a/Yesod/Mail.hs b/Yesod/Mail.hs
deleted file mode 100644
--- a/Yesod/Mail.hs
+++ /dev/null
@@ -1,120 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Yesod.Mail
-    ( Boundary (..)
-    , Mail (..)
-    , Part (..)
-    , Encoding (..)
-    , renderMail
-    , renderMail'
-    , sendmail
-    , Disposition (..)
-    , renderSendMail
-    , randomString
-    ) where
-
-import qualified Data.ByteString.Lazy as L
-import Text.Blaze.Builder.Utf8
-import Text.Blaze.Builder.Core
-import Data.Monoid
-import System.Random
-import Control.Arrow
-import System.Process
-import System.IO
-import System.Exit
-import Codec.Binary.Base64 (encode)
-import Control.Monad ((<=<))
-
-randomString :: RandomGen d => Int -> d -> (String, d)
-randomString len =
-    first (map toChar) . sequence' (replicate len (randomR (0, 61)))
-  where
-    sequence' [] g = ([], g)
-    sequence' (f:fs) g =
-        let (f', g') = f g
-            (fs', g'') = sequence' fs g'
-         in (f' : fs', g'')
-    toChar i
-        | i < 26 = toEnum $ i + fromEnum 'A'
-        | i < 52 = toEnum $ i + fromEnum 'a' - 26
-        | otherwise = toEnum $ i + fromEnum '0' - 52
-
-newtype Boundary = Boundary { unBoundary :: String }
-instance Random Boundary where
-    randomR = const random
-    random = first Boundary . randomString 10
-
-data Mail = Mail
-    { mailHeaders :: [(String, String)]
-    , mailPlain :: String
-    , mailParts :: [Part]
-    }
-
-data Encoding = None | Base64
-
-data Part = Part
-    { partType :: String -- ^ content type
-    , partEncoding :: Encoding
-    , partDisposition :: Disposition
-    , partContent :: L.ByteString
-    }
-
-data Disposition = Inline | Attachment String
-
-renderMail :: Boundary -> Mail -> L.ByteString
-renderMail (Boundary b) (Mail headers plain parts) = toLazyByteString $ mconcat
-    [ mconcat $ map showHeader headers
-    , mconcat $ map showHeader
-        [ ("MIME-Version", "1.0")
-        , ("Content-Type", "multipart/mixed; boundary=\""
-            ++ b ++ "\"")
-        ]
-    , fromByteString "\n"
-    , fromString plain
-    , mconcat $ map showPart parts
-    , fromByteString "\n--"
-    , fromString b
-    , fromByteString "--"
-    ]
-  where
-    showHeader (k, v) = mconcat
-        [ fromString k
-        , fromByteString ": "
-        , fromString v
-        , fromByteString "\n"
-        ]
-    showPart (Part contentType encoding disposition content) = mconcat
-        [ fromByteString "\n--"
-        , fromString b
-        , fromByteString "\n"
-        , showHeader ("Content-Type", contentType)
-        , case encoding of
-            None -> mempty
-            Base64 -> showHeader ("Content-Transfer-Encoding", "base64")
-        , case disposition of
-            Inline -> mempty
-            Attachment filename ->
-                showHeader ("Content-Disposition", "attachment; filename=" ++ filename)
-        , fromByteString "\n"
-        , case encoding of
-            None -> writeList writeByteString $ L.toChunks content
-            Base64 -> writeList writeByte $ map (toEnum . fromEnum) $ encode $ L.unpack content
-        ]
-
-renderMail' :: Mail -> IO L.ByteString
-renderMail' m = do
-    b <- randomIO
-    return $ renderMail b m
-
-sendmail :: L.ByteString -> IO ()
-sendmail lbs = do
-    (Just hin, _, _, phandle) <- createProcess $ (proc
-        "/usr/sbin/sendmail" ["-t"]) { std_in = CreatePipe }
-    L.hPut hin lbs
-    hClose hin
-    exitCode <- waitForProcess phandle
-    case exitCode of
-        ExitSuccess -> return ()
-        _ -> error $ "sendmail exited with error code " ++ show exitCode
-
-renderSendMail :: Mail -> IO ()
-renderSendMail = sendmail <=< renderMail'
diff --git a/Yesod/Request.hs b/Yesod/Request.hs
--- a/Yesod/Request.hs
+++ b/Yesod/Request.hs
@@ -28,13 +28,11 @@
     , lookupGetParam
     , lookupPostParam
     , lookupCookie
-    , lookupSession
     , lookupFile
       -- ** Multi-lookup
     , lookupGetParams
     , lookupPostParams
     , lookupCookies
-    , lookupSessions
     , lookupFiles
       -- * Parameter type synonyms
     , ParamName
@@ -98,8 +96,6 @@
 data Request = Request
     { reqGetParams :: [(ParamName, ParamValue)]
     , reqCookies :: [(ParamName, ParamValue)]
-      -- | Session data stored in a cookie via the clientsession package.
-    , reqSession :: [(ParamName, ParamValue)]
       -- | The POST parameters and submitted files. This is stored in an IO
       -- thunk, which essentially means it will be computed once at most, but
       -- only if requested. This allows avoidance of the potentially costly
@@ -108,6 +104,8 @@
     , reqWaiRequest :: W.Request
       -- | Languages which the client supports.
     , reqLangs :: [String]
+      -- | A random, session-specific nonce used to prevent CSRF attacks.
+    , reqNonce :: String
     }
 
 lookup' :: Eq a => a -> [(a, b)] -> [b]
@@ -161,13 +159,3 @@
 lookupCookies pn = do
     rr <- getRequest
     return $ lookup' pn $ reqCookies rr
-
--- | Lookup for session data.
-lookupSession :: RequestReader m => ParamName -> m (Maybe ParamValue)
-lookupSession = liftM listToMaybe . lookupSessions
-
--- | Lookup for session data.
-lookupSessions :: RequestReader m => ParamName -> m [ParamValue]
-lookupSessions pn = do
-    rr <- getRequest
-    return $ lookup' pn $ reqSession rr
diff --git a/Yesod/WebRoutes.hs b/Yesod/WebRoutes.hs
deleted file mode 100644
--- a/Yesod/WebRoutes.hs
+++ /dev/null
@@ -1,8 +0,0 @@
--- | This module should be removed when web-routes incorporates necessary support.
-module Yesod.WebRoutes
-    ( encodePathInfo
-    , Site (..)
-    ) where
-
-import Web.Routes.Base (encodePathInfo)
-import Web.Routes.Site (Site (..))
diff --git a/Yesod/Widget.hs b/Yesod/Widget.hs
--- a/Yesod/Widget.hs
+++ b/Yesod/Widget.hs
@@ -8,24 +8,29 @@
 module Yesod.Widget
     ( -- * Datatype
       GWidget (..)
-    , Widget
     , liftHandler
       -- * Creating
-    , newIdent
+      -- ** Head of page
     , setTitle
-    , addStyle
+    , addHamletHead
+    , addHtmlHead
+      -- ** Body
+    , addHamlet
+    , addHtml
+    , addWidget
+      -- ** CSS
+    , addCassius
     , addStylesheet
     , addStylesheetRemote
     , addStylesheetEither
+      -- ** Javascript
+    , addJulius
     , addScript
     , addScriptRemote
     , addScriptEither
-    , addHead
-    , addBody
-    , addJavascript
-      -- * Manipulating
-    , wrapWidget
+      -- * Utilities
     , extractBody
+    , newIdent
     ) where
 
 import Data.Monoid
@@ -34,17 +39,22 @@
 import Text.Hamlet
 import Text.Cassius
 import Text.Julius
-import Yesod.Handler (Route, GHandler)
+import Yesod.Handler (Route, GHandler, HandlerData)
 import Control.Applicative (Applicative)
 import Control.Monad.IO.Class (MonadIO)
 import Control.Monad.Trans.Class (lift)
-import "MonadCatchIO-transformers" Control.Monad.CatchIO (MonadCatchIO)
 import Yesod.Internal
 
+import Control.Monad.Invert (MonadInvertIO (..))
+import Control.Monad (liftM)
+import qualified Data.Map as Map
+
 -- | A generic widget, allowing specification of both the subsite and master
 -- site datatypes. This is basically a large 'WriterT' stack keeping track of
 -- dependencies along with a 'StateT' to track unique identifiers.
-newtype GWidget sub master a = GWidget (
+newtype GWidget s m a = GWidget { unGWidget :: GWInner s m a }
+    deriving (Functor, Applicative, Monad, MonadIO)
+type GWInner sub master =
     WriterT (Body (Route master)) (
     WriterT (Last Title) (
     WriterT (UniqueList (Script (Route master))) (
@@ -54,22 +64,28 @@
     WriterT (Head (Route master)) (
     StateT Int (
     GHandler sub master
-    )))))))) a)
-    deriving (Functor, Applicative, Monad, MonadIO, MonadCatchIO)
+    ))))))))
 instance Monoid (GWidget sub master ()) where
     mempty = return ()
     mappend x y = x >> y
--- | A 'GWidget' specialized to when the subsite and master site are the same.
-type Widget y = GWidget y y
+instance MonadInvertIO (GWidget s m) where
+    newtype InvertedIO (GWidget s m) a =
+        InvGWidgetIO
+            { runInvGWidgetIO :: InvertedIO (GWInner s m) a
+            }
+    type InvertedArg (GWidget s m) =
+        (Int, (HandlerData s m, (Map.Map String String, ())))
+    invertIO = liftM (fmap InvGWidgetIO) . invertIO . unGWidget
+    revertIO f = GWidget $ revertIO $ liftM runInvGWidgetIO . f
 
 instance HamletValue (GWidget s m ()) where
     newtype HamletMonad (GWidget s m ()) a =
         GWidget' { runGWidget' :: GWidget s m a }
     type HamletUrl (GWidget s m ()) = Route m
     toHamletValue = runGWidget'
-    htmlToHamletMonad = GWidget' . addBody . const
+    htmlToHamletMonad = GWidget' . addHtml
     urlToHamletMonad url params = GWidget' $
-        addBody $ \r -> preEscapedString (r url params)
+        addHamlet $ \r -> preEscapedString (r url params)
     fromHamletValue = GWidget'
 instance Monad (HamletMonad (GWidget s m ())) where
     return = GWidget' . return
@@ -85,14 +101,27 @@
 setTitle :: Html -> GWidget sub master ()
 setTitle = GWidget . lift . tell . Last . Just . Title
 
--- | Add some raw HTML to the head tag.
-addHead :: Hamlet (Route master) -> GWidget sub master ()
-addHead = GWidget . lift . lift . lift . lift . lift . lift . tell . Head
+-- | Add a 'Hamlet' to the head tag.
+addHamletHead :: Hamlet (Route master) -> GWidget sub master ()
+addHamletHead = GWidget . lift . lift . lift . lift . lift . lift . tell . Head
 
--- | Add some raw HTML to the body tag.
-addBody :: Hamlet (Route master) -> GWidget sub master ()
-addBody = GWidget . tell . Body
+-- | Add a 'Html' to the head tag.
+addHtmlHead :: Html -> GWidget sub master ()
+addHtmlHead = GWidget . lift . lift . lift . lift . lift . lift . tell . Head . const
 
+-- | Add a 'Hamlet' to the body tag.
+addHamlet :: Hamlet (Route master) -> GWidget sub master ()
+addHamlet = GWidget . tell . Body
+
+-- | Add a 'Html' to the body tag.
+addHtml :: Html -> GWidget sub master ()
+addHtml = GWidget . tell . Body . const
+
+-- | Add another widget. This is defined as 'id', by can help with types, and
+-- makes widget blocks look more consistent.
+addWidget :: GWidget s m () -> GWidget s m ()
+addWidget = id
+
 -- | Get a unique identifier.
 newIdent :: GWidget sub master String
 newIdent = GWidget $ lift $ lift $ lift $ lift $ lift $ lift $ lift $ do
@@ -102,8 +131,8 @@
     return $ "w" ++ show i'
 
 -- | Add some raw CSS to the style tag.
-addStyle :: Cassius (Route master) -> GWidget sub master ()
-addStyle = GWidget . lift . lift . lift . lift . tell . Just
+addCassius :: Cassius (Route master) -> GWidget sub master ()
+addCassius = GWidget . lift . lift . lift . lift . tell . Just
 
 -- | Link to the specified local stylesheet.
 addStylesheet :: Route master -> GWidget sub master ()
@@ -130,18 +159,8 @@
     GWidget . lift . lift . tell . toUnique . Script . Remote
 
 -- | Include raw Javascript in the page's script tag.
-addJavascript :: Julius (Route master) -> GWidget sub master ()
-addJavascript = GWidget . lift . lift . lift . lift . lift. tell . Just
-
--- | Modify the given 'GWidget' by wrapping the body tag HTML code with the
--- given function. You might also consider using 'extractBody'.
-wrapWidget :: GWidget s m a
-           -> (Hamlet (Route m) -> Hamlet (Route m))
-           -> GWidget s m a
-wrapWidget (GWidget w) wrap =
-    GWidget $ mapWriterT (fmap go) w
-  where
-    go (a, Body h) = (a, Body $ wrap h)
+addJulius :: Julius (Route master) -> GWidget sub master ()
+addJulius = GWidget . lift . lift . lift . lift . lift. tell . Just
 
 -- | Pull out the HTML tag contents and return it. Useful for performing some
 -- manipulations. It can be easier to use this sometimes than 'wrapWidget'.
diff --git a/Yesod/Yesod.hs b/Yesod/Yesod.hs
--- a/Yesod/Yesod.hs
+++ b/Yesod/Yesod.hs
@@ -36,6 +36,7 @@
 import Yesod.Content hiding (testSuite)
 import Yesod.Json hiding (testSuite)
 import Yesod.Handler hiding (testSuite)
+import qualified Data.ByteString.UTF8 as BSU
 #else
 import Yesod.Content
 import Yesod.Json
@@ -49,20 +50,19 @@
 import Yesod.Internal
 import Web.ClientSession (getKey, defaultKeyFile)
 import qualified Web.ClientSession as CS
-import qualified Data.ByteString.UTF8 as BSU
 import Database.Persist
 import Control.Monad.Trans.Class (MonadTrans (..))
-import Control.Monad.Attempt (Failure)
+import Control.Failure (Failure)
 import qualified Data.ByteString as S
 import qualified Network.Wai.Middleware.CleanPath
 import qualified Data.ByteString.Lazy as L
-import Yesod.WebRoutes
 import Data.Monoid
 import Control.Monad.Trans.Writer
 import Control.Monad.Trans.State hiding (get)
 import Text.Hamlet
 import Text.Cassius
 import Text.Julius
+import Web.Routes
 
 #if TEST
 import Test.Framework (testGroup, Test)
@@ -209,6 +209,11 @@
                      -> GHandler sub a (Maybe (Either String (Route a, [(String, String)])))
     addStaticContent _ _ _ = return Nothing
 
+    -- | Whether or not to tie a session to a specific IP address. Defaults to
+    -- 'True'.
+    sessionIpAddress :: a -> Bool
+    sessionIpAddress _ = True
+
 data AuthResult = Authorized | AuthenticationRequired | Unauthorized String
     deriving (Eq, Show, Read)
 
@@ -256,13 +261,13 @@
              -> GHandler sub master ChooseRep
 applyLayout' title body = fmap chooseRep $ defaultLayout $ do
     setTitle title
-    addBody body
+    addHamlet body
 
 -- | The default error handler for 'errorHandler'.
 defaultErrorHandler :: Yesod y => ErrorResponse -> GHandler sub y ChooseRep
 defaultErrorHandler NotFound = do
     r <- waiRequest
-    let path' = BSU.toString $ pathInfo r
+    let path' = bsToChars $ pathInfo r
     applyLayout' "Not Found" $ [$hamlet|
 %h1 Not Found
 %p $path'$
diff --git a/scaffold.hs b/scaffold.hs
--- a/scaffold.hs
+++ b/scaffold.hs
@@ -6,6 +6,9 @@
 import Language.Haskell.TH.Syntax
 import Data.Time (getCurrentTime, utctDay, toGregorian)
 import Control.Applicative ((<$>))
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Text.Lazy as LT
+import qualified Data.Text.Lazy.Encoding as LT
 
 main :: IO ()
 main = do
@@ -44,7 +47,7 @@
 
     let writeFile' fp s = do
             putStrLn $ "Generating " ++ fp
-            writeFile (dir ++ '/' : fp) s
+            L.writeFile (dir ++ '/' : fp) $ LT.encodeUtf8 $ LT.pack s
         mkDir fp = createDirectoryIfMissing True $ dir ++ '/' : fp
 
     mkDir "Handler"
diff --git a/scaffold/Controller_hs.cg b/scaffold/Controller_hs.cg
--- a/scaffold/Controller_hs.cg
+++ b/scaffold/Controller_hs.cg
@@ -32,9 +32,7 @@
 -- migrations handled by Yesod.
 with~sitearg~ :: (Application -> IO a) -> IO a
 with~sitearg~ f = Settings.withConnectionPool $ \p -> do
-    flip runConnectionPool p $ runMigration $ do
-        migrate (undefined :: User)
-        migrate (undefined :: Email)
+    runConnectionPool (runMigration migrateAll) p
     let h = ~sitearg~ s p
     toWaiApp h >>= f
   where
diff --git a/scaffold/Model_hs.cg b/scaffold/Model_hs.cg
--- a/scaffold/Model_hs.cg
+++ b/scaffold/Model_hs.cg
@@ -2,11 +2,13 @@
 module Model where
 
 import Yesod
+import Database.Persist.TH (share2)
+import Database.Persist.GenericSql (mkMigrate)
 
 -- You can define all of your database entities here. You can find more
 -- information on persistent and how to declare entities at:
 -- http://docs.yesodweb.com/book/persistent/
-mkPersist [$persist|
+share2 mkPersist (mkMigrate "migrateAll") [$persist|
 User
     ident String
     password String null update
diff --git a/scaffold/Root_hs.cg b/scaffold/Root_hs.cg
--- a/scaffold/Root_hs.cg
+++ b/scaffold/Root_hs.cg
@@ -16,7 +16,7 @@
     defaultLayout $ do
         h2id <- newIdent
         setTitle "~project~ homepage"
-        addBody $(hamletFile "homepage")
-        addStyle $(cassiusFile "homepage")
-        addJavascript $(juliusFile "homepage")
+        addCassius $(cassiusFile "homepage")
+        addJulius $(juliusFile "homepage")
+        addWidget $(hamletFile "homepage")
 
diff --git a/scaffold/Settings_hs.cg b/scaffold/Settings_hs.cg
--- a/scaffold/Settings_hs.cg
+++ b/scaffold/Settings_hs.cg
@@ -22,7 +22,7 @@
 import qualified Text.Julius as H
 import Language.Haskell.TH.Syntax
 import Database.Persist.~upper~
-import Yesod (MonadCatchIO)
+import Yesod (MonadInvertIO)
 
 -- | The base URL for your application. This will usually be different for
 -- development and production. Yesod automatically constructs URLs for you,
@@ -93,13 +93,13 @@
 -- is used for increased performance.
 --
 -- You can see an example of how to call these functions in Handler/Root.hs
+--
+-- Note: due to polymorphic Hamlet templates, hamletFileDebug is no longer
+-- used; to get the same auto-loading effect, it is recommended that you
+-- use the devel server.
 
 hamletFile :: FilePath -> Q Exp
-#ifdef PRODUCTION
 hamletFile x = H.hamletFile $ "hamlet/" ++ x ++ ".hamlet"
-#else
-hamletFile x = H.hamletFileDebug $ "hamlet/" ++ x ++ ".hamlet"
-#endif
 
 cassiusFile :: FilePath -> Q Exp
 #ifdef PRODUCTION
@@ -119,9 +119,9 @@
 -- database actions using a pool, respectively. It is used internally
 -- by the scaffolded application, and therefore you will rarely need to use
 -- them yourself.
-withConnectionPool :: MonadCatchIO m => (ConnectionPool -> m a) -> m a
+withConnectionPool :: MonadInvertIO m => (ConnectionPool -> m a) -> m a
 withConnectionPool = with~upper~Pool connStr connectionCount
 
-runConnectionPool :: MonadCatchIO m => SqlPersist m a -> ConnectionPool -> m a
+runConnectionPool :: MonadInvertIO m => SqlPersist m a -> ConnectionPool -> m a
 runConnectionPool = runSqlPool
 
diff --git a/scaffold/cabal.cg b/scaffold/cabal.cg
--- a/scaffold/cabal.cg
+++ b/scaffold/cabal.cg
@@ -20,15 +20,19 @@
     if flag(production)
         Buildable: False
     main-is:       simple-server.hs
-    build-depends: base >= 4 && < 5,
-                   yesod >= 0.5 && < 0.6,
-                   wai-extra,
-                   directory,
-                   bytestring,
-                   persistent,
-                   persistent-~lower~,
-                   template-haskell,
-                   hamlet
+    build-depends: base         >= 4       && < 5
+                 , yesod        >= 0.6     && < 0.7
+                 , yesod-auth   >= 0.2     && < 0.3
+                 , mime-mail    >= 0.0     && < 0.1
+                 , wai-extra
+                 , directory
+                 , bytestring
+                 , text
+                 , persistent
+                 , persistent-~lower~
+                 , template-haskell
+                 , hamlet
+                 , web-routes
     ghc-options:   -Wall
     extensions:    TemplateHaskell, QuasiQuotes, TypeFamilies
 
@@ -47,7 +51,7 @@
         Buildable: False
     cpp-options:   -DPRODUCTION
     main-is:       fastcgi.hs
-    build-depends: wai-handler-fastcgi
+    build-depends: wai-handler-fastcgi >= 0.2.2 && < 0.3
     ghc-options:   -Wall
     extensions:    TemplateHaskell, QuasiQuotes, TypeFamilies
 
diff --git a/scaffold/site-arg.cg b/scaffold/site-arg.cg
--- a/scaffold/site-arg.cg
+++ b/scaffold/site-arg.cg
@@ -1,5 +1,5 @@
 Great, we'll be creating ~project~ today, and placing it in ~dir~.
-What's going to be the name of your site argument datatype? This name must
+What's going to be the name of your foundation datatype? This name must
 start with a capital letter.
 
-Site argument: 
+Foundation: 
diff --git a/scaffold/sitearg_hs.cg b/scaffold/sitearg_hs.cg
--- a/scaffold/sitearg_hs.cg
+++ b/scaffold/sitearg_hs.cg
@@ -4,6 +4,7 @@
     , ~sitearg~Route (..)
     , resources~sitearg~
     , Handler
+    , Widget
     , maybeAuth
     , requireAuth
     , module Yesod
@@ -14,18 +15,22 @@
     ) where
 
 import Yesod
-import Yesod.Mail
 import Yesod.Helpers.Static
 import Yesod.Helpers.Auth
+import Yesod.Helpers.Auth.OpenId
+import Yesod.Helpers.Auth.Email
 import qualified Settings
 import System.Directory
 import qualified Data.ByteString.Lazy as L
-import Yesod.WebRoutes
+import Web.Routes.Site (Site (formatPathSegments))
 import Database.Persist.GenericSql
 import Settings (hamletFile, cassiusFile, juliusFile)
 import Model
-import Control.Monad (join)
 import Data.Maybe (isJust)
+import Control.Monad (join)
+import Network.Mail.Mime
+import qualified Data.Text.Lazy
+import qualified Data.Text.Lazy.Encoding
 
 -- | The site argument for your application. This can be a good place to
 -- keep settings and values requiring initialization before your application
@@ -40,6 +45,10 @@
 -- will need to be of this type.
 type Handler = GHandler ~sitearg~ ~sitearg~
 
+-- | A useful synonym; most of the widgets functions in your application
+-- will need to be of this type.
+type Widget = GWidget ~sitearg~ ~sitearg~
+
 -- This is where we define all of the routes in our application. For a full
 -- explanation of the syntax, please see:
 -- http://docs.yesodweb.com/book/web-routes-quasi/
@@ -78,7 +87,7 @@
         mmsg <- getMessage
         pc <- widgetToPageContent $ do
             widget
-            addStyle $(Settings.cassiusFile "default-layout")
+            addCassius $(Settings.cassiusFile "default-layout")
         hamletToRepHtml $(Settings.hamletFile "default-layout")
 
     -- This is done to provide an optimization for serving static files from
@@ -111,72 +120,93 @@
     runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db
 
 instance YesodAuth ~sitearg~ where
-    type AuthEntity ~sitearg~ = User
-    type AuthEmailEntity ~sitearg~ = Email
+    type AuthId ~sitearg~ = UserId
 
-    defaultDest _ = RootR
+    -- Where to send a user after successful login
+    loginDest _ = RootR
+    -- Where to send a user after logout
+    logoutDest _ = RootR
 
-    getAuthId creds _extra = runDB $ do
+    getAuthId creds = runDB $ do
         x <- getBy $ UniqueUser $ credsIdent creds
         case x of
             Just (uid, _) -> return $ Just uid
             Nothing -> do
                 fmap Just $ insert $ User (credsIdent creds) Nothing
 
-    openIdEnabled _ = True
+    showAuthId _ = showIntegral
+    readAuthId _ = readIntegral
 
-    emailSettings _ = Just EmailSettings
-        { addUnverified = \email verkey ->
-            runDB $ insert $ Email email Nothing (Just verkey)
-        , sendVerifyEmail = sendVerifyEmail'
-        , getVerifyKey = runDB . fmap (join . fmap emailVerkey) . get
-        , setVerifyKey = \eid key -> runDB $ update eid [EmailVerkey $ Just key]
-        , verifyAccount = \eid -> runDB $ do
-            me <- get eid
-            case me of
-                Nothing -> return Nothing
-                Just e -> do
-                    let email = emailEmail e
-                    case emailUser e of
-                        Just uid -> return $ Just uid
-                        Nothing -> do
-                            uid <- insert $ User email Nothing
-                            update eid [EmailUser $ Just uid]
-                            return $ Just uid
-        , getPassword = runDB . fmap (join . fmap userPassword) . get
-        , setPassword = \uid pass -> runDB $ update uid [UserPassword $ Just pass]
-        , getEmailCreds = \email -> runDB $ do
-            me <- getBy $ UniqueEmail email
-            case me of
-                Nothing -> return Nothing
-                Just (eid, e) -> return $ Just EmailCreds
-                    { emailCredsId = eid
-                    , emailCredsAuthId = emailUser e
-                    , emailCredsStatus = isJust $ emailUser e
-                    , emailCredsVerkey = emailVerkey e
-                    }
-        , getEmail = runDB . fmap (fmap emailEmail) . get
-        }
+    authPlugins = [ authOpenId
+                  , authEmail
+                  ]
 
-sendVerifyEmail' :: String -> String -> String -> GHandler Auth m ()
-sendVerifyEmail' email _ verurl =
-    liftIO $ renderSendMail Mail
-            { mailHeaders =
-                [ ("From", "noreply")
-                , ("To", email)
-                , ("Subject", "Verify your email address")
+instance YesodAuthEmail ~sitearg~ where
+    type AuthEmailId ~sitearg~ = EmailId
+
+    showAuthEmailId _ = showIntegral
+    readAuthEmailId _ = readIntegral
+
+    addUnverified email verkey =
+        runDB $ insert $ Email email Nothing $ Just verkey
+    sendVerifyEmail email _ verurl = liftIO $ renderSendMail Mail
+        { mailHeaders =
+            [ ("From", "noreply")
+            , ("To", email)
+            , ("Subject", "Verify your email address")
+            ]
+        , mailParts = [[textPart, htmlPart]]
+        }
+      where
+        textPart = Part
+            { partType = "text/plain; charset=utf-8"
+            , partEncoding = None
+            , partFilename = Nothing
+            , partContent = Data.Text.Lazy.Encoding.encodeUtf8
+                          $ Data.Text.Lazy.pack $ unlines
+                [ "Please confirm your email address by clicking on the link below."
+                , ""
+                , verurl
+                , ""
+                , "Thank you"
                 ]
-            , mailPlain = verurl
-            , mailParts = return Part
-                { partType = "text/html; charset=utf-8"
-                , partEncoding = None
-                , partDisposition = Inline
-                , partContent = renderHamlet id [$hamlet|
+            }
+        htmlPart = Part
+            { partType = "text/html; charset=utf-8"
+            , partEncoding = None
+            , partFilename = Nothing
+            , partContent = renderHtml [$hamlet|
 %p Please confirm your email address by clicking on the link below.
 %p
     %a!href=$verurl$ $verurl$
 %p Thank you
-|~~]
-                }
+|]
             }
+    getVerifyKey = runDB . fmap (join . fmap emailVerkey) . get
+    setVerifyKey eid key = runDB $ update eid [EmailVerkey $ Just key]
+    verifyAccount eid = runDB $ do
+        me <- get eid
+        case me of
+            Nothing -> return Nothing
+            Just e -> do
+                let email = emailEmail e
+                case emailUser e of
+                    Just uid -> return $ Just uid
+                    Nothing -> do
+                        uid <- insert $ User email Nothing
+                        update eid [EmailUser $ Just uid, EmailVerkey Nothing]
+                        return $ Just uid
+    getPassword = runDB . fmap (join . fmap userPassword) . get
+    setPassword uid pass = runDB $ update uid [UserPassword $ Just pass]
+    getEmailCreds email = runDB $ do
+        me <- getBy $ UniqueEmail email
+        case me of
+            Nothing -> return Nothing
+            Just (eid, e) -> return $ Just EmailCreds
+                { emailCredsId = eid
+                , emailCredsAuthId = emailUser e
+                , emailCredsStatus = isJust $ emailUser e
+                , emailCredsVerkey = emailVerkey e
+                }
+    getEmail = runDB . fmap (fmap emailEmail) . get
 
diff --git a/yesod.cabal b/yesod.cabal
--- a/yesod.cabal
+++ b/yesod.cabal
@@ -1,5 +1,5 @@
 name:            yesod
-version:         0.5.4.2
+version:         0.6.0
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
@@ -16,7 +16,7 @@
 homepage:        http://docs.yesodweb.com/
 extra-source-files: scaffold/*.cg
 
-flag buildtests
+flag test
   description: Build the executable to run unit tests
   default: False
 
@@ -25,11 +25,9 @@
                    , time                      >= 1.1.4    && < 1.3
                    , wai                       >= 0.2.0    && < 0.3
                    , wai-extra                 >= 0.2.4    && < 0.3
-                   , authenticate              >= 0.7      && < 0.8
                    , bytestring                >= 0.9.1.4  && < 0.10
                    , directory                 >= 1        && < 1.2
-                   , text                      >= 0.5      && < 0.10
-                   , utf8-string               >= 0.3.4    && < 0.4
+                   , text                      >= 0.5      && < 0.11
                    , template-haskell          >= 2.4      && < 2.6
                    , web-routes-quasi          >= 0.6      && < 0.7
                    , hamlet                    >= 0.5.1    && < 0.6
@@ -38,43 +36,39 @@
                    , clientsession             >= 0.4.0    && < 0.5
                    , pureMD5                   >= 1.1.0.0  && < 2.2
                    , random                    >= 1.0.0.2  && < 1.1
-                   , control-monad-attempt     >= 0.3      && < 0.4
                    , cereal                    >= 0.2      && < 0.4
-                   , dataenc                   >= 0.13.0.2 && < 0.14
+                   , base64-bytestring         >= 0.1      && < 0.2
                    , old-locale                >= 1.0.0.2  && < 1.1
-                   , persistent                >= 0.2.2    && < 0.3
-                   , neither                   >= 0.0.0    && < 0.1
-                   , MonadCatchIO-transformers >= 0.2.2.0  && < 0.3
-                   , data-object               >= 0.3.1    && < 0.4
+                   , persistent                >= 0.3.0    && < 0.4
+                   , neither                   >= 0.1.0    && < 0.2
                    , network                   >= 2.2.1.5  && < 2.3
                    , email-validate            >= 0.2.5    && < 0.3
-                   , process                   >= 1.0.1    && < 1.1
                    , web-routes                >= 0.23     && < 0.24
                    , xss-sanitize              >= 0.2      && < 0.3
+                   , data-default              >= 0.2      && < 0.3
+                   , failure                   >= 0.1      && < 0.2
+                   , containers                >= 0.2      && < 0.5
     exposed-modules: Yesod
                      Yesod.Content
                      Yesod.Dispatch
                      Yesod.Form
-                     Yesod.Form.Class
                      Yesod.Form.Core
-                     Yesod.Form.Fields
-                     Yesod.Form.Profiles
                      Yesod.Form.Jquery
                      Yesod.Form.Nic
                      Yesod.Hamlet
                      Yesod.Handler
-                     Yesod.Internal
                      Yesod.Json
-                     Yesod.Mail
                      Yesod.Request
                      Yesod.Widget
                      Yesod.Yesod
                      Yesod.Helpers.AtomFeed
-                     Yesod.Helpers.Auth
                      Yesod.Helpers.Crud
                      Yesod.Helpers.Sitemap
                      Yesod.Helpers.Static
-                     Yesod.WebRoutes
+    other-modules:   Yesod.Form.Class
+                     Yesod.Internal
+                     Yesod.Form.Fields
+                     Yesod.Form.Profiles
     ghc-options:     -Wall
 
 executable             yesod
@@ -85,7 +79,7 @@
     extensions:        TemplateHaskell
 
 executable             runtests
-    if flag(buildtests)
+    if flag(test)
         Buildable: True
         cpp-options:   -DTEST
         build-depends: test-framework,
