diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for elm-export-persistent
+
+## 0.1.0.0  -- 2016-12-12
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+MIT
+
+Copyright (c) 2016 William Casarin
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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/elm-export-persistent.cabal b/elm-export-persistent.cabal
new file mode 100644
--- /dev/null
+++ b/elm-export-persistent.cabal
@@ -0,0 +1,45 @@
+-- Initial elm-export-persistent.cabal generated by cabal init.  For
+-- further documentation, see http://haskell.org/cabal/users-guide/
+
+name:                elm-export-persistent
+version:             0.1.1
+synopsis:            elm-export persistent entities
+description:
+  Ent is a newtype that wraps Persistent Entity's, allowing you to export them
+  to Elm types. Specifically, it adds a To/From JSON instance which adds an id
+  field, as well as an ElmType instance that adds an id field constructor.
+homepage:            https://github.com/jb55/elm-export-persistent
+license:             MIT
+license-file:        LICENSE
+author:              William Casarin
+maintainer:          bill@casarin.me
+-- copyright:
+category:            Web, Database, Data
+build-type:          Simple
+extra-source-files:  ChangeLog.md
+cabal-version:       >=1.10
+tested-with:         GHC == 7.10.1
+                   , GHC == 7.10.2
+                   , GHC == 7.10.3
+                   , GHC == 8.0.1
+                   , GHC == 8.0.2
+
+source-repository head
+  type:     git
+  location: https://github.com/jb55/elm-export-persistent
+
+library
+  exposed-modules: Elm.Export.Persist
+                 , Elm.Export.Persist.Ent
+                 , Elm.Export.Persist.BackendKey
+  -- other-modules:
+  -- other-extensions:
+  build-depends: base >=4.8 && <5
+               , elm-export
+               , persistent
+               , aeson
+               , text
+               , unordered-containers
+               , scientific
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Elm/Export/Persist.hs b/src/Elm/Export/Persist.hs
new file mode 100644
--- /dev/null
+++ b/src/Elm/Export/Persist.hs
@@ -0,0 +1,141 @@
+-- |
+-- Module      :  Elm.Export.Persist
+-- Copyright   :  (C) 2016-17 William Casarin
+-- License     :  MIT
+-- Maintainer  :  William Casarin <bill@casarin.me>
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- == Usage
+-- #usage#
+--
+-- @
+-- newtype 'Ent' (field :: 'Symbol') a = 'Ent' ('Entity' a)
+--   deriving ('Generic')
+--
+-- type 'EntId' a = 'Ent' "id" a
+-- @
+--
+-- 'Ent' is a newtype that wraps Persistent 'Entity's, allowing you to export
+-- them to Elm types. Specifically, it adds a {To,From}JSON instance which
+-- adds an @id@ field, as well as an @'ElmType'@ instance that adds an @id@
+-- field constructor.
+--
+-- == Example
+-- #example#
+--
+-- Let\'s define a Persistent model:
+--
+-- @
+-- {-\# LANGUAGE FlexibleInstances #-}
+-- {-\# LANGUAGE GADTs #-}
+-- {-\# LANGUAGE GeneralizedNewtypeDeriving #-}
+-- {-\# LANGUAGE MultiParamTypeClasses #-}
+-- {-\# LANGUAGE OverloadedStrings #-}
+-- {-\# LANGUAGE QuasiQuotes #-}
+-- {-\# LANGUAGE TemplateHaskell #-}
+-- {-\# LANGUAGE TypeFamilies #-}
+-- {-\# LANGUAGE DeriveGeneric #-}
+-- {-\# LANGUAGE StandaloneDeriving #-}
+--
+-- module Main where
+--
+-- import Data.Aeson
+-- import Data.'Text'
+-- import Database.Persist.TH
+-- import Elm
+-- import GHC.Generics
+--
+-- import Elm.Export.Persist
+-- import Elm.Export.Persist.BackendKey ()
+--
+-- share [mkPersist sqlSettings, mkMigrate "migrateAccount"] [persistLowerCase|
+-- Account
+--   email 'Text'
+--   password 'Text'
+--   deriving Show 'Generic'
+--   UniqueEmail email
+-- |]
+--
+-- instance 'ToJSON' Account
+-- instance 'FromJSON' Account
+-- instance 'ElmType' Account
+--
+-- -- use GeneralizedNewtypeDeriving for ids
+-- -- this picks a simpler int-encoding
+-- deriving instance 'ElmType' AccountId
+-- @
+--
+-- Now let\'s export it with an id field:
+--
+-- @
+-- module Main where
+--
+-- import Db
+-- import Elm
+-- import Data.Proxy
+--
+-- mkSpecBody :: 'ElmType' a => a -> ['Text']
+-- mkSpecBody a =
+--   [ toElmTypeSource    a
+--   , toElmDecoderSource a
+--   , toElmEncoderSource a
+--   ]
+--
+-- defImports :: ['Text']
+-- defImports =
+--   [ "import Json.Decode exposing (..)"
+--   , "import Json.Decode.Pipeline exposing (..)"
+--   , "import Json.Encode"
+--   , "import Http"
+--   , "import String"
+--   ]
+--
+-- accountSpec :: Spec
+-- accountSpec =
+--   Spec [\"Generated\", \"Account\"] $
+--        defImports ++ mkSpecBody ('Proxy' :: 'Proxy' ('EntId' Account))
+--
+-- main :: IO ()
+-- main = specsToDir [accountSpec] \"some\/where\/output\"
+-- @
+--
+-- This generates:
+--
+-- @
+-- module Generated.Account exposing (..)
+--
+-- import Json.Decode exposing (..)
+-- import Json.Decode.Pipeline exposing (..)
+-- import Json.Encode
+-- import Http
+-- import String
+--
+-- type alias Account =
+--     { accountEmail : String
+--     , accountPassword : String
+--     , id : Int
+--     }
+--
+-- decodeAccount : Decoder Account
+-- decodeAccount =
+--     decode Account
+--         |> required "accountEmail" string
+--         |> required "accountPassword" string
+--         |> required "id" int
+--
+-- encodeAccount : Account -> Json.Encode.Value
+-- encodeAccount x =
+--     Json.Encode.object
+--         [ ( "accountEmail", Json.Encode.string x.accountEmail )
+--         , ( "accountPassword", Json.Encode.string x.accountPassword )
+--         , ( "id", Json.Encode.int x.id )
+--         ]
+-- @
+
+module Elm.Export.Persist
+  ( module Elm.Export.Persist.Ent
+  )
+  where
+
+import Elm.Export.Persist.Ent
diff --git a/src/Elm/Export/Persist/BackendKey.hs b/src/Elm/Export/Persist/BackendKey.hs
new file mode 100644
--- /dev/null
+++ b/src/Elm/Export/Persist/BackendKey.hs
@@ -0,0 +1,27 @@
+-- |
+-- Module      :  Elm.Export.Persist.Ent
+-- Copyright   :  (C) 2016-17 William Casarin
+-- License     :  MIT
+-- Maintainer  :  William Casarin <bill@casarin.me>
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Orphan instances needed for SQL keys
+--
+-- This is usually required, but optionally exported in case you have your own
+-- already
+
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Elm.Export.Persist.BackendKey () where
+
+import GHC.Generics
+import Elm
+import Database.Persist
+import Database.Persist.Sql
+
+deriving instance Generic (BackendKey SqlBackend)
+deriving instance ElmType (BackendKey SqlBackend)
diff --git a/src/Elm/Export/Persist/Ent.hs b/src/Elm/Export/Persist/Ent.hs
new file mode 100644
--- /dev/null
+++ b/src/Elm/Export/Persist/Ent.hs
@@ -0,0 +1,104 @@
+-- |
+-- Module      :  Elm.Export.Persist.Ent
+-- Copyright   :  (C) 2016-17 William Casarin
+-- License     :  MIT
+-- Maintainer  :  William Casarin <bill@casarin.me>
+-- Stability   :  experimental
+-- Portability :  non-portable
+
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DataKinds #-}
+
+module Elm.Export.Persist.Ent
+    ( Ent(..)
+    , EntId
+    ) where
+
+import Database.Persist
+import Database.Persist.Sql
+import Data.Aeson
+import Elm
+import Data.Proxy
+import Data.Text
+import Data.Scientific
+import GHC.TypeLits
+import GHC.Generics
+
+import qualified Data.HashMap.Strict as Map
+import qualified Data.Text as T
+
+-- | 'Entity' wrapper that adds `ToJSON`, `FromJSON`, and `ElmType` instances
+--
+-- The first type parameter 'field' is a symbol used for the key name
+--
+-- >>> toElmTypeSource (Proxy :: Proxy (Ent "userId" User))
+-- "type alias User = { userName : String, userId : Int }"
+newtype Ent (field :: Symbol) a = Ent (Entity a)
+  deriving (Generic)
+
+-- | 'Ent' alias, using "id" as the key
+--
+-- >>> toElmTypeSource (Proxy :: Proxy (EntId User))
+-- "type alias User = { userName : String, id : Int }"
+type EntId a = Ent "id" a
+
+elmIdField :: Text -> ElmValue
+elmIdField keyfield =
+  ElmField keyfield (ElmPrimitiveRef EInt)
+
+addIdToVals :: String -> ElmValue -> ElmValue
+addIdToVals keyname ev =
+  case ev of
+    ef@(ElmField{}) ->
+      Values ef (elmIdField (T.pack keyname))
+    Values v1 rest -> Values v1 (addIdToVals keyname rest)
+    _ -> ev
+
+
+instance (KnownSymbol field, ElmType a) => ElmType (Ent field a) where
+  toElmType _ =
+    case toElmType (Proxy :: Proxy a) of
+      ElmDatatype name (RecordConstructor x (Values v vals)) ->
+        ElmDatatype name (RecordConstructor x
+                            (Values v (addIdToVals keyname vals)))
+      x -> x
+    where
+      keyname :: String
+      keyname = symbolVal (Proxy :: Proxy field)
+
+instance (KnownSymbol field, ToJSON a) => ToJSON (Ent field a) where
+  toJSON (Ent (Entity k val)) =
+    case toJSON val of
+      Object hmap -> Object (Map.insert keyname (toJSON k) hmap)
+      x           -> x
+    where
+      keyname :: Text
+      keyname = T.pack $ symbolVal (Proxy :: Proxy field)
+
+valToKey :: ToBackendKey SqlBackend record => Value -> Maybe (Key record)
+valToKey (Number n) = toSqlKey <$> toBoundedInteger n
+valToKey _          = Nothing
+
+instance ( ToBackendKey SqlBackend a
+         , PersistEntity a
+         , KnownSymbol field
+         , FromJSON a) => FromJSON (Ent field a) where
+  parseJSON obj@(Object o) =
+    let
+      keyname :: String
+      keyname = symbolVal (Proxy :: Proxy field)
+      mkey = Map.lookup (T.pack keyname) o
+      keyParser = do key <- maybe (fail $ "Ent: no key found for field " ++ keyname)
+                            pure mkey
+                     maybe (fail "Ent: could not parse key as Int64")
+                           pure (valToKey key)
+    in
+      Ent <$>
+        (Entity <$> keyParser
+                <*> parseJSON obj)
+  parseJSON _ = fail "Ent: should be an Object"
+
