diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog for postgresql-simple-ltree 
+
+## 0.0.0.0
+
+* First release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright SimSpace (c) 2022
+
+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 SimSpace 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,16 @@
+# postgresql-ltree
+
+Haskell support for PostgreSQL's [ltree](https://www.postgresql.org/docs/current/ltree.html).
+
+## postgresql-ltree [![postgresql-ltree](https://img.shields.io/hackage/v/postgresql-ltree.svg?logo=haskell&color=blueviolet)](https://hackage.haskell.org/package/postgresql-ltree)
+
+Core types and functions for use in Haskell code.
+Does not provide support for any database library; use one of the
+following libraries instead.
+
+
+## postgresql-simple-ltree [![postgresql-simple-ltree](https://img.shields.io/hackage/v/postgresql-simple-ltree.svg?logo=haskell&color=blueviolet)](https://hackage.haskell.org/package/postgresql-simple-ltree)
+
+Contains type class instances for `FromField` and `ToField` for use with [postgresql-simple](https://hackage.haskell.org/package/postgresql-simple).
+Re-exports types and functions from `postgresql-ltree` so there should be no
+need to depend on both.
diff --git a/postgresql-simple-ltree.cabal b/postgresql-simple-ltree.cabal
new file mode 100644
--- /dev/null
+++ b/postgresql-simple-ltree.cabal
@@ -0,0 +1,62 @@
+cabal-version: 1.12
+
+name:           postgresql-simple-ltree
+version:        0.0.0.0
+description:    Please see the README on GitHub at <https://github.com/simspace/postgresql-ltree#readme>
+synopsis:       Instances for using ltree with postgresql-simple
+category:       Database
+homepage:       https://github.com/simspace/postgresql-ltree#readme
+bug-reports:    https://github.com/simspace/postgresql-ltree/issues
+author:         Cary Robbins
+maintainer:     carymrobbins@gmail.com
+copyright:      2022 SimSpace
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/simspace/postgresql-ltree
+
+library
+  exposed-modules:
+      Database.PostgreSQL.Simple.LQuery
+      Database.PostgreSQL.Simple.LTree
+  other-modules:
+      Paths_postgresql_simple_ltree
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , aeson >=1.5.6.0 && <2
+    , postgresql-ltree ==0.0.0.0
+    , postgresql-simple >=0.6.4 && <1
+    , text >=1.2.4.1 && <2
+  default-language: Haskell2010
+  ghc-options:
+    -Wall -fwarn-tabs -Wincomplete-uni-patterns
+    -Werror=missing-home-modules -eventlog +RTS -A32M -RTS
+
+test-suite postgresql-simple-ltree-test
+  type: exitcode-stdio-1.0
+  main-is: postgresql-simple-ltree-test.hs
+  hs-source-dirs: test
+  ghc-options:
+    -Wall -fwarn-tabs -Wincomplete-uni-patterns
+    -Werror=missing-home-modules -eventlog +RTS -A32M -RTS
+    -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , hspec
+    , monad-logger
+    , postgresql-ltree
+    , postgresql-simple
+    , postgresql-simple-ltree
+    , QuickCheck
+    , text
+    , tmp-postgres
+  default-language: Haskell2010
diff --git a/src/Database/PostgreSQL/Simple/LQuery.hs b/src/Database/PostgreSQL/Simple/LQuery.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Simple/LQuery.hs
@@ -0,0 +1,34 @@
+-- | This module is a wrapper for PostgreSQL's @lquery@ https://www.postgresql.org/docs/current/ltree.html
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Database.PostgreSQL.Simple.LQuery
+  ( module Database.PostgreSQL.LQuery
+  ) where
+
+import Prelude
+
+import Database.PostgreSQL.LQuery
+
+import Control.Monad (when)
+import Database.PostgreSQL.Simple.FromField
+  ( FromField(fromField), ResultError(Incompatible, UnexpectedNull), returnError, typename
+  )
+import Database.PostgreSQL.Simple.ToField (ToField(toField))
+
+import qualified Data.Text.Encoding as Text
+
+instance ToField LQuery where
+  toField = toField . unLQuery
+
+instance FromField LQuery where
+  fromField fld mbs = do
+    -- There might be a more efficient way to check this, need to see
+    -- if the @lquery@ type has a stable typoid or not.
+    typ <- typename fld
+    -- Ensure we don't accidentally deserialize a @text@ field which
+    -- would produce corrupted @lquery@s.
+    when (typ /= "lquery") $
+      returnError Incompatible fld $ "Expected type lquery, got: " <> show typ
+    case mbs of
+      Nothing -> returnError UnexpectedNull fld ""
+      Just bs -> pure $ LQuery $ Text.decodeUtf8 bs
diff --git a/src/Database/PostgreSQL/Simple/LTree.hs b/src/Database/PostgreSQL/Simple/LTree.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Simple/LTree.hs
@@ -0,0 +1,36 @@
+-- | This module is a wrapper for PostgreSQL's @ltree@ https://www.postgresql.org/docs/current/ltree.html
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Database.PostgreSQL.Simple.LTree
+  ( module Database.PostgreSQL.LTree
+  ) where
+
+import Prelude hiding (map, null)
+
+import Database.PostgreSQL.LTree
+
+import Control.Monad (when)
+import Database.PostgreSQL.Simple.FromField
+  ( FromField(fromField), ResultError(Incompatible, UnexpectedNull), returnError, typename
+  )
+import Database.PostgreSQL.Simple.ToField (ToField(toField))
+
+import qualified Data.Text.Encoding as Text
+
+instance ToField LTree where
+  toField = toField . render
+
+instance FromField LTree where
+  fromField fld mbs = do
+    -- There might be a more efficient way to check this, need to see
+    -- if the @ltree@ type has a stable typoid or not.
+    typ <- typename fld
+    -- Ensure we don't accidentally deserialize a @text@ field which
+    -- would produce corrupted @label@s.
+    when (typ /= "ltree") $
+      returnError Incompatible fld $ "Expected type ltree, got: " <> show typ
+    case mbs of
+      Nothing -> returnError UnexpectedNull fld ""
+      -- Since this is coming from postgres and we've confirmed the type matches
+      -- @ltree@, it is safe to use 'unsafeUncheckedParse' here.
+      Just bs -> pure $ unsafeUncheckedParse $ Text.decodeUtf8 bs
diff --git a/test/postgresql-simple-ltree-test.hs b/test/postgresql-simple-ltree-test.hs
new file mode 100644
--- /dev/null
+++ b/test/postgresql-simple-ltree-test.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+module Main where
+
+import Control.Exception (bracket, throw, throwIO, try)
+import Control.Monad.Logger.CallStack (logWarn, runStderrLoggingT)
+import Data.Function ((&))
+import Data.Functor ((<&>), void)
+import Data.Text (Text)
+import Database.PostgreSQL.Simple
+  ( Only(..), SqlError(sqlErrorMsg), Connection, close, connectPostgreSQL, execute_, query
+  )
+import Database.PostgreSQL.Simple.LTree (mkLabel)
+import System.Environment (lookupEnv)
+import Test.Hspec (describe, expectationFailure, hspec, it)
+import Test.QuickCheck (Gen, arbitrary, property, suchThat)
+import Test.QuickCheck.Monadic (forAllM, monadicIO, run)
+
+import qualified Data.Bifunctor as Bifunctor
+import qualified Data.ByteString.Char8 as C8
+import qualified Data.Text as Text
+import qualified Database.Postgres.Temp as TmpPostgres
+
+main :: IO ()
+main = withTmpPostgres $ \conn -> hspec $ do
+  describe "mkLabel" $ do
+    it "should only produce valid labels" $
+      property $ monadicIO $ forAllM genPGText $ \t ->
+        run $ mkLabel t & either
+          (const $ verifyTextCannotBeALabel conn t)
+          (const $ verifyTextCanBeALabel conn t)
+  where
+  withTmpPostgres f = do
+    let go conn = do
+          _ <- execute_ conn "create extension if not exists ltree"
+          f conn
+    -- Checks for the PGDATABASE env var. If it's not set, use
+    -- tmp-postgres. Otherwise, connect to the database
+    -- specified via env vars.
+    lookupEnv "PGDATABASE" >>= \case
+      Nothing ->
+        either throwIO pure =<<
+          TmpPostgres.with
+            (\db ->
+                bracket
+                (connectPostgreSQL $ TmpPostgres.toConnectionString db)
+                close
+                go
+            )
+      Just _ ->
+        -- Uses the env vars as specified here -
+        -- https://www.postgresql.org/docs/13/libpq-envars.html
+        bracket
+          (connectPostgreSQL "")
+          close
+          go
+
+  verifyTextCanBeALabel :: Connection -> Text -> IO ()
+  verifyTextCanBeALabel conn t = tryConvertTextToLabelInPG conn t >>= \case
+    Left msg -> expectationFailure msg
+    Right () -> pure ()
+
+  -- Making this throw an expectationFailure is a bit too strong of a test.
+  -- It's probably ok if 'mkLabel' is too strict; it's mostly important that
+  -- it's not too lenient. We'll just log a warning for cases found in which
+  -- 'mkLabel' is too strict.
+  verifyTextCannotBeALabel :: Connection -> Text -> IO ()
+  verifyTextCannotBeALabel conn t = tryConvertTextToLabelInPG conn t >>= \case
+    Left _ -> pure ()
+    Right () -> runStderrLoggingT $
+      logWarn $ "PostgreSQL unexpectedly parsed label: " <> Text.pack (show t)
+
+  tryConvertTextToLabelInPG :: Connection -> Text -> IO (Either String ())
+  tryConvertTextToLabelInPG conn t =
+    try (void $ query @_ @[Int] conn "select ?::ltree where false" $ Only ltree)
+      <&>
+      Bifunctor.first
+        (\(e :: SqlError) ->
+          if "syntax error at position " `C8.isPrefixOf` sqlErrorMsg e then
+            "Failed to parse label via PostgreSQL: " <> show t
+          else
+            throw e)
+    where
+    -- Produce an ltree by joining a label with itself
+    ltree = t <> "." <> t
+
+  genAnyText :: Gen Text
+  genAnyText = Text.pack <$> arbitrary
+
+  genPGText :: Gen Text
+  genPGText = genAnyText `suchThat` (not . Text.isInfixOf "\0")
