diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for esqueleto-pgcrypto
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2021
+
+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 Author name here 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,48 @@
+# esqueleto-pgcrypto
+
+Esqueleto support for the pgcrypto PostgreSQL module        
+
+````haskell
+share
+    [mkPersist sqlSettings]
+    [persistLowerCase|
+    UserAccount json
+        name T.Text
+        UniqueName name
+        passwordHash T.Text
+        deriving Show Read Eq
+
+-- insert a `PersistField` using pgcrypto's `crypt` function for password hashing
+insertUserAccount = insertSelect $ 
+    pure $
+        UserAccount
+            <# val "username"
+            <&> toCrypt (BF Nothing) "1234password"
+
+login name pwd = select $ do
+    user <- from $ Table UserAccount
+    where_ $ user ^. UserAccountName ==. val name
+        &&. fromCrypt (user ^. UserAccountPasswordHash) pwd
+    pure user
+
+````
+
+## Tests
+
+To run tests you will need a `pgctest` named postgresql database listening on port 5432.
+
+The connection details are
+
+````haskell
+"host=localhost port=5432 user=pgctest password=pgctest dbname=pgctest"
+````
+
+You can crete this instace as follows:
+
+````bash
+$ sudo -u postgres createuser --superuser pgctest 
+$ sudo -u postgres createdb pgctest
+$ sudo -u postgres psql
+# set the password of pgctest to pgctest
+postgres=# \password pgctest 
+````
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/esqueleto-pgcrypto.cabal b/esqueleto-pgcrypto.cabal
new file mode 100644
--- /dev/null
+++ b/esqueleto-pgcrypto.cabal
@@ -0,0 +1,57 @@
+cabal-version:      1.12
+name:               esqueleto-pgcrypto
+version:            0.1.0.0
+license:            BSD3
+license-file:       LICENSE
+copyright:          2021 Kyriakos Papachrysanthou
+maintainer:         papachrysanthou.k@gmail.com
+author:             Kyriakos Papachrysanthou
+homepage:           https://github.com/3kyro/esqueleto-pgcrypto#readme
+bug-reports:        https://github.com/3kyro/esqueleto-pgcrypto/issues
+synopsis:           Esqueleto support for the pgcrypto PostgreSQL module
+description:
+    Please see the README on GitHub at <https://github.com/3kyro/esqueleto-pgcrypto#readme>
+
+category:           Database
+build-type:         Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+    type:     git
+    location: https://github.com/3kyro/esqueleto-pgcrypto
+
+library
+    exposed-modules:  Database.Esqueleto.PostgreSQL.Pgcrypto
+    hs-source-dirs:   src
+    other-modules:    Paths_esqueleto_pgcrypto
+    default-language: Haskell2010
+    build-depends:
+        base >=4.13 && <5,
+        esqueleto >=3.5.2.2 && <3.6,
+        text >=1.2.4.1 && <1.3
+
+test-suite esqueleto-pgcrypto-test
+    type:             exitcode-stdio-1.0
+    main-is:          Spec.hs
+    hs-source-dirs:   test
+    other-modules:
+        Model
+        Utils
+        Paths_esqueleto_pgcrypto
+
+    default-language: Haskell2010
+    ghc-options:      -threaded -rtsopts -with-rtsopts=-N
+    build-depends:
+        QuickCheck >=2.14.2 && <2.15,
+        base >=4.13 && <5,
+        esqueleto >=3.5.2.2 && <3.6,
+        esqueleto-pgcrypto -any,
+        hspec >=2.7.10 && <2.8,
+        monad-logger >=0.3.36 && <0.4,
+        persistent >=2.13.1.2 && <2.14,
+        persistent-postgresql >=2.13.1.0 && <2.14,
+        text >=1.2.4.1 && <1.3,
+        transformers >=0.5.6.2 && <0.6,
+        unliftio >=0.2.20 && <0.3
diff --git a/src/Database/Esqueleto/PostgreSQL/Pgcrypto.hs b/src/Database/Esqueleto/PostgreSQL/Pgcrypto.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Esqueleto/PostgreSQL/Pgcrypto.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | This module contains functions specific to the [pgcrypto](https://www.postgresql.org/docs/current/pgcrypto.html) module
+
+module Database.Esqueleto.PostgreSQL.Pgcrypto
+    (HashAlgorithm(..),
+     toCrypt,
+     fromCrypt,
+    ) where
+
+import qualified Data.Text.Internal.Builder as TLB
+import Database.Esqueleto.Experimental (toPersistValue)
+import Database.Esqueleto.Internal.Internal
+
+{- | pgcrypto hashing algorithms
+see: https://www.postgresql.org/docs/current/pgcrypto.html
+
+`bf` and `xdes` algorithms have an optional iterations count parameter. All limitations and considerations
+mentioned in the `pgcrypto` module documentation regarding iteration count apply. It is possible to supply
+an invalid iteration count, which will lead to an sql error.
+
+/Requires/ the pgcrypto module.
+
+-}
+data HashAlgorithm
+    = BF (Maybe Word)
+    | MD5
+    | XDES (Maybe Word)
+    | DES
+  deriving (Eq, Show)
+
+{- | (@crypt()@) Calculate a crypt-like hash from the provided password
+
+/Requires/ the pgcrypto module.
+
+/WARNING/: Using `toCrypt` may leak sensitive data via logging. Filtering logs in production environments
+when using `toCrypt`, such as using `filterLogger` on `monad-logger` based stacks is highly advised.
+
+example:
+
+@
+share
+    [mkPersist sqlSettings]
+    [persistLowerCase|
+    UserAccount json
+        name T.Text
+        UniqueName name
+        passwordHash T.Text
+        deriving Show Read Eq
+
+insertSelect $ do
+    pure $
+        UserAccount
+            <# val "username"
+            <&> toCrypt (BF Nothing) "1234password"
+@
+
+-}
+toCrypt :: SqlString s => HashAlgorithm -> s -> SqlExpr (Value s)
+toCrypt algorithm pass =
+    let alg = case algorithm of
+            BF mIterCount ->
+                "'bf'" <> case mIterCount of
+                             Nothing -> mempty
+                             Just iterCount ->
+                                "," <> TLB.fromString (show iterCount)
+            MD5 -> "'md5'"
+            XDES mIterCount ->
+                "'xdes'" <> case mIterCount of
+                             Nothing -> mempty
+                             Just iterCount ->
+                                "," <> TLB.fromString (show iterCount)
+            DES -> "'des'"
+    in ERaw noMeta $ \_ _ -> ("crypt (?, gen_salt(" <> alg <> "))", [toPersistValue pass])
+
+{- | (@crypt()@) Retrieve a hashed password
+
+/Requires/ the pgcrypto module.
+
+example:
+
+@
+share
+    [mkPersist sqlSettings]
+    [persistLowerCase|
+    UserAccount json
+        name T.Text
+        UniqueName name
+        passwordHash T.Text
+        deriving Show Read Eq
+
+
+login name pwd = select $ do
+    user <- from $ Table UserAccount
+    where_ $ user ^. UserAccountName ==. val name
+        &&. fromCrypt (user ^. UserAccountPasswordHash) pwd
+    pure user
+@
+
+-}
+fromCrypt :: SqlString s => SqlExpr (Value s) -> s -> SqlExpr (Value Bool)
+fromCrypt expr pass =
+    expr
+        ==. ERaw
+            noMeta
+            ( \_ info ->
+                let name = columnName expr info
+                in ("crypt (?, " <> name <> ")", [toPersistValue pass])
+            )
+  where
+    columnName (ERaw _ f) info =
+            fst $ f Never info
diff --git a/test/Model.hs b/test/Model.hs
new file mode 100644
--- /dev/null
+++ b/test/Model.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE EmptyDataDecls #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Model where
+
+import Database.Esqueleto.Experimental
+import Database.Persist.Sql
+import Database.Persist.TH
+
+share
+    [mkPersist sqlSettings, mkMigrate "migrateAll"]
+    [persistUpperCase|
+  User
+    username String
+    passwordHash String
+    deriving Eq Show Ord
+|]
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+import Database.Esqueleto.Experimental
+import qualified Database.Esqueleto.PostgreSQL.Pgcrypto as PGC
+import Model
+import Test.Hspec
+import Utils
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = beforeAll mkConnectionPool $ do
+    testCrypt
+
+testCrypt :: SpecDb
+testCrypt =
+    describe "Crypt password hashing functions" $ do
+        itDb "authenticates a user" $ do
+            rawExecute "CREATE EXTENSION IF NOT EXISTS pgcrypto" []
+            _ <- insertSelect $ do
+                pure $
+                    User
+                        <# val "name"
+                        <&> PGC.toCrypt (PGC.BF $ Just 6) "1234password"
+            authenticated <-
+                select $ do
+                    user' <- from $ table @User
+                    where_ $
+                        user' ^. UserUsername ==. val "name"
+                            &&. PGC.fromCrypt (user' ^. UserPasswordHash) "1234password"
+                    pure user'
+            let authUser = entityVal $ head authenticated
+            asserting $ do
+                userUsername authUser `shouldBe` "name"
+                userPasswordHash authUser `shouldNotBe` "1234password"
diff --git a/test/Utils.hs b/test/Utils.hs
new file mode 100644
--- /dev/null
+++ b/test/Utils.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Utils where
+
+import Control.Monad
+import Control.Monad.IO.Class
+import Control.Monad.Logger
+import Control.Monad.Trans.Reader
+import Database.Esqueleto.Experimental
+import Database.Persist.Postgresql (createPostgresqlPool)
+import Model
+import Test.Hspec
+import UnliftIO
+
+mkConnectionPool :: IO ConnectionPool
+mkConnectionPool = do
+    pool <-
+        runStderrLoggingT $
+            createPostgresqlPool
+                "host=localhost port=5432 user=pgctest password=pgctest dbname=pgctest"
+                4
+    flip runSqlPool pool $ do
+        migrateIt
+    pure pool
+
+migrateIt :: MonadUnliftIO m => SqlPersistT m ()
+migrateIt = mapReaderT runNoLoggingT $ do
+    void $
+        runMigrationSilent $ do
+            migrateAll
+    cleanDB
+
+cleanDB ::
+    forall m.
+    (MonadIO m) =>
+    SqlPersistT m ()
+cleanDB =
+    delete $ from (table @User) >> pure ()
+
+type SpecDb = SpecWith ConnectionPool
+
+asserting :: MonadIO f => IO () -> SqlPersistT f ()
+asserting = liftIO
+
+itDb ::
+    HasCallStack =>
+    String ->
+    SqlPersistT IO x ->
+    SpecDb
+itDb message action =
+    it message $ \connection -> do
+        void $ testDb connection action
+
+testDb :: ConnectionPool -> SqlPersistT IO a -> IO a
+testDb conn action =
+    liftIO $
+        flip runSqlPool conn $ do
+            a <- action
+            transactionUndo
+            pure a
