diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for deriving-aeson
+
+## 0 -- 2020-02-26
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Fumiaki Kinoshita (c) 2020
+
+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 Fumiaki Kinoshita 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/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/deriving-aeson.cabal b/deriving-aeson.cabal
new file mode 100644
--- /dev/null
+++ b/deriving-aeson.cabal
@@ -0,0 +1,29 @@
+cabal-version:       2.4
+name:                deriving-aeson
+version:             0
+synopsis:            Type driven generic aeson instance customisation
+description:         This package provides a newtype wrapper with
+  FromJSON/ToJSON instances customisable via a phantom type parameter.
+  The instances can be rendered to the original type using DerivingVia.
+bug-reports:         https://github.com/fumieval/deriving-aeson
+license:             BSD-3-Clause
+license-file:        LICENSE
+author:              Fumiaki Kinoshita
+maintainer:          fumiexcel@gmail.com
+copyright:           Copyright (c) 2020 Fumiaki Kinoshita
+category:            JSON, Generics
+extra-source-files:  CHANGELOG.md
+
+library
+  exposed-modules:     Deriving.Aeson
+  build-depends:       base >= 4.12 && <5, aeson
+  hs-source-dirs: src
+  default-language:    Haskell2010
+  ghc-options: -Wall
+
+test-suite test
+  type: exitcode-stdio-1.0
+  main-is: test.hs
+  build-depends: base, aeson, deriving-aeson, bytestring
+  hs-source-dirs: tests
+  default-language:    Haskell2010
diff --git a/src/Deriving/Aeson.hs b/src/Deriving/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Deriving/Aeson.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE UndecidableInstances #-}
+--------------------
+-- | Type-directed aeson instance CustomJSONisation
+--------------------
+module Deriving.Aeson
+  ( CustomJSON(..)
+  , FieldLabelModifier
+  , ConstrctorTagModifier
+  , OmitNothingFields
+  , TagSingleConstructors
+  , NoAllNullaryToStringTag
+  -- * Name modifiers
+  , StripPrefix
+  , CamelToKebab
+  , CamelToSnake
+  -- * Interface
+  , AesonOptions(..)
+  , StringModifier(..)
+  -- * Reexports
+  , FromJSON
+  , ToJSON
+  , Generic
+  )where
+
+import Data.Aeson
+import Data.Coerce
+import Data.List (stripPrefix)
+import Data.Maybe (fromMaybe)
+import Data.Proxy
+import GHC.Generics
+import GHC.TypeLits
+
+-- | A newtype wrapper which gives FromJSON/ToJSON instances with modified options.
+newtype CustomJSON t a = CustomJSON { unCustomJSON :: a }
+
+instance (AesonOptions t, Generic a, GFromJSON Zero (Rep a)) => FromJSON (CustomJSON t a) where
+  parseJSON = (coerce `asTypeOf` fmap CustomJSON) . genericParseJSON (aesonOptions @t)
+  {-# INLINE parseJSON #-}
+
+instance (AesonOptions t, Generic a, GToJSON Zero (Rep a)) => ToJSON (CustomJSON t a) where
+  toJSON = genericToJSON (aesonOptions @t) . unCustomJSON
+  {-# INLINE toJSON #-}
+
+-- | Function applied to field labels. Handy for removing common record prefixes for example.
+data FieldLabelModifier t
+
+-- | Function applied to constructor tags which could be handy for lower-casing them for example.
+data ConstrctorTagModifier t
+
+-- | Record fields with a Nothing value will be omitted from the resulting object.
+data OmitNothingFields
+
+-- | Encode types with a single constructor as sums, so that allNullaryToStringTag and sumEncoding apply.
+data TagSingleConstructors
+
+-- | the encoding will always follow the 'sumEncoding'.
+data NoAllNullaryToStringTag
+
+-- | Strip prefix @t@. If it doesn't have the prefix, keep it as-is.
+data StripPrefix t
+
+-- | CamelCase to snake_case
+data CamelToSnake
+
+-- | CamelCase to kebab-case
+data CamelToKebab
+
+-- | Reify a function which modifies names
+class StringModifier t where
+  getStringModifier :: String -> String
+
+instance KnownSymbol k => StringModifier (StripPrefix k) where
+  getStringModifier = fromMaybe <*> stripPrefix (symbolVal (Proxy @k))
+
+-- | Left-to-right (@'flip' '.'@) composition
+instance (StringModifier a, StringModifier b) => StringModifier (a, b) where
+  getStringModifier = getStringModifier @b . getStringModifier @a
+
+instance StringModifier CamelToKebab where
+  getStringModifier = camelTo2 '-'
+
+instance StringModifier CamelToSnake where
+  getStringModifier = camelTo2 '_'
+
+-- | Reify 'Options' from a type-level list
+class AesonOptions xs where
+  aesonOptions :: Options
+
+instance AesonOptions '[] where
+  aesonOptions = defaultOptions
+
+instance AesonOptions xs => AesonOptions (OmitNothingFields ': xs) where
+  aesonOptions = (aesonOptions @xs) { omitNothingFields = True }
+
+instance (StringModifier f, AesonOptions xs) => AesonOptions (FieldLabelModifier f ': xs) where
+  aesonOptions = (aesonOptions @xs) { fieldLabelModifier = getStringModifier @f }
+
+instance (StringModifier f, AesonOptions xs) => AesonOptions (ConstrctorTagModifier f ': xs) where
+  aesonOptions = (aesonOptions @xs) { constructorTagModifier = getStringModifier @f }
+
+instance AesonOptions xs => AesonOptions (TagSingleConstructors ': xs) where
+  aesonOptions = (aesonOptions @xs) { tagSingleConstructors = True }
+
+instance AesonOptions xs => AesonOptions (NoAllNullaryToStringTag ': xs) where
+  aesonOptions = (aesonOptions @xs) { allNullaryToStringTag = False }
diff --git a/tests/test.hs b/tests/test.hs
new file mode 100644
--- /dev/null
+++ b/tests/test.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE DerivingVia, DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+module Main where
+
+import Data.Aeson
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Deriving.Aeson
+
+data User = User
+  { userId :: Int
+  , userName :: String
+  , userAPIToken :: Maybe String
+  } deriving Generic
+  deriving (FromJSON, ToJSON)
+  via CustomJSON '[OmitNothingFields, FieldLabelModifier (StripPrefix "user", CamelToSnake)] User
+
+testData :: [User]
+testData = [User 42 "Alice" Nothing, User 43 "Bob" (Just "xyz")]
+
+main = BL.putStrLn $ encode $ testData
