diff --git a/ReleaseNotes.md b/ReleaseNotes.md
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,3 +1,17 @@
+## 0.6.0
+
+* The Scotty transformers (`ScottyT` and `ActionT`) are now parameterized
+  over a custom exception type, allowing one to extend Scotty's `ErrorT`
+  layer with something richer than `Text` errors. See the `exceptions`
+  example for use. `ScottyM` and `ActionM` remain specialized to `Text`
+  exceptions for simplicity.
+
+* Both monads are now instances of `Functor` and `Applicative`.
+
+* There is a new `cookies` example.
+
+* Internals brought up-to-date with WAI 2.0 and related packages.
+
 ## 0.5.0
 
 * The Scotty monads (`ScottyM` and `ActionM`) are now monad transformers,
@@ -25,4 +39,4 @@
 
 * `header` split into `setHeader` and `addHeader`. The former replaces
   a response header (original behavior). The latter adds a header (useful
-  for multiple `Set-Cookie`s, for instance.
+  for multiple `Set-Cookie`s, for instance).
diff --git a/Web/Scotty.hs b/Web/Scotty.hs
--- a/Web/Scotty.hs
+++ b/Web/Scotty.hs
@@ -22,7 +22,7 @@
       -- definition, as they completely replace the current 'Response' body.
     , text, html, file, json, source, raw
       -- ** Exceptions
-    , raise, rescue, next
+    , raise, rescue, next, defaultHandler
       -- * Parsing Parameters
     , Param, Trans.Parsable(..), Trans.readEither
       -- * Types
@@ -36,15 +36,18 @@
 
 import Data.Aeson (FromJSON, ToJSON)
 import Data.ByteString.Lazy.Char8 (ByteString)
-import Data.Conduit (Flush, ResourceT, Source)
+import Data.Conduit (Flush, Source)
 import Data.Text.Lazy (Text)
 
 import Network.HTTP.Types (Status, StdMethod)
 import Network.Wai (Application, Middleware, Request)
 import Network.Wai.Handler.Warp (Port)
 
-import Web.Scotty.Types (Param, ActionM, ScottyM, RoutePattern, Options, File)
+import Web.Scotty.Types (ScottyT, ActionT, Param, RoutePattern, Options, File)
 
+type ScottyM = ScottyT Text IO
+type ActionM = ActionT Text IO 
+
 -- | Run a scotty application using the warp server.
 scotty :: Port -> ScottyM () -> IO ()
 scotty p = Trans.scottyT p id id
@@ -58,6 +61,13 @@
 scottyApp :: ScottyM () -> IO Application
 scottyApp = Trans.scottyAppT id id
 
+-- | Global handler for uncaught exceptions. 
+--
+-- Uncaught exceptions normally become 500 responses. 
+-- You can use this to selectively override that behavior.
+defaultHandler :: (Text -> ActionM ()) -> ScottyM ()
+defaultHandler = Trans.defaultHandler
+
 -- | Use given middleware. Middleware is nested such that the first declared
 -- is the outermost middleware (it has first dibs on the request and last action
 -- on the response). Every middleware is run on each request.
@@ -75,14 +85,14 @@
 -- As an example, these two routes overlap. The only way the second one will
 -- ever run is if the first one calls 'next'.
 --
--- > get "/foo/:number" $ do
--- >   n <- param "number"
--- >   unless (all isDigit n) $ next
--- >   text "a number"
--- >
 -- > get "/foo/:bar" $ do
--- >   bar <- param "bar"
--- >   text "not a number"
+-- >   w :: Text <- param "bar"
+-- >   unless (w == "special") next
+-- >   text "You made a request to /foo/special"
+-- >
+-- > get "/foo/:baz" $ do
+-- >   w <- param "baz"
+-- >   text $ "You made a request to: " <> w
 next :: ActionM a
 next = Trans.next
 
@@ -161,7 +171,7 @@
 html = Trans.html
 
 -- | Send a file as the response. Doesn't set the \"Content-Type\" header, so you probably
--- want to do that on your own with 'header'.
+-- want to do that on your own with 'setHeader'.
 file :: FilePath -> ActionM ()
 file = Trans.file
 
@@ -172,12 +182,12 @@
 
 -- | Set the body of the response to a Source. Doesn't set the
 -- \"Content-Type\" header, so you probably want to do that on your
--- own with 'header'.
-source :: Source (ResourceT IO) (Flush Builder) -> ActionM ()
+-- own with 'setHeader'.
+source :: Source IO (Flush Builder) -> ActionM ()
 source = Trans.source
 
 -- | Set the body of the response to the given 'BL.ByteString' value. Doesn't set the
--- \"Content-Type\" header, so you probably want to do that on your own with 'header'.
+-- \"Content-Type\" header, so you probably want to do that on your own with 'setHeader'.
 raw :: ByteString -> ActionM ()
 raw = Trans.raw
 
diff --git a/Web/Scotty/Action.hs b/Web/Scotty/Action.hs
--- a/Web/Scotty/Action.hs
+++ b/Web/Scotty/Action.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedStrings, RankNTypes #-}
 module Web.Scotty.Action
     ( addHeader
     , body
@@ -37,11 +37,12 @@
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy.Char8 as BL
 import qualified Data.CaseInsensitive as CI
-import Data.Conduit (Flush, ResourceT, Source)
+import Data.Conduit (Flush, Source)
 import Data.Default (def)
-import Data.Monoid (mconcat, (<>))
+import Data.Monoid (mconcat)
+import qualified Data.Text as ST
 import qualified Data.Text.Lazy as T
-import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)
+import Data.Text.Lazy.Encoding (encodeUtf8)
 
 import Network.HTTP.Types
 import Network.Wai
@@ -51,27 +52,29 @@
 
 -- Nothing indicates route failed (due to Next) and pattern matching should continue.
 -- Just indicates a successful response.
-runAction :: Monad m => ActionEnv -> ActionT m () -> m (Maybe Response)
-runAction env action = do
+runAction :: (ScottyError e, Monad m) => ErrorHandler e m -> ActionEnv -> ActionT e m () -> m (Maybe Response)
+runAction h env action = do
     (e,r) <- flip MS.runStateT def
            $ flip runReaderT env
            $ runErrorT
            $ runAM
-           $ action `catchError` defaultHandler
-    return $ either (const Nothing) (const $ Just r) e
+           $ action `catchError` (defH h)
+    return $ either (const Nothing) (const $ Just $ mkResponse r) e
 
-defaultHandler :: Monad m => ActionError -> ActionT m ()
-defaultHandler (Redirect url) = do
+-- | Default error handler for all actions.
+defH :: (ScottyError e, Monad m) => ErrorHandler e m -> ActionError e -> ActionT e m ()
+defH _          (Redirect url)    = do
     status status302
     setHeader "Location" url
-defaultHandler (ActionError msg) = do
+defH Nothing    (ActionError e)   = do
     status status500
-    html $ mconcat ["<h1>500 Internal Server Error</h1>", msg]
-defaultHandler Next = next
+    html $ mconcat ["<h1>500 Internal Server Error</h1>", showError e]
+defH h@(Just f) (ActionError e)   = f e `catchError` (defH h) -- so handlers can throw exceptions themselves
+defH _          Next              = next
 
 -- | Throw an exception, which can be caught with 'rescue'. Uncaught exceptions
 -- turn into HTTP 500 responses.
-raise :: Monad m => T.Text -> ActionT m a
+raise :: (ScottyError e, Monad m) => e -> ActionT e m a
 raise = throwError . ActionError
 
 -- | Abort execution of this action and continue pattern matching routes.
@@ -80,24 +83,24 @@
 -- As an example, these two routes overlap. The only way the second one will
 -- ever run is if the first one calls 'next'.
 --
--- > get "/foo/:number" $ do
--- >   n <- param "number"
--- >   unless (all isDigit n) $ next
--- >   text "a number"
--- >
 -- > get "/foo/:bar" $ do
--- >   bar <- param "bar"
--- >   text "not a number"
-next :: Monad m => ActionT m a
+-- >   w :: Text <- param "bar"
+-- >   unless (w == "special") next
+-- >   text "You made a request to /foo/special"
+-- >
+-- > get "/foo/:baz" $ do
+-- >   w <- param "baz"
+-- >   text $ "You made a request to: " <> w
+next :: (ScottyError e, Monad m) => ActionT e m a
 next = throwError Next
 
 -- | Catch an exception thrown by 'raise'.
 --
 -- > raise "just kidding" `rescue` (\msg -> text msg)
-rescue :: Monad m => ActionT m a -> (T.Text -> ActionT m a) -> ActionT m a
-rescue action handler = catchError action $ \e -> case e of
-    ActionError msg -> handler msg      -- handle errors
-    other           -> throwError other -- rethrow redirects and nexts
+rescue :: (ScottyError e, Monad m) => ActionT e m a -> (e -> ActionT e m a) -> ActionT e m a
+rescue action h = catchError action $ \e -> case e of
+    ActionError err -> h err            -- handle errors
+    other           -> throwError other -- rethrow internal error types
 
 -- | Redirect to given URL. Like throwing an uncatchable exception. Any code after the call to redirect
 -- will not be run.
@@ -107,32 +110,32 @@
 -- OR
 --
 -- > redirect "/foo/bar"
-redirect :: Monad m => T.Text -> ActionT m a
+redirect :: (ScottyError e, Monad m) => T.Text -> ActionT e m a
 redirect = throwError . Redirect
 
 -- | Get the 'Request' object.
-request :: Monad m => ActionT m Request
-request = liftM getReq ask
+request :: (ScottyError e, Monad m) => ActionT e m Request
+request = ActionT $ liftM getReq ask
 
 -- | Get list of uploaded files.
-files :: Monad m => ActionT m [File]
-files = liftM getFiles ask
+files :: (ScottyError e, Monad m) => ActionT e m [File]
+files = ActionT $ liftM getFiles ask
 
 -- | Get a request header. Header name is case-insensitive.
-reqHeader :: Monad m => T.Text -> ActionT m (Maybe T.Text)
+reqHeader :: (ScottyError e, Monad m) => T.Text -> ActionT e m (Maybe T.Text)
 reqHeader k = do
     hs <- liftM requestHeaders request
     return $ fmap strictByteStringToLazyText $ lookup (CI.mk (lazyTextToStrictByteString k)) hs
 
 -- | Get the request body.
-body :: Monad m => ActionT m BL.ByteString
-body = liftM getBody ask
+body :: (ScottyError e, Monad m) => ActionT e m BL.ByteString
+body = ActionT $ liftM getBody ask
 
 -- | Parse the request body as a JSON object and return it. Raises an exception if parse is unsuccessful.
-jsonData :: (A.FromJSON a, Monad m) => ActionT m a
+jsonData :: (A.FromJSON a, ScottyError e, Monad m) => ActionT e m a
 jsonData = do
     b <- body
-    maybe (raise $ "jsonData - no parse: " <> decodeUtf8 b) return $ A.decode b
+    maybe (raise $ stringError $ "jsonData - no parse: " ++ BL.unpack b) return $ A.decode b
 
 -- | Get a parameter. First looks in captures, then form data, then query parameters.
 --
@@ -141,16 +144,16 @@
 -- * If parameter is found, but 'read' fails to parse to the correct type, 'next' is called.
 --   This means captures are somewhat typed, in that a route won't match if a correctly typed
 --   capture cannot be parsed.
-param :: (Parsable a, Monad m) => T.Text -> ActionT m a
+param :: (Parsable a, ScottyError e, Monad m) => T.Text -> ActionT e m a
 param k = do
-    val <- liftM (lookup k . getParams) ask
+    val <- ActionT $ liftM (lookup k . getParams) ask
     case val of
-        Nothing -> raise $ mconcat ["Param: ", k, " not found!"]
+        Nothing -> raise $ stringError $ "Param: " ++ T.unpack k ++ " not found!"
         Just v  -> either (const next) return $ parseParam v
 
 -- | Get all parameters from capture, form and query (in that order).
-params :: Monad m => ActionT m [Param]
-params = liftM getParams ask
+params :: (ScottyError e, Monad m) => ActionT e m [Param]
+params = ActionT $ liftM getParams ask
 
 -- | Minimum implemention: 'parseParam'
 class Parsable a where
@@ -165,6 +168,7 @@
 
 -- No point using 'read' for Text, ByteString, Char, and String.
 instance Parsable T.Text where parseParam = Right
+instance Parsable ST.Text where parseParam = Right . T.toStrict
 instance Parsable B.ByteString where parseParam = Right . lazyTextToStrictByteString
 -- | Overrides default 'parseParamList' to parse String.
 instance Parsable Char where
@@ -195,52 +199,52 @@
                 _   -> Left "readEither: ambiguous parse"
 
 -- | Set the HTTP response status. Default is 200.
-status :: Monad m => Status -> ActionT m ()
-status = MS.modify . setStatus
+status :: (ScottyError e, Monad m) => Status -> ActionT e m ()
+status = ActionT . MS.modify . setStatus
 
 -- | Add to the response headers. Header names are case-insensitive.
-addHeader :: Monad m => T.Text -> T.Text -> ActionT m ()
-addHeader k v = MS.modify $ setHeaderWith $ add (CI.mk $ lazyTextToStrictByteString k) (lazyTextToStrictByteString v)
+addHeader :: (ScottyError e, Monad m) => T.Text -> T.Text -> ActionT e m ()
+addHeader k v = ActionT . MS.modify $ setHeaderWith $ add (CI.mk $ lazyTextToStrictByteString k) (lazyTextToStrictByteString v)
 
 -- | Set one of the response headers. Will override any previously set value for that header.
 -- Header names are case-insensitive.
-setHeader :: Monad m => T.Text -> T.Text -> ActionT m ()
-setHeader k v = MS.modify $ setHeaderWith $ replace (CI.mk $ lazyTextToStrictByteString k) (lazyTextToStrictByteString v)
+setHeader :: (ScottyError e, Monad m) => T.Text -> T.Text -> ActionT e m ()
+setHeader k v = ActionT . MS.modify $ setHeaderWith $ replace (CI.mk $ lazyTextToStrictByteString k) (lazyTextToStrictByteString v)
 
 -- | Set the body of the response to the given 'T.Text' value. Also sets \"Content-Type\"
 -- header to \"text/plain\".
-text :: Monad m => T.Text -> ActionT m ()
+text :: (ScottyError e, Monad m) => T.Text -> ActionT e m ()
 text t = do
     setHeader "Content-Type" "text/plain"
     raw $ encodeUtf8 t
 
 -- | Set the body of the response to the given 'T.Text' value. Also sets \"Content-Type\"
 -- header to \"text/html\".
-html :: Monad m => T.Text -> ActionT m ()
+html :: (ScottyError e, Monad m) => T.Text -> ActionT e m ()
 html t = do
     setHeader "Content-Type" "text/html"
     raw $ encodeUtf8 t
 
 -- | Send a file as the response. Doesn't set the \"Content-Type\" header, so you probably
--- want to do that on your own with 'header'.
-file :: Monad m => FilePath -> ActionT m ()
-file = MS.modify . setContent . ContentFile
+-- want to do that on your own with 'setHeader'.
+file :: (ScottyError e, Monad m) => FilePath -> ActionT e m ()
+file = ActionT . MS.modify . setContent . ContentFile
 
 -- | Set the body of the response to the JSON encoding of the given value. Also sets \"Content-Type\"
 -- header to \"application/json\".
-json :: (A.ToJSON a, Monad m) => a -> ActionT m ()
+json :: (A.ToJSON a, ScottyError e, Monad m) => a -> ActionT e m ()
 json v = do
     setHeader "Content-Type" "application/json"
     raw $ A.encode v
 
 -- | Set the body of the response to a Source. Doesn't set the
 -- \"Content-Type\" header, so you probably want to do that on your
--- own with 'header'.
-source :: Monad m => Source (ResourceT IO) (Flush Builder) -> ActionT m ()
-source = MS.modify . setContent . ContentSource
+-- own with 'setHeader'.
+source :: (ScottyError e, Monad m) => Source IO (Flush Builder) -> ActionT e m ()
+source = ActionT . MS.modify . setContent . ContentSource
 
 -- | Set the body of the response to the given 'BL.ByteString' value. Doesn't set the
 -- \"Content-Type\" header, so you probably want to do that on your
--- own with 'header'.
-raw :: Monad m => BL.ByteString -> ActionT m ()
-raw = MS.modify . setContent . ContentBuilder . fromLazyByteString
+-- own with 'setHeader'.
+raw :: (ScottyError e, Monad m) => BL.ByteString -> ActionT e m ()
+raw = ActionT . MS.modify . setContent . ContentBuilder . fromLazyByteString
diff --git a/Web/Scotty/Route.hs b/Web/Scotty/Route.hs
--- a/Web/Scotty/Route.hs
+++ b/Web/Scotty/Route.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings, FlexibleInstances, ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings, FlexibleContexts, FlexibleInstances, RankNTypes #-}
 module Web.Scotty.Route
     ( get, post, put, delete, patch, addroute, matchAny, notFound,
       capture, regex, function, literal
@@ -7,7 +7,7 @@
 import Control.Arrow ((***))
 import Control.Monad.Error
 import qualified Control.Monad.State as MS
-import Control.Monad.Trans.Resource (ResourceT, transResourceT)
+import Control.Monad.Trans.Resource (runResourceT, withInternalState, MonadBaseControl)
 
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy.Char8 as BL
@@ -32,32 +32,32 @@
 import Web.Scotty.Util
 
 -- | get = 'addroute' 'GET'
-get :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+get :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 get = addroute GET
 
 -- | post = 'addroute' 'POST'
-post :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+post :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 post = addroute POST
 
 -- | put = 'addroute' 'PUT'
-put :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+put :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 put = addroute PUT
 
 -- | delete = 'addroute' 'DELETE'
-delete :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+delete :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 delete = addroute DELETE
 
 -- | patch = 'addroute' 'PATCH'
-patch :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+patch :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 patch = addroute PATCH
 
 -- | Add a route that matches regardless of the HTTP verb.
-matchAny :: MonadIO m => RoutePattern -> ActionT m () -> ScottyT m ()
+matchAny :: (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m ()
 matchAny pattern action = mapM_ (\v -> addroute v pattern action) [minBound..maxBound]
 
 -- | Specify an action to take if nothing else is found. Note: this _always_ matches,
 -- so should generally be the last route specified.
-notFound :: MonadIO m => ActionT m () -> ScottyT m ()
+notFound :: (ScottyError e, MonadIO m) => ActionT e m () -> ScottyT e m ()
 notFound action = matchAny (Function (\req -> Just [("path", path req)])) (status status404 >> action)
 
 -- | Define a route with a 'StdMethod', 'T.Text' value representing the path spec,
@@ -74,29 +74,26 @@
 --
 -- >>> curl http://localhost:3000/foo/something
 -- something
-addroute :: MonadIO m => StdMethod -> RoutePattern -> ActionT m () -> ScottyT m ()
-addroute method pat action = MS.modify $ addRoute $ route method pat action
+addroute :: (ScottyError e, MonadIO m) => StdMethod -> RoutePattern -> ActionT e m () -> ScottyT e m ()
+addroute method pat action = ScottyT $ MS.modify $ \s -> addRoute (route (handler s) method pat action) s
 
-route :: MonadIO m => StdMethod -> RoutePattern -> ActionT m () -> Middleware m
-route method pat action app req =
+route :: (ScottyError e, MonadIO m) => ErrorHandler e m -> StdMethod -> RoutePattern -> ActionT e m () -> Middleware m
+route h method pat action app req =
     let tryNext = app req
     in if Right method == parseMethod (requestMethod req)
        then case matchRoute pat req of
             Just captures -> do
                 env <- mkEnv req captures
-                res <- lift $ runAction env action
+                res <- runAction h env action
                 maybe tryNext return res
             Nothing -> tryNext
        else tryNext
 
 matchRoute :: RoutePattern -> Request -> Maybe [Param]
-
-matchRoute (Literal pat) req | pat == path req = Just []
-                             | otherwise       = Nothing
-
+matchRoute (Literal pat)  req | pat == path req = Just []
+                              | otherwise       = Nothing
 matchRoute (Function fun) req = fun req
-
-matchRoute (Capture pat) req = go (T.split (=='/') pat) (T.split (=='/') $ path req) []
+matchRoute (Capture pat)  req = go (T.split (=='/') pat) (T.split (=='/') $ path req) []
     where go [] [] prs = Just prs -- request string and pattern match!
           go [] r  prs | T.null (mconcat r)  = Just prs -- in case request has trailing slashes
                        | otherwise           = Nothing  -- request string is longer than pattern
@@ -112,21 +109,22 @@
 path = T.fromStrict . TS.cons '/' . TS.intercalate "/" . pathInfo
 
 -- Stolen from wai-extra, modified to accept body as lazy ByteString
-parseRequestBody :: MonadIO m
+parseRequestBody :: (MonadBaseControl IO m, MonadIO m)
                  => BL.ByteString
                  -> Parse.BackEnd y
                  -> Request
-                 -> ResourceT m ([Parse.Param], [Parse.File y])
+                 -> m ([Parse.Param], [Parse.File y])
 parseRequestBody b s r =
     case Parse.getRequestBodyType r of
         Nothing -> return ([], [])
-        Just rbt -> transResourceT liftIO $ liftM partitionEithers $ sourceLbs b $$ Parse.conduitRequestBody s rbt =$ consume
+        Just rbt -> runResourceT $ withInternalState $ \ is -> 
+                        liftIO $ liftM partitionEithers $ sourceLbs b $$ Parse.conduitRequestBody is s rbt =$ consume
 
-mkEnv :: MonadIO m => Request -> [Param] -> ResourceT m ActionEnv
+mkEnv :: MonadIO m => Request -> [Param] -> m ActionEnv
 mkEnv req captures = do
-    b <- transResourceT liftIO $ liftM BL.fromChunks $ lazyConsume (requestBody req)
+    b <- liftIO $ liftM BL.fromChunks $ lazyConsume (requestBody req)
 
-    (formparams, fs) <- transResourceT liftIO $ parseRequestBody b Parse.lbsBackEnd req
+    (formparams, fs) <- liftIO $ parseRequestBody b Parse.lbsBackEnd req
 
     let convert (k, v) = (strictByteStringToLazyText k, strictByteStringToLazyText v)
         parameters = captures ++ map convert formparams ++ queryparams
diff --git a/Web/Scotty/Trans.hs b/Web/Scotty/Trans.hs
--- a/Web/Scotty/Trans.hs
+++ b/Web/Scotty/Trans.hs
@@ -26,11 +26,11 @@
       -- definition, as they completely replace the current 'Response' body.
     , text, html, file, json, source, raw
       -- ** Exceptions
-    , raise, rescue, next
+    , raise, rescue, next, defaultHandler, ScottyError(..)
       -- * Parsing Parameters
     , Param, Parsable(..), readEither
       -- * Types
-    , ScottyM, ActionM, RoutePattern, File
+    , RoutePattern, File
       -- * Monad Transformers
     , ScottyT, ActionT
     ) where
@@ -39,7 +39,6 @@
 
 import Control.Monad (when)
 import Control.Monad.State (execStateT, modify)
-import Control.Monad.Trans.Resource (transResourceT)
 import Control.Monad.IO.Class
 
 import Data.Default (def)
@@ -59,7 +58,7 @@
         => Port
         -> (forall a. m a -> n a)      -- ^ Run monad 'm' into monad 'n', called once at 'ScottyT' level.
         -> (m Response -> IO Response) -- ^ Run monad 'm' into 'IO', called at each action.
-        -> ScottyT m ()
+        -> ScottyT e m ()
         -> n ()
 scottyT p = scottyOptsT $ def { settings = (settings def) { settingsPort = p } }
 
@@ -69,7 +68,7 @@
             => Options
             -> (forall a. m a -> n a)      -- ^ Run monad 'm' into monad 'n', called once at 'ScottyT' level.
             -> (m Response -> IO Response) -- ^ Run monad 'm' into 'IO', called at each action.
-            -> ScottyT m ()
+            -> ScottyT e m ()
             -> n ()
 scottyOptsT opts runM runActionToIO s = do
     when (verbose opts > 0) $
@@ -82,19 +81,26 @@
 scottyAppT :: (Monad m, Monad n)
            => (forall a. m a -> n a)      -- ^ Run monad 'm' into monad 'n', called once at 'ScottyT' level.
            -> (m Response -> IO Response) -- ^ Run monad 'm' into 'IO', called at each action.
-           -> ScottyT m ()
+           -> ScottyT e m ()
            -> n Application
 scottyAppT runM runActionToIO defs = do
     s <- runM $ execStateT (runS defs) def
-    let rapp = transResourceT runActionToIO . foldl (flip ($)) notFoundApp (routes s)
+    let rapp = runActionToIO . foldl (flip ($)) notFoundApp (routes s)
     return $ foldl (flip ($)) rapp (middlewares s)
 
 notFoundApp :: Monad m => Scotty.Application m
-notFoundApp _ = return $ ResponseBuilder status404 [("Content-Type","text/html")]
+notFoundApp _ = return $ responseBuilder status404 [("Content-Type","text/html")]
                        $ fromByteString "<h1>404: File Not Found!</h1>"
 
+-- | Global handler for uncaught exceptions. 
+--
+-- Uncaught exceptions normally become 500 responses. 
+-- You can use this to selectively override that behavior.
+defaultHandler :: Monad m => (e -> ActionT e m ()) -> ScottyT e m ()
+defaultHandler f = ScottyT $ modify $ addHandler $ Just f
+
 -- | Use given middleware. Middleware is nested such that the first declared
 -- is the outermost middleware (it has first dibs on the request and last action
 -- on the response). Every middleware is run on each request.
-middleware :: Monad m => Middleware -> ScottyT m ()
-middleware = modify . addMiddleware
+middleware :: Monad m => Middleware -> ScottyT e m ()
+middleware = ScottyT . modify . addMiddleware
diff --git a/Web/Scotty/Types.hs b/Web/Scotty/Types.hs
--- a/Web/Scotty/Types.hs
+++ b/Web/Scotty/Types.hs
@@ -1,21 +1,28 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleInstances, MultiParamTypeClasses #-}
 module Web.Scotty.Types where
 
-import Control.Monad.Error
-import Control.Monad.Reader
-import Control.Monad.State
+import           Blaze.ByteString.Builder (Builder)
 
-import Data.ByteString.Lazy.Char8 (ByteString)
-import Data.Default (Default, def)
-import Data.String (IsString(..))
-import Data.Text.Lazy (Text, pack)
+import           Control.Applicative
+import           Control.Monad.Error
+import           Control.Monad.Reader
+import           Control.Monad.State
 
+import           Data.ByteString.Lazy.Char8 (ByteString)
 import qualified Data.Conduit as C
-import Network.Wai hiding (Middleware, Application)
+import           Data.Default (Default, def)
+import           Data.Monoid (mempty)
+import           Data.String (IsString(..))
+import           Data.Text.Lazy (Text, pack)
+
+import           Network.HTTP.Types
+
+import           Network.Wai hiding (Middleware, Application)
 import qualified Network.Wai as Wai
-import Network.Wai.Handler.Warp (Settings, defaultSettings)
-import Network.Wai.Parse (FileInfo)
+import           Network.Wai.Handler.Warp (Settings, defaultSettings)
+import           Network.Wai.Parse (FileInfo)
 
+--------------------- Options -----------------------
 data Options = Options { verbose :: Int -- ^ 0 = silent, 1(def) = startup banner
                        , settings :: Settings -- ^ Warp 'Settings'
                        }
@@ -23,55 +30,99 @@
 instance Default Options where
     def = Options 1 defaultSettings
 
-data ScottyState m = ScottyState { middlewares :: [Wai.Middleware]
-                                 , routes :: [Middleware m]
-                                 }
-
+----- Transformer Aware Applications/Middleware -----
 type Middleware m = Application m -> Application m
-type Application m = Request -> C.ResourceT m Response
+type Application m = Request -> m Response
 
-addMiddleware :: Wai.Middleware -> ScottyState m -> ScottyState m
+--------------- Scotty Applications -----------------
+data ScottyState e m = 
+    ScottyState { middlewares :: [Wai.Middleware]
+                , routes :: [Middleware m]
+                , handler :: ErrorHandler e m
+                }
+
+instance Monad m => Default (ScottyState e m) where
+    def = ScottyState [] [] Nothing
+
+addMiddleware :: Wai.Middleware -> ScottyState e m -> ScottyState e m
 addMiddleware m s@(ScottyState {middlewares = ms}) = s { middlewares = m:ms }
 
-addRoute :: Monad m => Middleware m -> ScottyState m -> ScottyState m
+addRoute :: Monad m => Middleware m -> ScottyState e m -> ScottyState e m
 addRoute r s@(ScottyState {routes = rs}) = s { routes = r:rs }
 
-instance Default (ScottyState m) where
-    def = ScottyState [] []
+addHandler :: ErrorHandler e m -> ScottyState e m -> ScottyState e m
+addHandler h s = s { handler = h }
 
-newtype ScottyT m a = ScottyT { runS :: StateT (ScottyState m) m a }
-    deriving (Monad, MonadIO, Functor, MonadState (ScottyState m))
+newtype ScottyT e m a = ScottyT { runS :: StateT (ScottyState e m) m a }
+    deriving ( Functor, Applicative, Monad, MonadIO )
 
-instance MonadTrans ScottyT where
+instance MonadTrans (ScottyT e) where
     lift = ScottyT . lift
 
-type ScottyM a = ScottyT IO a
+------------------ Scotty Errors --------------------
+data ActionError e = Redirect Text
+                   | Next
+                   | ActionError e
 
-type Param = (Text, Text)
+-- | In order to use a custom exception type (aside from 'Text'), you must
+-- define an instance of 'ScottyError' for that type. 
+class ScottyError e where
+    stringError :: String -> e
+    showError :: e -> Text
 
-data ActionError = Redirect Text
-                 | ActionError Text
-                 | Next
-    deriving (Eq,Show)
+instance ScottyError Text where
+    stringError = pack
+    showError = id
 
-instance Error ActionError where
-    strMsg = ActionError . pack
+instance ScottyError e => ScottyError (ActionError e) where
+    stringError = ActionError . stringError
+    showError (Redirect url)  = url
+    showError Next            = pack "Next"
+    showError (ActionError e) = showError e
 
+instance ScottyError e => Error (ActionError e) where 
+    strMsg = stringError
+
+type ErrorHandler e m = Maybe (e -> ActionT e m ())
+
+------------------ Scotty Actions -------------------
+type Param = (Text, Text)
+
 type File = (Text, FileInfo ByteString)
 
-data ActionEnv = Env { getReq :: Request, getParams :: [Param], getBody :: ByteString, getFiles :: [File] }
+data ActionEnv = Env { getReq    :: Request
+                     , getParams :: [Param]
+                     , getBody   :: ByteString
+                     , getFiles  :: [File] 
+                     }
 
-newtype ActionT m a = ActionT { runAM :: ErrorT ActionError (ReaderT ActionEnv (StateT Response m)) a }
-    deriving ( Monad, MonadIO, Functor
-             , MonadReader ActionEnv, MonadState Response, MonadError ActionError)
+data Content = ContentBuilder Builder
+             | ContentFile    FilePath
+             | ContentSource  (C.Source IO (C.Flush Builder))
 
-instance MonadTrans ActionT where
+data ScottyResponse = SR { srStatus  :: Status
+                         , srHeaders :: ResponseHeaders
+                         , srContent :: Content
+                         }
+
+instance Default ScottyResponse where
+    def = SR status200 [] (ContentBuilder mempty)
+
+newtype ActionT e m a = ActionT { runAM :: ErrorT (ActionError e) (ReaderT ActionEnv (StateT ScottyResponse m)) a }
+    deriving ( Functor, Applicative, Monad, MonadIO )
+
+instance ScottyError e => MonadTrans (ActionT e) where
     lift = ActionT . lift . lift . lift
 
-type ActionM a = ActionT IO a
+instance (ScottyError e, Monad m) => MonadError (ActionError e) (ActionT e m) where
+    throwError = ActionT . throwError
 
+    catchError (ActionT m) f = ActionT (catchError m (runAM . f))
+
+------------------ Scotty Routes --------------------
 data RoutePattern = Capture   Text
                   | Literal   Text
                   | Function  (Request -> Maybe [Param])
 
-instance IsString RoutePattern where fromString = Capture . pack
+instance IsString RoutePattern where 
+    fromString = Capture . pack
diff --git a/Web/Scotty/Util.hs b/Web/Scotty/Util.hs
--- a/Web/Scotty/Util.hs
+++ b/Web/Scotty/Util.hs
@@ -4,7 +4,7 @@
     , setContent
     , setHeaderWith
     , setStatus
-    , Content(..)
+    , mkResponse
     , replace
     , add
     ) where
@@ -13,17 +13,11 @@
 
 import Network.HTTP.Types
 
-import Blaze.ByteString.Builder (Builder)
-import Data.Conduit (Flush, Source, ResourceT)
-import Data.Default
-import Data.Monoid
-
 import qualified Data.ByteString as B
 import qualified Data.Text.Lazy as T
 import qualified Data.Text.Encoding as ES
 
-instance Default Response where
-    def = ResponseBuilder status200 [] mempty
+import Web.Scotty.Types
 
 lazyTextToStrictByteString :: T.Text -> B.ByteString
 lazyTextToStrictByteString = ES.encodeUtf8 . T.toStrict
@@ -31,30 +25,22 @@
 strictByteStringToLazyText :: B.ByteString -> T.Text
 strictByteStringToLazyText = T.fromStrict . ES.decodeUtf8
 
-data Content = ContentBuilder Builder
-             | ContentFile FilePath
-             | ContentSource (Source (ResourceT IO) (Flush Builder))
+setContent :: Content -> ScottyResponse -> ScottyResponse
+setContent c sr = sr { srContent = c }
 
-setContent :: Content -> Response -> Response
-setContent (ContentBuilder b)  (ResponseBuilder s h _) = ResponseBuilder s h b
-setContent (ContentBuilder b)  (ResponseFile s h _ _)  = ResponseBuilder s h b
-setContent (ContentBuilder b)  (ResponseSource s h _)  = ResponseBuilder s h b
-setContent (ContentFile f)     (ResponseBuilder s h _) = ResponseFile s h f Nothing
-setContent (ContentFile f)     (ResponseFile s h _ _)  = ResponseFile s h f Nothing
-setContent (ContentFile f)     (ResponseSource s h _)  = ResponseFile s h f Nothing
-setContent (ContentSource src) (ResponseBuilder s h _) = ResponseSource s h src
-setContent (ContentSource src) (ResponseFile s h _ _)  = ResponseSource s h src
-setContent (ContentSource src) (ResponseSource s h _)  = ResponseSource s h src
+setHeaderWith :: ([(HeaderName, B.ByteString)] -> [(HeaderName, B.ByteString)]) -> ScottyResponse -> ScottyResponse
+setHeaderWith f sr = sr { srHeaders = f (srHeaders sr) }
 
-setHeaderWith :: ([(HeaderName, B.ByteString)] -> [(HeaderName, B.ByteString)]) -> Response -> Response
-setHeaderWith g (ResponseBuilder s h b) = ResponseBuilder s (g h) b
-setHeaderWith g (ResponseFile s h f fp) = ResponseFile s (g h) f fp
-setHeaderWith g (ResponseSource s h cs) = ResponseSource s (g h) cs
+setStatus :: Status -> ScottyResponse -> ScottyResponse
+setStatus s sr = sr { srStatus = s }
 
-setStatus :: Status -> Response -> Response
-setStatus s (ResponseBuilder _ h b) = ResponseBuilder s h b
-setStatus s (ResponseFile _ h f fp) = ResponseFile s h f fp
-setStatus s (ResponseSource _ h cs) = ResponseSource s h cs
+mkResponse :: ScottyResponse -> Response
+mkResponse sr = case srContent sr of
+                    ContentBuilder b  -> responseBuilder s h b
+                    ContentFile f     -> responseFile s h f Nothing
+                    ContentSource src -> responseSource s h src
+    where s = srStatus sr
+          h = srHeaders sr
 
 -- Note: we assume headers are not sensitive to order here (RFC 2616 specifies they are not)
 replace :: (Eq a) => a -> b -> [(a,b)] -> [(a,b)]
diff --git a/examples/basic.hs b/examples/basic.hs
--- a/examples/basic.hs
+++ b/examples/basic.hs
@@ -38,7 +38,7 @@
     -- You can set status and headers directly.
     get "/redirect-custom" $ do
         status status302
-        header "Location" "http://www.google.com"
+        setHeader "Location" "http://www.google.com"
         -- note first arg to header is NOT case-sensitive
 
     -- redirects preempt execution
diff --git a/examples/cookies.hs b/examples/cookies.hs
new file mode 100644
--- /dev/null
+++ b/examples/cookies.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- This examples requires you to: cabal install cookie
+-- and: cabal install blaze-html
+import Control.Monad (forM_)
+import Data.Text.Lazy (Text)
+import qualified Data.Text.Lazy as T
+import qualified Data.Text.Lazy.Encoding as T
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BSL
+import qualified Blaze.ByteString.Builder as B
+
+import qualified Text.Blaze.Html5 as H
+import Text.Blaze.Html5.Attributes
+import Text.Blaze.Html.Renderer.Text (renderHtml)
+import Web.Scotty
+import Web.Cookie
+
+makeCookie :: BS.ByteString -> BS.ByteString -> SetCookie
+makeCookie n v = def { setCookieName = n, setCookieValue = v }
+
+renderSetCookie' :: SetCookie -> Text
+renderSetCookie' = T.decodeUtf8 . B.toLazyByteString . renderSetCookie
+
+setCookie :: BS.ByteString -> BS.ByteString -> ActionM ()
+setCookie n v = setHeader "Set-Cookie" (renderSetCookie' (makeCookie n v))
+
+getCookies :: ActionM (Maybe CookiesText)
+getCookies =
+    fmap (fmap (parseCookiesText . lazyToStrict . T.encodeUtf8)) $
+        reqHeader "Cookie"
+    where
+        lazyToStrict = BS.concat . BSL.toChunks
+
+renderCookiesTable :: CookiesText -> H.Html
+renderCookiesTable cs =
+    H.table $ do
+        H.tr $ do
+            H.th "name"
+            H.th "value"
+        forM_ cs $ \(name, val) -> do
+            H.tr $ do
+                H.td (H.toMarkup name)
+                H.td (H.toMarkup val)
+
+main :: IO ()
+main = scotty 3000 $ do
+    get "/" $ do
+        cookies <- getCookies
+        html $ renderHtml $ do
+            case cookies of
+                Just cs -> renderCookiesTable cs
+                Nothing -> return ()
+            H.form H.! method "post" H.! action "/set-a-cookie" $ do
+                H.input H.! type_ "text" H.! name "name"
+                H.input H.! type_ "text" H.! name "value"
+                H.input H.! type_ "submit" H.! value "set a cookie"
+
+    post "/set-a-cookie" $ do
+        name <- param "name"
+        value <- param "value"
+        setCookie name value
+        redirect "/"
diff --git a/examples/exceptions.hs b/examples/exceptions.hs
new file mode 100644
--- /dev/null
+++ b/examples/exceptions.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
+module Main where
+
+import Control.Applicative
+import Control.Monad.Error
+
+import Data.Monoid
+import Data.String (fromString)
+
+import Network.HTTP.Types
+import Network.Wai.Middleware.RequestLogger
+import Network.Wai
+
+import System.Random
+
+import Web.Scotty.Trans
+
+-- Define a custom exception type.
+data Except = Forbidden | NotFound Int | StringEx String
+    deriving (Show, Eq)
+
+-- The type must be an instance of 'ScottyError'.
+-- 'ScottyError' is essentially a combination of 'Error' and 'Show'.
+instance ScottyError Except where
+    stringError = StringEx
+    showError = fromString . show
+
+-- Handler for uncaught exceptions.
+handleEx :: Monad m => Except -> ActionT Except m ()
+handleEx Forbidden    = do
+    status status403
+    html "<h1>Scotty Says No</h1>"
+handleEx (NotFound i) = do
+    status status404
+    html $ fromString $ "<h1>Can't find " ++ show i ++ ".</h1>"
+
+main :: IO ()
+main = scottyT 3000 id id $ do -- note, we aren't using any additional transformer layers
+                               -- so we can just use 'id' for the runners.
+    middleware logStdoutDev
+
+    defaultHandler handleEx    -- define what to do with uncaught exceptions
+
+    get "/" $ do
+        html $ mconcat ["<a href=\"/switch/1\">Option 1 (Not Found)</a>"
+                       ,"<br/>"
+                       ,"<a href=\"/switch/2\">Option 2 (Forbidden)</a>"
+                       ,"<br/>"
+                       ,"<a href=\"/random\">Option 3 (Random)</a>"
+                       ]
+
+    get "/switch/:val" $ do
+        v <- param "val"
+        if even v then raise Forbidden else raise (NotFound v)
+        text "this will never be reached"
+
+    get "/random" $ do
+        rBool <- liftIO randomIO
+        i <- liftIO randomIO
+        let catchOne Forbidden = html "<h1>Forbidden was randomly thrown, but we caught it."
+            catchOne other     = raise other
+        raise (if rBool then Forbidden else NotFound i) `rescue` catchOne
diff --git a/examples/globalstate.hs b/examples/globalstate.hs
--- a/examples/globalstate.hs
+++ b/examples/globalstate.hs
@@ -13,7 +13,8 @@
 import Control.Monad.Reader 
 
 import Data.Default
-import Data.Text.Lazy (pack)
+import Data.String
+import Data.Text.Lazy (Text)
 
 import Network.Wai.Middleware.RequestLogger
 
@@ -58,16 +59,23 @@
         -- 'runActionToIO' is called once per action.
         runActionToIO = runM
 
-    scottyT 3000 runM runActionToIO $ do
-        middleware logStdoutDev
-        get "/" $ do
-            c <- webM $ gets tickCount
-            text $ pack $ show c
+    scottyT 3000 runM runActionToIO app
 
-        get "/plusone" $ do
-            webM $ modify $ \ st -> st { tickCount = tickCount st + 1 }
-            redirect "/"
+-- This app doesn't use raise/rescue, so the exception
+-- type is ambiguous. We can fix it by putting a type
+-- annotation just about anywhere. In this case, we'll
+-- just do it on the entire app.
+app :: ScottyT Text WebM ()
+app = do
+    middleware logStdoutDev
+    get "/" $ do
+        c <- webM $ gets tickCount
+        text $ fromString $ show c
 
-        get "/plustwo" $ do
-            webM $ modify $ \ st -> st { tickCount = tickCount st + 2 }
-            redirect "/"
+    get "/plusone" $ do
+        webM $ modify $ \ st -> st { tickCount = tickCount st + 1 }
+        redirect "/"
+
+    get "/plustwo" $ do
+        webM $ modify $ \ st -> st { tickCount = tickCount st + 2 }
+        redirect "/"
diff --git a/examples/json.hs b/examples/json.hs
deleted file mode 100644
--- a/examples/json.hs
+++ /dev/null
@@ -1,66 +0,0 @@
-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
-import Data.Aeson.TH (deriveJSON)
-
-import qualified Data.Text.Lazy as T
-
-import Network.Wai.Middleware.RequestLogger
-import Network.Wai.Middleware.Static
-
-import qualified Text.Blaze.Html5 as H
-import Text.Blaze.Html5 ((!))
-import Text.Blaze.Html5.Attributes as A
-import Text.Blaze.Html.Renderer.Text (renderHtml)
-
-import Web.Scotty
-
--- A rather contrived example to test round-tripping JSON through Scotty
-data Foo = Quux
-         | Bar Int
-         | Baz (Float, String)
-    deriving (Eq, Show)
-
-$(deriveJSON Prelude.id ''Foo)
-
-main :: IO ()
-main = scotty 3000 $ do
-    middleware logStdoutDev
-    middleware $ staticPolicy (noDots >-> addBase "static")
-
-    get "/" $ do
-        html $ wrapper $ do
-            H.form ! A.id "fooform" ! method "post" ! action "#" $ do
-                H.h5 "Select a constructor: "
-                H.input ! type_ "radio" ! A.id "fooquux" ! name "con" ! value "Quux"
-                H.label ! for "fooquux" $ "Quux"
-                H.input ! type_ "radio" ! A.id "foobar" ! name "con" ! value "Bar"
-                H.label ! for "foobar" $ "Bar"
-                H.input ! type_ "radio" ! A.id "foobaz" ! name "con" ! value "Baz"
-                H.label ! for "foobaz" $ "Baz"
-                H.br
-                H.h5 "Enter an int: "
-                H.input ! type_ "text" ! class_ "barfields" ! name "Barint"
-                H.br
-                H.h5 "Enter a float: "
-                H.input ! type_ "text" ! class_ "bazfields" ! name "Bazfloat"
-                H.h5 "Enter a string: "
-                H.input ! type_ "text" ! class_ "bazfields" ! name "Bazstring"
-                H.br
-                H.input ! type_ "submit"
-            H.div ! A.id "foolog" $ ""
-
-    post "/foo" $ do
-        v <- jsonData
-        json $ case v of
-                Quux -> Quux
-                Bar i -> Bar $ i + 1
-                Baz (f,s) -> Baz (f + 0.5, s)
-
-wrapper :: H.Html -> T.Text
-wrapper content' = renderHtml
-    $ H.html $ do
-        H.header $ do
-            -- the first two are libraries, the last is our custom code
-            H.script ! type_ "text/javascript" ! src "jquery.js" $ ""
-            H.script ! type_ "text/javascript" ! src "jquery-json.js" $ ""
-            H.script ! type_ "text/javascript" ! src "json.js" $ ""
-        H.body content'
diff --git a/examples/static/json.js b/examples/static/json.js
deleted file mode 100644
--- a/examples/static/json.js
+++ /dev/null
@@ -1,59 +0,0 @@
-$(document).ready(function() {
-    alert("here!");
-
-    $("#fooquux").click(function () {
-        $(".barfields").prop("disabled", true);
-        $(".bazfields").prop("disabled", true);
-    });
-
-    $("#foobar").click(function () {
-        $(".barfields").prop("disabled", false);
-        $(".bazfields").prop("disabled", true);
-    });
-
-    $("#foobaz").click(function () {
-        $(".bazfields").prop("disabled", false);
-        $(".barfields").prop("disabled", true);
-    });
-
-    // Some things to note:
-    // The result coming back (res) is a javascript object, so we must turn it to a string to view it.
-    // Errors will fail silently, since we haven't declared an error handler.
-    // JSON.stringify is broke-sauce... use $.toJSON from the jquery-json plugin.
-    $("#fooform").submit(function () {
-        var con = $(this).children('[name="con"]:checked').val();
-        var inputs = $(this).children('[name^="' + con + '"]');
-        var fields = $.map(inputs, function(v) { return $(v).val(); });
-        $.ajax({ url: "/foo",
-                 type: "POST",
-                 data: mkCon(con,fields),
-                 contentType: "application/json; charset=utf-8",
-                 success: function(res) {
-                              $("#foolog").append($.toJSON(res) + "</br>");
-                          },
-                 dataType: "json"}); // desired response type
-        return false; // prevent default submission action
-    });
-});
-
-function mkCon(con, fields) {
-    // All user input is a string at first,
-    // and we need to recover the numeric types
-    if (con == 'Bar') {
-        fields[0] = parseInt(fields[0]);
-    } else if (con == 'Baz') {
-        fields[0] = parseFloat(fields[0]);
-    }
-
-    // now build our object
-    // Aeson seems to be inconsistent here (maybe it's the JSON spec?)
-    // Constructors with zero or more than one fields become lists,
-    // but Constructors with exactly on field become values.
-    var o = {};
-    if (fields.length == 1) {
-        o[con] = fields[0];
-    } else {
-        o[con] = fields;
-    }
-    return $.toJSON(o);
-}
diff --git a/examples/upload.hs b/examples/upload.hs
--- a/examples/upload.hs
+++ b/examples/upload.hs
@@ -19,7 +19,7 @@
 main :: IO ()
 main = scotty 3000 $ do
     middleware logStdoutDev
-    middleware $ staticPolicy (addBase "uploads")
+    middleware $ staticPolicy (noDots >-> addBase "uploads")
 
     get "/" $ do
         html $ renderHtml
diff --git a/scotty.cabal b/scotty.cabal
--- a/scotty.cabal
+++ b/scotty.cabal
@@ -1,13 +1,13 @@
 Name:                scotty
-Version:             0.5.0
+Version:             0.6.0
 Synopsis:            Haskell web framework inspired by Ruby's Sinatra, using WAI and Warp
-Homepage:            https://github.com/ku-fpg/scotty
-Bug-reports:         https://github.com/ku-fpg/scotty/issues
+Homepage:            https://github.com/scotty-web/scotty
+Bug-reports:         https://github.com/scotty-web/scotty/issues
 License:             BSD3
 License-file:        LICENSE
 Author:              Andrew Farmer <anfarmer@ku.edu>
 Maintainer:          Andrew Farmer <anfarmer@ku.edu>
-Copyright:           (c) 2012 Andrew Farmer
+Copyright:           (c) 2012-2013 Andrew Farmer
 Category:            Web
 Stability:           experimental
 Build-type:          Simple
@@ -50,15 +50,15 @@
     ReleaseNotes.md
     examples/404.html
     examples/basic.hs
+    examples/cookies.hs
+    examples/exceptions.hs
     examples/globalstate.hs
     examples/gzip.hs
-    examples/json.hs
     examples/options.hs
     examples/upload.hs
     examples/urlshortener.hs
     examples/static/jquery.js
     examples/static/jquery-json.js
-    examples/static/json.js
 
 Library
   Exposed-modules:     Web.Scotty
@@ -68,25 +68,25 @@
                        Web.Scotty.Types
                        Web.Scotty.Util
   default-language:    Haskell2010
-  build-depends:       aeson            >= 0.6.0.2,
-                       base             >= 4.3.1 && < 5,
-                       blaze-builder    >= 0.3.1.0,
-                       bytestring       >= 0.9.1,
-                       case-insensitive >= 0.4.0.3,
-                       conduit          >= 0.5.2.7,
-                       data-default     >= 0.5.0,
-                       http-types       >= 0.8.0,
-                       mtl              >= 2.1.2,
-                       regex-compat     >= 0.95.1,
-                       resourcet        >= 0.4.0.2,
-                       text             >= 0.11.2.3,
-                       transformers     >= 0.3.0.0,
-                       wai              >= 1.3.0.1,
-                       wai-extra        >= 1.3.0.3,
-                       warp             >= 1.3.4.1
+  build-depends:       aeson            >= 0.6.2.1  && < 0.7,
+                       base             >= 4.3.1    && < 5,
+                       blaze-builder    >= 0.3.3.0  && < 0.4,
+                       bytestring       >= 0.10.0.2 && < 0.11,
+                       case-insensitive >= 1.0.0.1  && < 1.2,
+                       conduit          >= 1.0.9.3  && < 1.1,
+                       data-default     >= 0.5.3    && < 0.6,
+                       http-types       >= 0.8.2    && < 0.9,
+                       mtl              >= 2.1.2    && < 2.2,
+                       regex-compat     >= 0.95.1   && < 0.96,
+                       resourcet        >= 0.4.7.2  && < 0.5,
+                       text             >= 0.11.3.1 && < 0.12,
+                       transformers     >= 0.3.0.0  && < 0.4,
+                       wai              >= 2.0.0    && < 2.1,
+                       wai-extra        >= 2.0.0.1  && < 2.1,
+                       warp             >= 2.0.0.1  && < 2.1
 
   GHC-options: -Wall -fno-warn-orphans
 
 source-repository head
   type:     git
-  location: git://github.com/ku-fpg/scotty.git
+  location: git://github.com/scotty-web/scotty.git
