diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for aeson-single-field
+
+## 0.1.0.0 -- 2022-06-12
+
+* First version. 
diff --git a/README.lhs b/README.lhs
new file mode 100644
--- /dev/null
+++ b/README.lhs
@@ -0,0 +1,77 @@
+Aeson Single Field
+=================
+
+When working with HTMX, I found the slightly irritating requirement that all JSON fragments must be records, not any other type of literals. This resulting in some boilerplate code to make newtype wrappers to wrap any type whose JSON forms where not records. This package exports a single type (`SingleField`) that helps reduce boilerplate for this problem.
+
+This is a literate Haskell file, so we start by importing some packages and setting some language options
+
+```haskell
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeApplications #-}
+
+import Data.Aeson (FromJSON, ToJSON, encode)
+import Data.Aeson.SingleField (SingleField(..))
+import GHC.Generics (Generic())
+import Test.HUnit
+  ( Assertion
+  , Test(TestCase, TestList)
+  , assertEqual
+  , runTestTTAndExit
+  )
+```
+
+As an example, consider the following type.
+
+```haskell
+data MyPair =
+  MyPair Int String
+  deriving (Eq, Show, Generic)
+
+instance ToJSON MyPair
+
+instance FromJSON MyPair
+```
+
+This is a contrived example, but the JSON representation of `MyPair` is a vector.
+
+```haskell
+encodeMyPair :: Assertion
+encodeMyPair =
+  assertEqual "Encode instance" (encode $ MyPair 1 "hi") "[1,\"hi\"]"
+```
+
+We could override the `ToJSON` and `FromJSON` instances here, but that is error prone, and introduces extra complexity just to deal with a slight API mismatch. Instead we can do the following.
+
+```haskell
+encodeMyPairWithField :: Assertion
+encodeMyPairWithField =
+  let wrappedField :: SingleField "test-field" MyPair =
+        SingleField $ MyPair 1 "hi"
+   in assertEqual "Encode Instance" (encode wrappedField) "{\"test-field\":[1,\"hi\"]}"
+```
+
+Note the type level string - that is what gives us our field name. We can now see that our JSON is an object with a single field, just as we wanted. One can also write this in a more compact form with type applications.
+
+```haskell
+encodeMyPairWithFieldCompact :: Assertion
+encodeMyPairWithFieldCompact =
+  assertEqual
+    "Encode Instance"
+    (encode (SingleField @"test-field" (MyPair 1 "hi")))
+    "{\"test-field\":[1,\"hi\"]}"
+```
+
+The following is required to run these assertions.
+
+```haskell
+main :: IO ()
+main =
+  runTestTTAndExit $
+  TestList $
+  map
+    TestCase
+    [encodeMyPair, encodeMyPairWithField, encodeMyPairWithFieldCompact]
+```
diff --git a/aeson-single-field.cabal b/aeson-single-field.cabal
new file mode 100644
--- /dev/null
+++ b/aeson-single-field.cabal
@@ -0,0 +1,31 @@
+cabal-version:      2.4
+name:               aeson-single-field
+version:            0.1.0.0
+
+synopsis: Conveniently wrap a single value in a record when encoding to and from JSON
+description: This package exports a single type (`SingleField`) that allows one to conveniently wrap a single value in a record for use with JSON.
+
+license: MIT
+category:           Text, Web, JSON
+author:             Ed Wastell
+maintainer:         ed@wastell.co.uk
+
+extra-source-files: CHANGELOG.md
+
+library
+  exposed-modules:  Data.Aeson.SingleField
+  build-depends:    base >= 4.14.0.0 && < 5
+    , aeson ^>= 2.0.0.0
+  hs-source-dirs:   src
+  default-language: Haskell2010
+
+test-suite readme
+  build-depends: base >= 4.14.0.0 && < 5
+    , aeson-single-field
+    , aeson ^>= 2.0.0.0
+    , markdown-unlit ^>= 0.5
+    , HUnit
+  type: exitcode-stdio-1.0
+  ghc-options: -pgmL markdown-unlit -Wall -Wno-unused-top-binds
+  default-language: Haskell2010
+  main-is: README.lhs
diff --git a/src/Data/Aeson/SingleField.hs b/src/Data/Aeson/SingleField.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Aeson/SingleField.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+-- | A type for wrapping a single type in a record when encoding to and from JSON.
+-- See `SingleField` for this type.
+module Data.Aeson.SingleField (SingleField (..)) where
+
+import Data.Aeson
+import Data.Proxy (Proxy (..))
+import GHC.Exts (fromString)
+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
+
+-- | When interacting with or producing JSON, it is common to wrap a single field in
+-- an object with a single field. This can be a bit awkward to use from Haskell - we
+-- tend to write a single `ToJSON` and `FromJSON` instance for each type, making this
+-- extra wrapping cumbersome.
+--
+-- This newtype wrapper can helper with this. @SingleField field a@ contains a single
+-- value of @a@. The difference is that the `ToJSON` and `FromJSON` instances will wrap
+-- the @a@ in an object with a single field @field@. @field@ is a /type level/ string,
+-- using data kinds.
+--
+-- >>> encode (SingleField @"myField" 3)
+-- "{\"myField\":3}"
+--
+-- >>> getSingleField <$> decode @(SingleField "myField" Int) (encode $ SingleField @"myField" 123)
+-- Just 123
+newtype SingleField (field :: Symbol) (a :: *) = SingleField {getSingleField :: a}
+  deriving (Eq, Show, Functor)
+
+keyFromProxy :: KnownSymbol n => Proxy n -> Key
+keyFromProxy = fromString . symbolVal
+
+instance (ToJSON a, KnownSymbol field) => ToJSON (SingleField field a) where
+  toJSON (SingleField a) = object [keyFromProxy (Proxy @field) .= a]
+
+instance (FromJSON a, KnownSymbol field) => FromJSON (SingleField field a) where
+  parseJSON = withObject ("SingleField " <> symbolVal (Proxy @field)) $
+    \obj -> SingleField <$> (obj .: keyFromProxy (Proxy @field))
