diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Eric Conlon (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 Eric Conlon 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,5 @@
+# aeson-via
+
+[![CircleCI](https://circleci.com/gh/ejconlon/aeson-via/tree/master.svg?style=svg)](https://circleci.com/gh/ejconlon/aeson-via/tree/master)
+
+Wrappers to derive-via Aeson ToJSON/FromJSON typeclasses with typeclass-directed configuration.
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/aeson-via.cabal b/aeson-via.cabal
new file mode 100644
--- /dev/null
+++ b/aeson-via.cabal
@@ -0,0 +1,43 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 60d4229649edb0ef7f1268f79df05c7fa9d34be348ef1a2d860902ccdc143242
+
+name:           aeson-via
+version:        0.1.0
+synopsis:       Wrappers to derive-via Aeson ToJSON/FromJSON typeclasses
+description:    Please see the README on GitHub at <https://github.com/ejconlon/aeson-via#readme>
+category:       Data
+homepage:       https://github.com/ejconlon/aeson-via#readme
+bug-reports:    https://github.com/ejconlon/aeson-via/issues
+author:         Eric Conlon
+maintainer:     ejconlon@gmail.com
+copyright:      (c) 2020 Eric Conlon
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/ejconlon/aeson-via
+
+library
+  exposed-modules:
+      AesonVia
+  other-modules:
+      Paths_aeson_via
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds
+  build-depends:
+      aeson >=1.4
+    , aeson-casing >=0.2
+    , base >=4.12 && <5
+    , newtype-generics >=0.5
+    , text >=1.2
+  default-language: Haskell2010
diff --git a/src/AesonVia.hs b/src/AesonVia.hs
new file mode 100644
--- /dev/null
+++ b/src/AesonVia.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module AesonVia
+  ( AesonRecord (..)
+  , AesonNewtype (..)
+  , AesonTag (..)
+  , HasJSONOptions (..)
+  , HasTagPrefix (..)
+  ) where
+
+import Control.Newtype.Generics (Newtype, O, pack, unpack)
+import Data.Aeson
+import Data.Aeson.Casing (aesonPrefix, snakeCase)
+import Data.Proxy (Proxy (..))
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics (Generic, Rep)
+import Prelude
+
+-- Options
+
+recordOptions :: Options
+recordOptions = (aesonPrefix snakeCase) { omitNothingFields = True }
+
+tagOptions :: Text -> Options
+tagOptions prefix =
+  let prefixLen = Text.length prefix
+  in defaultOptions
+      { allNullaryToStringTag = True
+      , constructorTagModifier = snakeCase . drop prefixLen
+      }
+
+newtypeOptions :: Options
+newtypeOptions = defaultOptions
+  { unwrapUnaryRecords = True
+  }
+
+-- Has classes
+
+class HasJSONOptions a where
+  getJSONOptions :: Proxy a -> Options
+
+class HasTagPrefix a where
+  getTagPrefix :: Proxy a -> Text
+
+-- Wrappers
+
+newtype AesonTag a = AesonTag { unAesonTag :: a }
+
+instance HasTagPrefix a => HasJSONOptions (AesonTag a) where
+  getJSONOptions _ = tagOptions (getTagPrefix (Proxy :: Proxy a))
+
+instance (HasJSONOptions (AesonTag a), Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a)) => ToJSON (AesonTag a) where
+  toJSON = genericToJSON (getJSONOptions (Proxy :: Proxy (AesonTag a))) . unAesonTag
+  toEncoding = genericToEncoding (getJSONOptions (Proxy :: Proxy (AesonTag a))) . unAesonTag
+
+instance (HasJSONOptions (AesonTag a), Generic a, GFromJSON Zero (Rep a)) => FromJSON (AesonTag a) where
+  parseJSON = (AesonTag <$>) . genericParseJSON (getJSONOptions (Proxy :: Proxy (AesonTag a)))
+
+newtype AesonRecord a = AesonRecord { unAesonRecord :: a }
+
+instance HasJSONOptions (AesonRecord a) where
+  getJSONOptions _ = recordOptions
+
+instance (HasJSONOptions (AesonRecord a), Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a)) => ToJSON (AesonRecord a) where
+  toJSON = genericToJSON (getJSONOptions (Proxy :: Proxy (AesonRecord a))) . unAesonRecord
+  toEncoding = genericToEncoding (getJSONOptions (Proxy :: Proxy (AesonRecord a))) . unAesonRecord
+
+instance (HasJSONOptions (AesonRecord a), Generic a, GFromJSON Zero (Rep a)) => FromJSON (AesonRecord a) where
+  parseJSON = (AesonRecord <$>) . genericParseJSON (getJSONOptions (Proxy :: Proxy (AesonRecord a)))
+
+newtype AesonNewtype n o = AesonNewtype { unAesonNewtype :: n }
+
+instance HasJSONOptions (AesonNewtype n o) where
+  getJSONOptions _ = newtypeOptions
+
+instance (Newtype n, o ~ O n, ToJSON o) => ToJSON (AesonNewtype n o) where
+  toJSON = toJSON . unpack . unAesonNewtype
+  toEncoding = toEncoding . unpack . unAesonNewtype
+
+instance (Newtype n, o ~ O n, FromJSON o) => FromJSON (AesonNewtype n o) where
+  parseJSON = ((AesonNewtype . pack) <$>) . parseJSON
