diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2026, Nikita Volkov
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# hasql-mapping
+
+[![Hackage](https://img.shields.io/hackage/v/hasql-mapping.svg)](https://hackage.haskell.org/package/hasql-mapping)
+[![Continuous Haddock](https://img.shields.io/badge/haddock-master-blue)](https://nikita-volkov.github.io/hasql-mapping/)
+
+SDK for defining mappings between Haskell and PostgreSQL.
+
+The central piece of this library is the `IsStatement` typeclass, which is used for declaring type-safe mappings to particular SQL statements.
diff --git a/hasql-mapping.cabal b/hasql-mapping.cabal
new file mode 100644
--- /dev/null
+++ b/hasql-mapping.cabal
@@ -0,0 +1,64 @@
+cabal-version: 3.0
+name: hasql-mapping
+version: 0.1
+synopsis: SDK for defining modular mappings to databases on top of Hasql
+description:
+  SDK for defining mappings to databases using Hasql that promotes modular design.
+
+  It avoids exporting abstractions unrelated to mapping, ensuring more stable versioning.
+
+homepage: https://github.com/nikita-volkov/hasql-mapping
+bug-reports: https://github.com/nikita-volkov/hasql-mapping/issues
+author: Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>
+copyright: (c) 2026, Nikita Volkov
+license: MIT
+license-file: LICENSE
+extra-doc-files:
+  LICENSE
+  README.md
+
+source-repository head
+  type: git
+  location: https://github.com/nikita-volkov/hasql-mapping
+
+common base
+  default-language: Haskell2010
+  default-extensions:
+    ApplicativeDo
+    BlockArguments
+    DefaultSignatures
+    DeriveFunctor
+    DerivingStrategies
+    DerivingVia
+    DuplicateRecordFields
+    FlexibleContexts
+    FlexibleInstances
+    FunctionalDependencies
+    GeneralizedNewtypeDeriving
+    LambdaCase
+    MultiParamTypeClasses
+    NoImplicitPrelude
+    OverloadedStrings
+    ScopedTypeVariables
+    TypeApplications
+    TypeFamilies
+
+library
+  import: base
+  hs-source-dirs: src/library
+  exposed-modules:
+    Hasql.Mapping
+    Hasql.Mapping.IsScalar
+    Hasql.Mapping.IsStatement
+
+  build-depends:
+    aeson >=2.0 && <3,
+    base >=4.12 && <5,
+    bytestring >=0.10 && <0.13,
+    hasql ^>=1.10.1,
+    iproute >=1.7 && <2,
+    scientific >=0.3 && <0.4,
+    text >=1.2 && <3,
+    time >=1.9 && <2,
+    uuid >=1.3 && <2,
diff --git a/src/library/Hasql/Mapping.hs b/src/library/Hasql/Mapping.hs
new file mode 100644
--- /dev/null
+++ b/src/library/Hasql/Mapping.hs
@@ -0,0 +1,11 @@
+-- | Reexports of classes without methods.
+--
+-- To access the methods import the class-specific modules preferably qualified.
+module Hasql.Mapping
+  ( IsScalar,
+    IsStatement,
+  )
+where
+
+import Hasql.Mapping.IsScalar (IsScalar)
+import Hasql.Mapping.IsStatement (IsStatement)
diff --git a/src/library/Hasql/Mapping/IsScalar.hs b/src/library/Hasql/Mapping/IsScalar.hs
new file mode 100644
--- /dev/null
+++ b/src/library/Hasql/Mapping/IsScalar.hs
@@ -0,0 +1,110 @@
+module Hasql.Mapping.IsScalar where
+
+import qualified Data.Aeson as Aeson
+import Data.ByteString (ByteString)
+import Data.Functor.Contravariant ((>$<))
+import Data.IP (IPRange)
+import Data.Int (Int16, Int32, Int64)
+import Data.Scientific (Scientific)
+import Data.Text (Text)
+import Data.Time (Day, DiffTime, LocalTime, TimeOfDay, TimeZone, UTCTime)
+import Data.UUID (UUID)
+import Data.Word (Word8)
+import qualified Hasql.Decoders as Decoders
+import qualified Hasql.Encoders as Encoders
+import Prelude
+
+-- | Mapping to a scalar value. Anything but array.
+--
+-- The current Hasql API doesn't provide a typesafe boundary to enforce the value being scalar,
+-- so consider this to be a part of the contract to not define instances for array mappings using this class.
+class IsScalar a where
+  encoder :: Encoders.Value a
+  decoder :: Decoders.Value a
+
+-- Numeric types
+instance IsScalar Bool where
+  encoder = Encoders.bool
+  decoder = Decoders.bool
+
+instance IsScalar Int16 where
+  encoder = Encoders.int2
+  decoder = Decoders.int2
+
+instance IsScalar Int32 where
+  encoder = Encoders.int4
+  decoder = Decoders.int4
+
+instance IsScalar Int64 where
+  encoder = Encoders.int8
+  decoder = Decoders.int8
+
+instance IsScalar Int where
+  encoder = fromIntegral >$< Encoders.int8
+  decoder = fromIntegral <$> Decoders.int8
+
+instance IsScalar Float where
+  encoder = Encoders.float4
+  decoder = Decoders.float4
+
+instance IsScalar Double where
+  encoder = Encoders.float8
+  decoder = Decoders.float8
+
+instance IsScalar Scientific where
+  encoder = Encoders.numeric
+  decoder = Decoders.numeric
+
+-- Text types
+instance IsScalar Text where
+  encoder = Encoders.text
+  decoder = Decoders.text
+
+-- Binary types
+instance IsScalar ByteString where
+  encoder = Encoders.bytea
+  decoder = Decoders.bytea
+
+-- Date/Time types
+instance IsScalar Day where
+  encoder = Encoders.date
+  decoder = Decoders.date
+
+instance IsScalar LocalTime where
+  encoder = Encoders.timestamp
+  decoder = Decoders.timestamp
+
+instance IsScalar UTCTime where
+  encoder = Encoders.timestamptz
+  decoder = Decoders.timestamptz
+
+instance IsScalar TimeOfDay where
+  encoder = Encoders.time
+  decoder = Decoders.time
+
+instance IsScalar (TimeOfDay, TimeZone) where
+  encoder = Encoders.timetz
+  decoder = Decoders.timetz
+
+instance IsScalar DiffTime where
+  encoder = Encoders.interval
+  decoder = Decoders.interval
+
+-- UUID
+instance IsScalar UUID where
+  encoder = Encoders.uuid
+  decoder = Decoders.uuid
+
+-- Network types
+instance IsScalar IPRange where
+  encoder = Encoders.inet
+  decoder = Decoders.inet
+
+instance IsScalar (Word8, Word8, Word8, Word8, Word8, Word8) where
+  encoder = Encoders.macaddr
+  decoder = Decoders.macaddr
+
+-- JSON types
+instance IsScalar Aeson.Value where
+  encoder = Encoders.jsonb
+  decoder = Decoders.jsonb
diff --git a/src/library/Hasql/Mapping/IsStatement.hs b/src/library/Hasql/Mapping/IsStatement.hs
new file mode 100644
--- /dev/null
+++ b/src/library/Hasql/Mapping/IsStatement.hs
@@ -0,0 +1,55 @@
+module Hasql.Mapping.IsStatement where
+
+import qualified Hasql.Statement as Statement
+
+-- |
+-- Evidence that a data-structure models statement parameters determining the statement and its result type.
+--
+-- Supports a modularisation pattern, where you define everything related to one statement in an isolated module.
+-- This pattern leads to high code cohesion and low coupling.
+--
+-- ==== __Example of such a module__
+--
+-- > module MusicCatalogueDb.Statements.SelectArtistIdsByName where
+-- >
+-- > import Data.Functor.Contravariant
+-- > import Data.Text (Text)
+-- > import Data.UUID (UUID)
+-- > import Data.Vector (Vector)
+-- > import qualified Hasql.Decoders as Decoders
+-- > import qualified Hasql.Encoders as Encoders
+-- > import Hasql.Mapping.IsStatement
+-- > import Prelude
+-- >
+-- > data SelectArtistIdsByName = SelectArtistIdsByName
+-- >   { name :: Text
+-- >   }
+-- >
+-- > type SelectArtistIdsByNameResult = Vector SelectArtistIdsByNameResultRow
+-- >
+-- > data SelectArtistIdsByNameResultRow = SelectArtistIdsByNameResultRow
+-- >   { id :: UUID
+-- >   }
+-- >
+-- > instance IsStatement SelectArtistIdsByName where
+-- >   type Result SelectArtistIdsByName = SelectArtistIdsByNameResult
+-- >   statement =
+-- >     Statement.preparable sql encoder decoder
+-- >     where
+-- >       sql =
+-- >         "select id from artist\n\
+-- >         \where name = $1\n\
+-- >         \limit 1"
+-- >       encoder =
+-- >         mconcat
+-- >           [ (\(SelectArtistIdsByName x) -> x)
+-- >               >$< Encoders.param (Encoders.nonNullable Encoders.text)
+-- >           ]
+-- >       decoder =
+-- >         Decoders.rowVector
+-- >           ( SelectArtistIdsByNameResultRow
+-- >               <$> Decoders.column (Decoders.nonNullable Decoders.uuid)
+-- >           )
+class IsStatement a where
+  type Result a
+  statement :: Statement.Statement a (Result a)
