diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Alexander Dunlap.
+
+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 Alexander Dunlap 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/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/Yesod/Markdown.hs b/Yesod/Markdown.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Markdown.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving, CPP, QuasiQuotes, TypeFamilies, FlexibleInstances,
+              MultiParamTypeClasses #-}
+-- | This module provides functions for using Markdown with Yesod. An example pipeline could be
+--
+-- > (writePandoc defaultWriterOptions <$>) . localLinks . parseMarkdown defaultParserState
+module Yesod.Markdown
+  ( Markdown (..)
+  , parseMarkdown
+  , localLinks
+  , writePandoc
+  , writePandocTrusted
+    -- * Option sets
+  , yesodDefaultWriterOptions
+  , yesodDefaultParserState
+  , yesodDefaultParserStateTrusted
+  )
+  where
+
+import Yesod
+import Data.Monoid
+import Data.String
+import Yesod.Form.Core
+import Text.Pandoc
+import Text.Pandoc.Shared
+import Text.HTML.SanitizeXSS
+import Control.Applicative
+import Data.Map ( Map )
+import qualified Data.Map as Map
+import qualified Data.ByteString.Char8 as B
+
+newtype Markdown = Markdown String
+  deriving (Eq, Ord, Show, Read, PersistField, IsString, Monoid)
+
+instance ToFormField Markdown y where
+  toFormField = markdownField
+
+instance ToFormField (Maybe Markdown) y where
+  toFormField = maybeMarkdownField
+
+markdownField :: (IsForm f, FormType f ~ Markdown)
+              => FormFieldSettings -> Maybe Markdown -> f
+markdownField = requiredFieldHelper markdownFieldProfile
+
+maybeMarkdownField :: FormFieldSettings -> FormletField sub y (Maybe Markdown)
+maybeMarkdownField = optionalFieldHelper markdownFieldProfile
+
+markdownFieldProfile :: FieldProfile sub y Markdown
+markdownFieldProfile = FieldProfile
+    { fpParse = Right . Markdown
+    , fpRender = \(Markdown m) -> m
+    , fpWidget = \theId name val _isReq -> addHamlet
+#if GHC7
+        [hamlet|
+#else
+        [$hamlet|
+#endif
+%textarea.markdown#$theId$!name=$name$ $val$
+|]
+    }
+
+-- | Write untrusted 'Pandoc' to 'Html'. Calls 'sanitizeBalance' from xss-sanitize.
+writePandoc :: WriterOptions -> Pandoc -> Html
+writePandoc wo = preEscapedString . sanitizeBalance . writeHtmlString wo
+
+-- | Write trusted 'Pandoc' to 'Html'. Does not sanitize or balance tags.
+writePandocTrusted :: WriterOptions -> Pandoc -> Html
+writePandocTrusted wo = preEscapedString . writeHtmlString wo
+
+-- | Read in 'Markdown' to an intermediate 'Pandoc' type.
+parseMarkdown :: ParserState -> Markdown -> Pandoc
+parseMarkdown ro (Markdown m) = readMarkdown ro m
+
+-- | Convert local (in-site) links. This function works on all link URLs that start with the two-character
+-- prefix @~:@. It normalizes the links and replaces the @~:@ with the @approot@ value for the site.
+localLinks :: Yesod master => Pandoc -> GHandler sub master Pandoc
+localLinks p = (\y -> processWith (links y) p) <$> getYesod where
+  links y (Link x ('~':':':l,n)) = Link x (joinPath y (approot y) (links' y (B.pack l)) [],n)
+  links _ x = x
+  links' y l = case splitPath y l of
+    Left corrected -> links' y corrected
+    Right xs       -> xs
+
+-- | A set of default Pandoc writer options good for Yesod websites. Enables Javascript-based email obfuscation,
+-- eliminates div tags around sections, and disables text wrapping.
+yesodDefaultWriterOptions :: WriterOptions
+yesodDefaultWriterOptions = defaultWriterOptions
+  { writerEmailObfuscation = JavascriptObfuscation
+  , writerSectionDivs = False
+  , writerWrapText = False
+  }
+
+-- | A set of default Pandoc reader options good for Yesod websites /where the data input is trusted/. Enables raw
+-- HTML.
+yesodDefaultParserStateTrusted :: ParserState
+yesodDefaultParserStateTrusted = yesodDefaultParserState { stateParseRaw = True }
+
+-- | A set of default Pandoc reader options good for Yesod websites. Enables smart parsing.
+yesodDefaultParserState :: ParserState
+yesodDefaultParserState = defaultParserState { stateSmart = True }
diff --git a/Yesod/Markdown/Macros.hs b/Yesod/Markdown/Macros.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Markdown/Macros.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE ViewPatterns #-}
+-- | Macros allow users to access more advanced functionality from within Markdown syntax. There are two types
+-- of macros, block and inline, which allow substitution of 'Block' and 'Inline' data, respectively. Macros
+-- are called in a very similar fashion to shell programs: the argument string is split on whitespace. The
+-- first word is the name of the macro, and the remaining words are the arguments. Option-parsing libraries
+-- may be useful for interpreting the arguments.
+--
+-- Note: using 'blockMacros' and 'inlineMacros' at the same time with the same magic string and can lead to behavior
+-- that depends on the order in which they are called, if any of the macro names are the same for block and inline
+-- macros. However, if none of the macro names are the same, unrecognized macro names will be ignored by the pass
+-- that doesn't recognize them, leaving them available to be recognized by the other pass.
+
+module Yesod.Markdown.Macros
+  where
+
+import Text.Pandoc
+import Yesod
+import Control.Applicative
+import Data.Map ( Map )
+import qualified Data.Map as Map
+import qualified Data.ByteString.Lazy.UTF8 as U
+
+-- | Convert block-level macros. Block-level macros are signalled by a first-level header containing a piece of
+-- inline code starting with a client-specified magic string. For example, if the magic string is @??@, a macro
+-- can be called by
+--
+-- > #`??MACRO_NAME MACRO_ARGS`
+--
+-- where @MACRO_NAME@ is the identifying name of the macro and @MACRO_ARGS@ is a space-separated list of arguments.
+blockMacros
+  :: Yesod master
+  => String                                             -- ^ Magic string to introduce the macro
+  -> Map String ([String] -> GHandler sub master Block) -- ^ Lookup table from macro names to macro functions
+  -> Pandoc
+  -> GHandler sub master Pandoc
+blockMacros magic table p = processWithM blockMacros' p where
+  blockMacros' (Header 1 [Code (splitAt (length magic) -> (magic',words -> ((flip Map.lookup table -> Just f):xs)))])
+    | magic == magic' = f xs
+  blockMacros' b = return b
+
+-- | Convert block-level macros. Inline-level macros are signalled by a piece of inline code starting with a
+-- client-specified magic string. For example, if the magic string is @??@, a macro can be called by
+--
+-- > `??MACRO_NAME MACRO_ARGS`
+--
+-- where @MACRO_NAME@ is the identifying name of the macro and @MACRO_ARGS@ is a space-separated list of arguments.
+inlineMacros
+  :: Yesod master
+  => String                                              -- ^ Magic string to introduce the macro
+  -> Map String ([String] -> GHandler sub master Inline) -- ^ Lookup table from macro names to macro functions
+  -> Pandoc
+  -> GHandler sub master Pandoc
+inlineMacros magic table p = processWithM inlineMacros' p where
+  inlineMacros' (Code (splitAt (length magic) -> (magic',words -> ((flip Map.lookup table -> Just f):xs))))
+    | magic == magic' = f xs
+  inlineMacros' b = return b
+
+-- | Convert a 'Hamlet' value to a 'Block'.
+hamletToBlock :: Hamlet (Route master) -> GHandler sub master Block
+hamletToBlock x = RawHtml . U.toString . renderHtml . x <$> getUrlRenderParams
+
+-- | Convert a 'Hamlet' value to an 'Inline'.
+hamletToInline :: Hamlet (Route master) -> GHandler sub master Inline
+hamletToInline x = HtmlInline . U.toString . renderHtml . x <$> getUrlRenderParams
diff --git a/yesod-markdown.cabal b/yesod-markdown.cabal
new file mode 100644
--- /dev/null
+++ b/yesod-markdown.cabal
@@ -0,0 +1,41 @@
+-- yesod-markdown.cabal auto-generated by cabal init. For additional
+-- options, see
+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.
+Name:                 yesod-markdown
+Version:              0.0
+Synopsis:             Markdown processing for Yesod sites using Pandoc
+Description:          Process Markdown in Yesod sites.
+License:              BSD3
+License-file:         LICENSE
+Author:               Alexander Dunlap
+Maintainer:           alexander.dunlap@gmail.com
+Copyright:            (c) 2010 Alexander Dunlap
+Category:             Web, Yesod
+Build-type:           Simple
+Stability:            Alpha
+-- Extra files to be distributed with the package, such as examples or
+-- a README.
+-- Extra-source-files:  
+
+-- Constraint on the version of Cabal needed to build this package.
+Cabal-version:       >=1.6
+
+flag ghc7
+
+Library
+  if flag(ghc7)
+    build-depends:   base                      >= 4.3      && < 5
+    cpp-options:     -DGHC7
+  else
+    build-depends:   base                      >= 4        && < 4.3
+  Exposed-modules:    Yesod.Markdown, Yesod.Markdown.Macros
+  Build-depends: yesod
+               , pandoc
+               , containers
+               , xss-sanitize
+               , bytestring
+               , utf8-string
+
+source-repository head
+  type:     git
+  location: git://github.com/ajdunlap/yesod-markdown.git
