diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Isaac van Bakel (c) 2020
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Isaac van Bakel nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+# ytl - Yesod Transformer Library
+
+[Yesod](https://github.com/yesodweb/yesod) is a powerful, type-safe webserver library. At its core, Yesod lets you describe *foundation sites* which carry the configuration of your webserver, and *handlers* and *widgets* which use that foundation to handle requests, generate HTML and CSS, and even access a database.
+
+Haskellers who are familiar with [mtl](https://github.com/haskell/mtl) are used to transforming monad stacks, adding or computing monadic features at whim, to introduce wide-ranging effects to a program with a few small, type-safe changes. Unfortunately, Yesod does not support monad transformers - its basic `HandlerFor` and `WidgetFor` monads are woven into the server code, and won't allow for other monads to stack on top.
+
+However, a `mtl`-style approach can be done for Yesod - the secret is, rather than transforming Yesod's `HandlerFor` and `WidgetFor` monads, to *transform the foundation site itself*. The result is `ytl` - the Yesod transfomer library, which exports a similar API to `mtl`, but in terms of site transformers.
+
+A site transformer wraps a Yesod foundation site in additional information. Yesod's code is fine with custom foundation types, so the wrapped site can be used in Yesod code as normal - giving additional effects to existing Yesod code for "free".
+
+## An example
+
+The `ReaderSite` transformer adds additional data to a site, similar to how `ReaderT` adds data to a monad stack. The data accessible by `ReaderSite` can be accessed through a `MonadReader`-style API in Yesod's `HandlerFor` and `WidgetFor` monads - like this:
+
+```haskell
+useValue
+  :: (SiteReader r site)
+  => HandlerFor site a
+  -> (r -> m site b)
+  -> HandlerFor site (a, b)
+useValue mArg f = do
+  a <- mArg
+  r <- ask
+  b <- f r
+  pure (a, b)
+```
+
+## Writing your own transformers
+
+Writing your own `ytl` site transformers is relatively simple. Define your site wrapper, and some class for its API - in the above example, `ReaderSite` is the wrapper, and `SiteReader` is the API class.
+
+Implement the class for the wrapper (duh!). Then, to interact nicely with the other transformers, you'll want to define a lifting instance, which lifts the API implementation through any transformer layers. For `ReaderSite`, this looks like
+```haskell
+instance {-# OVERLAPPABLE #-}
+  (SiteTrans t, SiteReader r site) => SiteReader r (t site) where
+  ...
+```
+
+### Implement `RenderRoute`, and maybe `Yesod`
+
+You'll also need to implement `RenderRoute` for your wrapped site type - make sure to make the route type coercible to the route type of the base site, because `ytl` relies on them being representation-identical (see `SiteCompatible`)! This is normally fine, because you don't want to modify the routes anyways.
+
+If your site transformer changes some `Yesod` behaviour - like logging, middleware, etc. - you need to also implement `Yesod` for your wrapped site type. `ytl` comes with some Template Haskell helpers for writing `Yesod` implementations that pass through to the base site most of the time; so if you just want to override how logging works, you can ignore the other `Yesod` methods and they'll be implemented for you.
+
+### Why isn't my translation of this mtl type working?
+
+Remember that, in Yesod's handlers and widgets, the site is in *reader* position - it gets passed into Yesod functions. That means that the site type is basically contravariant (as evidenced by `withSiteT`) - so `HandlerFor` and `WidgetFor` are an awful lot like profunctors. That means that you can't translate `mtl`-style code directly - you have to consider the difference in data flow.
+
+For example, `ReaderSite` is not an exact copy of `ReaderT`: `ReaderT` takes the read value as an argument:
+```
+data ReaderT m a = ReaderT (r -> m a)
+```
+But for Yesod, the site is already in reader position - taking `r` as an argument wouldn't have the right semantics. Instead, `ReaderSite` includes `r` *alongside* the site:
+```
+data ReaderSite r site = ReaderSite r site
+```
+That looks very different. However, they end up being very similar in use:
+```haskell
+runReaderT :: ReaderT r m a -> r -> m a
+
+runReaderSite :: HandlerFor (ReaderSite r site) a -> r -> HandlerFor site a
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Yesod/Site/Class.hs b/src/Yesod/Site/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Site/Class.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+
+module Yesod.Site.Class
+  ( MonadSite (..)
+  ) where
+
+import Yesod.Site.Util
+
+import Control.Monad.Reader
+import Yesod.Core.Types
+
+-- | A unified class instance for Yesod's site-using monads
+--
+-- This is used for functions which work for both 'WidgetFor' and 'HandlerFor'.
+class (forall site. MonadIO (m site)) => MonadSite (m :: * -> * -> *) where
+  -- | Get the site itself in a computation
+  askSite :: m site site
+
+  -- | Run a computation under a given site transformation
+  --
+  -- This is the main entry point for site transformations - note that the
+  -- site parameter is contravariant.
+  withSiteT
+    :: SiteCompatible site site'
+    => (site -> site')
+    -> m site' a
+    -> m site a
+
+instance MonadSite HandlerFor where
+  askSite = do
+    hd <- ask
+    pure (getSite hd)
+
+  withSiteT siteT (HandlerFor innerHandler)
+    = HandlerFor (innerHandler . withSite siteT)
+
+instance MonadSite WidgetFor where
+  askSite = do
+    wd <- ask
+    pure (getWidgetSite wd)
+
+  withSiteT siteT (WidgetFor innerWidget)
+    = WidgetFor (innerWidget . withWidgetSite siteT)
+
diff --git a/src/Yesod/Site/Util.hs b/src/Yesod/Site/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Site/Util.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Yesod.Site.Util
+  ( SiteCompatible
+
+  , getSite
+  , getWidgetSite
+
+  , withSite
+  , withWidgetSite
+  ) where
+
+import Data.Coerce
+import Yesod.Core.Types
+
+getSite :: HandlerData child site -> site
+getSite = rheSite . handlerEnv
+
+getWidgetSite :: WidgetData site -> site
+getWidgetSite = getSite . wdHandler
+
+type SiteCompatible site site' = (Coercible (Route site) (Route site'), Coercible (Route site') (Route site))
+
+withWidgetSite
+  :: SiteCompatible site site'
+  => (site -> site')
+  -> WidgetData site
+  -> WidgetData site'
+withWidgetSite f WidgetData{..}
+  = WidgetData
+      { wdRef = coerce wdRef
+      , wdHandler = withSite f wdHandler
+      }
+
+withSite
+  :: SiteCompatible site site'
+  => (site -> site')
+  -> HandlerData site site
+  -> HandlerData site' site'
+withSite f = withSubSite f . withSuperSite f
+
+withSubSite
+  :: SiteCompatible site site'
+  => (site -> site')
+  -> HandlerData site parent
+  -> HandlerData site' parent
+withSubSite f HandlerData{..}
+  = let RunHandlerEnv{..} = handlerEnv
+    in
+      HandlerData
+        { handlerEnv = RunHandlerEnv
+            { rheChild = f rheChild
+            , rheRoute = coerce rheRoute
+            , rheRouteToMaster = rheRouteToMaster . coerce
+            , ..
+            }
+        , ..
+        }
+
+withSuperSite
+  :: SiteCompatible site site'
+  => (site -> site')
+  -> HandlerData child site
+  -> HandlerData child site'
+withSuperSite f HandlerData{..}
+  = let RunHandlerEnv{..} = handlerEnv
+    in
+      HandlerData
+        { handlerEnv = RunHandlerEnv
+            { rheSite = f rheSite
+            , rheRender = rheRender . coerce
+            , rheRouteToMaster = coerce . rheRouteToMaster
+            , ..
+            }
+        , ..
+        }
+
diff --git a/src/Yesod/Trans.hs b/src/Yesod/Trans.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Trans.hs
@@ -0,0 +1,26 @@
+{-# OPTIONS_GHC -Wno-orphans -Wno-overlapping-patterns #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE UndecidableInstances #-}
+module Yesod.Trans
+  ( module Yesod.Trans.Class
+  ) where
+
+import Yesod.Site.Util
+import Yesod.Trans.Class
+import Yesod.Trans.TH
+
+import Data.Copointed
+import Yesod.Core
+
+defaultYesodInstanceExcept [| copoint |] [d|
+  instance {-# OVERLAPPABLE #-}
+    ( SiteTrans t
+    , RenderRoute (t site)
+    , SiteCompatible site (t site)
+    , Copointed t
+    , Yesod site
+    ) => Yesod (t site) where
+  |]
diff --git a/src/Yesod/Trans/Class.hs b/src/Yesod/Trans/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Trans/Class.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Yesod.Trans.Class
+  ( SiteTrans (..)
+  ) where
+
+import Yesod.Site.Class
+import Yesod.Site.Util
+
+-- | The class of site transformations
+--
+-- A site transformation is a wrapper around a Yesod foundation site which
+-- augments it with additional functionality.
+class SiteTrans (t :: * -> *) where
+  -- | Lift a Yesod computation to a transformed computation, typically by
+  -- wrapping the foundation site directly
+  lift :: (MonadSite m) => m site a -> m (t site) a
+
+  -- | Transform the Yesod computation under a site transformation
+  --
+  -- Unlike transformers and mtl, ytl does not allow transformers which are
+  -- not functors in the category of (Yesod) monads. This is because all such
+  -- transformed monads must still be isomorphic to @ReaderT@ (since that is
+  -- their underlying representation in Yesod).
+  mapSiteT
+    :: (MonadSite m, MonadSite n, SiteCompatible site site')
+    => (m site a -> n site' b)
+    -> m (t site) a -> n (t site') b
+
diff --git a/src/Yesod/Trans/Class/Reader.hs b/src/Yesod/Trans/Class/Reader.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Trans/Class/Reader.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Yesod.Trans.Class.Reader
+  ( ReaderSite (..)
+  , runReaderSite
+
+  , SiteReader (..)
+  ) where
+
+import Yesod.Site.Class
+import Yesod.Trans.Class
+
+import Data.Copointed
+import Yesod.Core
+  ( RenderRoute (..)
+  )
+
+-- | The class of sites which can read some data
+class SiteReader r site | site -> r where
+  {-# MINIMAL (ask | reader), local #-}
+  -- | Get the data value
+  ask :: (MonadSite m) => m site r
+  ask = reader id
+  -- | Extract a value from the data
+  reader :: (MonadSite m) => (r -> a) -> m site a
+  reader f = f <$> ask
+
+  -- | Run a computation with a transformed version of the current data
+  -- value
+  local :: (MonadSite m) => (r -> r) -> m site a -> m site a
+
+instance {-# OVERLAPPABLE #-}
+  (SiteTrans t, SiteReader r site) => SiteReader r (t site) where
+  ask = lift ask
+  reader f = lift $ reader f
+
+  local f = mapSiteT (local f)
+
+-- | A site transformation which extends a site with some additional data
+-- which can be read
+data ReaderSite r site = ReaderSite
+  { readVal :: r
+  , unReaderSite :: site
+  }
+
+-- | Compute the effect of 'ReaderSite' by passing in the data value to be
+-- used when reading
+runReaderSite
+  :: (MonadSite m)
+  => r
+  -> m (ReaderSite r site) a
+  -> m site a
+runReaderSite r
+  = withSiteT (ReaderSite r)
+
+instance Copointed (ReaderSite r) where
+  copoint = unReaderSite
+
+instance {-# OVERLAPPING #-} SiteReader r (ReaderSite r site) where
+  ask = do
+    ReaderSite r _ <- askSite
+    pure r
+
+  local f = withSiteT (\(ReaderSite r site) -> ReaderSite (f r) site)
+
+instance RenderRoute site => RenderRoute (ReaderSite r site) where
+  newtype Route (ReaderSite r site) = ReaderRoute (Route site)
+  renderRoute (ReaderRoute route) = renderRoute route
+
+deriving instance Eq (Route site) => Eq (Route (ReaderSite r site))
+
+instance SiteTrans (ReaderSite r) where
+  lift = withSiteT unReaderSite
+
+  mapSiteT runner argM = do
+    ReaderSite r _ <- askSite
+    withSiteT unReaderSite $ runner $ withSiteT (ReaderSite r) argM
diff --git a/src/Yesod/Trans/Class/Writer.hs b/src/Yesod/Trans/Class/Writer.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Trans/Class/Writer.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Yesod.Trans.Class.Writer
+  ( WriterSite (..)
+  , runWriterSite
+
+  , SiteWriter (..)
+  ) where
+
+import Yesod.Site.Class
+import Yesod.Trans.Class
+import Yesod.Trans.Class.Reader
+
+import Data.Copointed
+import Data.IORef
+import Yesod.Core
+  ( liftIO
+  , RenderRoute (..)
+  )
+
+-- TODO: Decide if we want a lazy/strict distinction
+
+-- | The class of sites which have some writing output
+class (Monoid w) => SiteWriter w site | site -> w where
+  {-# MINIMAL (writer | tell), listen, pass #-}
+
+  -- | Write something to the output, returning a wrapped value
+  writer :: (MonadSite m) => (a, w) -> m site a
+  writer (a, w) = tell w >> pure a
+
+  -- | Write something to the output
+  tell :: (MonadSite m) => w -> m site ()
+  tell w = writer ((), w)
+
+  -- | Run a computation, returning the resulting contents of the output
+  listen :: (MonadSite m) => m site a -> m site (a, w)
+
+  -- | Run a computation which modifies the output
+  pass :: MonadSite m => m site (a, w -> w) -> m site a
+
+instance {-# OVERLAPPABLE #-}
+  (SiteTrans t, SiteWriter w site) => SiteWriter w (t site) where
+  writer = lift . writer
+  tell = lift . tell
+
+  listen = mapSiteT listen
+  pass = mapSiteT pass
+
+-- | A site transformation which extends a site with some writing output
+newtype WriterSite w site = WriterSite
+  { unWriterSite :: ReaderSite (IORef w) site
+    -- "Is an IORef safe in Yesod sites?"
+    --
+    -- Yes, because the Yesod code uses one to build its web pages.
+  }
+
+-- | Compute the effect of a 'WriterSite', getting back the output after having
+-- run the computation
+runWriterSite
+  :: (MonadSite m, Monoid w)
+  => m (WriterSite w site) a
+  -> m site (a, w)
+runWriterSite inner = do
+  wRef <- liftIO (newIORef mempty)
+  a <- runReaderSite wRef $ withSiteT WriterSite $ inner
+  w <- liftIO $ readIORef wRef
+  pure (a, w)
+
+instance Copointed (WriterSite w) where
+  copoint = copoint . unWriterSite
+
+instance {-# OVERLAPPING #-} (Monoid w) => SiteWriter w (WriterSite w site) where
+  tell v = withSiteT unWriterSite do
+    wRef <- ask
+    liftIO $ modifyIORef' wRef (<> v)
+
+  listen argM = do
+    a <- argM
+    withSiteT unWriterSite do
+      wRef <- ask
+      w <- liftIO $ readIORef wRef
+      pure (a, w)
+
+  pass modM = do
+    (a, f) <- modM
+    withSiteT unWriterSite do
+      wRef <- ask
+      liftIO $ modifyIORef' wRef f
+      pure a
+
+instance RenderRoute site => RenderRoute (WriterSite w site) where
+  newtype Route (WriterSite w site) = WriterRoute (Route (ReaderSite (IORef w) site))
+  renderRoute (WriterRoute route) = renderRoute route
+
+deriving instance Eq (Route site) => Eq (Route (WriterSite w site))
+
+instance SiteTrans (WriterSite w) where
+  lift = withSiteT unWriterSite . lift
+
+  mapSiteT runner argM = do
+    withSiteT unWriterSite $ mapSiteT runner $ withSiteT WriterSite argM
diff --git a/src/Yesod/Trans/TH.hs b/src/Yesod/Trans/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Trans/TH.hs
@@ -0,0 +1,161 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Yesod.Trans.TH
+  ( defaultYesodInstanceExcept
+  ) where
+
+import Yesod.Site.Util
+import Yesod.Trans.Class
+
+import Data.Coerce
+import Yesod.Core
+  ( Approot (..)
+  , guessApproot
+  , HtmlUrl
+  , RenderRoute (..)
+  , ScriptLoadPosition (..)
+  , Yesod (..)
+  )
+
+import Language.Haskell.TH
+
+coerceHtmlUrl
+  :: SiteCompatible site site'
+  => HtmlUrl (Route site) -> HtmlUrl (Route site')
+coerceHtmlUrl url = url . (. coerce)
+
+-- | Fills in an instance for the 'Yesod' class for a 'SiteTrans' wrapper with
+-- implementations which just invoke the base class, *except* for those implementations
+-- which are defined on the instance itself.
+--
+-- This is useful for 'SiteTrans' implementations that want to modify some of
+-- the 'Yesod' behaviour of the site, but mostly want to delegate behaviour to
+-- the base site. Instead of writing out a whole 'Yesod' instance, you can just
+-- override the class methods that you need. The rest will be filled in with
+-- working implementations that do what you expect.
+defaultYesodInstanceExcept
+  :: Q Exp -- ^ How to go from the wrapped site to the base site.
+           -- If the instance is for a type of the form 't site', then this
+           -- should be an expression of type 't site -> site'. This operation
+           -- is necessary to use some of the default implementations.
+  -> Q [Dec] -- ^ The partial 'Yesod' instance. Should just be a since @instance@
+             -- declaration, except that its body can be as empty as you like. For
+             -- example:
+             --
+             -- @
+             -- defaultYesodInstanceExcept [| myLowerer |] [d|
+             --   instance (Yesod site) => Yesod (MyWrapper site) where
+             --     yesodMiddleware = ... -- insert some middleware
+             --
+             --     -- But everything else should be defined in the default way
+             --   |]
+             -- @
+             --
+             -- This declaration will include the custom definition for
+             -- 'yesodMiddleware', as well as implementations for the other
+             -- class methods that just delegate to the base class.
+  -> Q [Dec]
+defaultYesodInstanceExcept baseSite partialInstanceQ = do
+  [InstanceD overlap ctxt instanceHead exceptions] <- partialInstanceQ
+  defaultImplementations <- defaultImplementationsQ
+
+  let fullBody = exceptions <> (filter (`undeclaredIn` exceptions) defaultImplementations)
+
+  pure [InstanceD overlap ctxt instanceHead fullBody]
+  where
+    decName :: Dec -> Maybe Name
+    decName (FunD name _) = Just name
+    decName (ValD (VarP name) _ _) = Just name
+    decName _ = Nothing
+
+    undeclaredIn :: Dec -> [Dec] -> Bool
+    undeclaredIn dec
+      | Just name <- decName dec
+      = not . any (maybe False (== name) . decName)
+    undeclaredIn _ = const True
+
+    defaultImplementationsQ = [d|
+      $(pure $ VarP 'approot) = case $([|approot|]) of
+        ApprootRelative -> ApprootRelative
+        ApprootStatic t -> ApprootStatic t
+        ApprootMaster f -> ApprootMaster (f . $(baseSite))
+        ApprootRequest f -> ApprootRequest (f. $(baseSite))
+        -- Approot is non-exhaustive, so for API compatibility, we need a
+        -- (apparently redundant) fallthrough case
+        _ -> guessApproot
+
+      $(pure $ VarP 'errorHandler) = lift . $([|errorHandler|])
+
+      $(pure $ VarP 'defaultLayout) = mapSiteT $([|defaultLayout|])
+
+      $(pure $ VarP 'urlParamRenderOverride) = \site route ->
+        $([|urlParamRenderOverride|]) ($(baseSite) site) (coerce route)
+
+      $(pure $ VarP 'isAuthorized) = \route isWrite ->
+        lift ($([|isAuthorized|]) (coerce route) isWrite)
+
+      $(pure $ VarP 'isWriteRequest)
+        = lift . $([|isWriteRequest|]) . coerce
+
+      $(pure $ VarP 'authRoute)
+        = fmap coerce . $([|authRoute|]) . $(baseSite)
+
+      $(pure $ VarP 'cleanPath) = $([|cleanPath|]) . $(baseSite)
+
+      $(pure $ VarP 'joinPath) = $([|joinPath|]) . $(baseSite)
+
+      $(pure $ VarP 'addStaticContent) = \fn mime content -> do
+        ret <- lift $ $([|addStaticContent|]) fn mime content
+        pure $ case ret of
+          Nothing -> Nothing
+          Just (Left t) -> Just (Left t)
+          Just (Right (route, params))
+            -> Just (Right (coerce route, params))
+
+      $(pure $ VarP 'maximumContentLength) = \site mRoute ->
+        $([|maximumContentLength|]) ($(baseSite) site) (coerce <$> mRoute)
+
+      $(pure $ VarP 'maximumContentLengthIO) = \site mRoute ->
+        $([|maximumContentLengthIO|]) ($(baseSite) site) (coerce <$> mRoute)
+
+      $(pure $ VarP 'makeLogger)
+         = $([|makeLogger|]) . $(baseSite)
+
+      $(pure $ VarP 'messageLoggerSource)
+        = $([|messageLoggerSource|]) . $(baseSite)
+
+      $(pure $ VarP 'jsLoader) = \site ->
+        case $([|jsLoader|]) ($(baseSite) site) of
+            BottomOfBody -> BottomOfBody
+            BottomOfHeadBlocking -> BottomOfHeadBlocking
+            BottomOfHeadAsync async
+              -> BottomOfHeadAsync
+                  (\urls mHtml ->
+                      coerceHtmlUrl $ async urls (coerceHtmlUrl <$> mHtml))
+
+      $(pure $ VarP 'jsAttributes)
+        = $([|jsAttributes|]) . $(baseSite)
+
+      $(pure $ VarP 'jsAttributesHandler)
+        = lift $([|jsAttributesHandler|])
+
+      $(pure $ VarP 'makeSessionBackend)
+        = $([|makeSessionBackend|]) . $(baseSite)
+
+      $(pure $ VarP 'fileUpload)
+        = $([|fileUpload|]) . $(baseSite)
+
+      $(pure $ VarP 'shouldLogIO)
+        = $([|shouldLogIO|]) . $(baseSite)
+
+      $(pure $ VarP 'yesodMiddleware)
+        = mapSiteT $([|yesodMiddleware|])
+
+      $(pure $ VarP 'yesodWithInternalState) = \site mRoute ->
+        $([|yesodWithInternalState|]) ($(baseSite) site) (coerce <$> mRoute)
+
+      $(pure $ VarP 'defaultMessageWidget) = \html url ->
+        lift $ $([|defaultMessageWidget|]) html (url . coerce)
+      |]
diff --git a/ytl.cabal b/ytl.cabal
new file mode 100644
--- /dev/null
+++ b/ytl.cabal
@@ -0,0 +1,48 @@
+name:                ytl
+version:             0.1.0.0
+synopsis:            mtl-style transformations for Yesod sites
+description:         A library of transformers for extending the behaviour of
+                     Yesod sites in an mtl-style API, through transformations
+                     of the foundation site.
+                     .
+                     This package contains:
+                     .
+                      * An API for declaring and using site transformers,
+                        which modify the behaviour of a Yesod site while still
+                        allowing it to be used with existing Yesod code, until
+                        monad transformers
+                     .
+                     * Some useful site transformers and their corresponding
+                       APIs
+homepage:            https://github.com/ivanbakel/ytl#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Isaac van Bakel
+maintainer:          ivb@vanbakel.io
+copyright:           2020 Isaac van Bakel
+category:            Web
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Yesod.Site.Class
+                      ,Yesod.Site.Util
+
+                      ,Yesod.Trans
+                      ,Yesod.Trans.Class
+                      ,Yesod.Trans.Class.Reader
+                      ,Yesod.Trans.Class.Writer
+                      ,Yesod.Trans.TH
+
+  build-depends:       base >= 4.7 && < 5
+                      ,mtl
+                      ,pointed
+                      ,template-haskell
+                      ,yesod-core
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/ivanbakel/ytl
