diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,7 +1,11 @@
 # Changelog for aeson-deriving
 
-## Unreleased changes
+# Revision history for servant-openapi
 
- * A newtype for encoding Aeson's generic Options type
- * A newtype for sums of records
- * A newtype for nesting the encoding under a single field
+### 0.1.1
+
+* Added newtype for Text validated against a regex pattern [PR 2](https://github.com/fieldstrength/aeson-deriving/pull/2)
+
+## 0.1.0
+
+* First version. Released on an unsuspecting world.
diff --git a/aeson-deriving.cabal b/aeson-deriving.cabal
--- a/aeson-deriving.cabal
+++ b/aeson-deriving.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: bd8e2b4289bac4670675ad3e5d5307767ea3ad1d4a053a709b95767944b84df0
+-- hash: 10c4d376657004b7575f0a910fbcb4dd80c3a284e99343ca3f3e962eeba7cd0e
 
 name:           aeson-deriving
-version:        0.1.0.0
+version:        0.1.1
 synopsis:       data types for compositional, type-directed serialization
 description:    Please see the README on GitHub at <https://github.com/fieldstrength/aeson-deriving#readme>
 category:       Serialization
@@ -39,6 +39,8 @@
       Data.Aeson.Deriving.Utils
       Data.Aeson.Deriving.WithConstantFields
   other-modules:
+      Data.Aeson.Deriving.Text
+      Data.Aeson.Deriving.Text.Unsafe
       Paths_aeson_deriving
   hs-source-dirs:
       src
@@ -47,6 +49,7 @@
   build-depends:
       aeson >=1.2 && <1.5
     , base >=4.7 && <5
+    , regex-tdfa
     , text
     , unordered-containers
   default-language: Haskell2010
@@ -65,6 +68,7 @@
     , aeson-deriving
     , base >=4.7 && <5
     , hedgehog
+    , regex-tdfa
     , text
     , unordered-containers
   default-language: Haskell2010
diff --git a/src/Data/Aeson/Deriving.hs b/src/Data/Aeson/Deriving.hs
--- a/src/Data/Aeson/Deriving.hs
+++ b/src/Data/Aeson/Deriving.hs
@@ -4,4 +4,5 @@
 import           Data.Aeson.Deriving.Known              as AllExports
 import           Data.Aeson.Deriving.ModifyField        as AllExports
 import           Data.Aeson.Deriving.SingleFieldObject  as AllExports
+import           Data.Aeson.Deriving.Text               as AllExports
 import           Data.Aeson.Deriving.WithConstantFields as AllExports
diff --git a/src/Data/Aeson/Deriving/Generic.hs b/src/Data/Aeson/Deriving/Generic.hs
--- a/src/Data/Aeson/Deriving/Generic.hs
+++ b/src/Data/Aeson/Deriving/Generic.hs
@@ -31,6 +31,7 @@
   , SnakeCase
   , Uppercase
   , Lowercase
+  , FirstChar
   , DropLowercasePrefix
   , DropPrefix
   , DropSuffix
diff --git a/src/Data/Aeson/Deriving/Internal/Generic.hs b/src/Data/Aeson/Deriving/Internal/Generic.hs
--- a/src/Data/Aeson/Deriving/Internal/Generic.hs
+++ b/src/Data/Aeson/Deriving/Internal/Generic.hs
@@ -304,6 +304,7 @@
 data SnakeCase
 data Uppercase
 data Lowercase
+data FirstChar a
 -- | Applies 'dropLowercasePrefix', dropping until the first uppercase character.
 data DropLowercasePrefix
 data DropPrefix (str :: Symbol)
@@ -328,6 +329,11 @@
   stringFunction Proxy x
     | x == symbolVal (Proxy @a) = symbolVal (Proxy @b)
     | otherwise = x
+
+instance StringFunction a => StringFunction (FirstChar a) where
+  stringFunction Proxy = \case
+    [] -> []
+    c:cs -> stringFunction (Proxy @a) [c] ++ cs
 
 ------------------------------------------------------------------------------------------
 -- Sum type encodings
diff --git a/src/Data/Aeson/Deriving/Text.hs b/src/Data/Aeson/Deriving/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Aeson/Deriving/Text.hs
@@ -0,0 +1,5 @@
+module Data.Aeson.Deriving.Text
+  ( TextWithPattern
+  ) where
+
+import Data.Aeson.Deriving.Text.Unsafe (TextWithPattern)
diff --git a/src/Data/Aeson/Deriving/Text/Unsafe.hs b/src/Data/Aeson/Deriving/Text/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Aeson/Deriving/Text/Unsafe.hs
@@ -0,0 +1,17 @@
+module Data.Aeson.Deriving.Text.Unsafe where
+
+import           Control.Monad   (unless)
+import           Data.Aeson
+import           Data.Proxy
+import           Data.Text       (Text, unpack)
+import           Text.Regex.TDFA ((=~))
+import           GHC.TypeLits    (KnownSymbol, Symbol, symbolVal)
+
+newtype TextWithPattern (regex :: Symbol) = TextWithPattern Text
+  deriving newtype (ToJSON)
+
+instance KnownSymbol regex => FromJSON (TextWithPattern regex) where
+  parseJSON = withText "Text" $ \s ->
+    TextWithPattern s <$ unless (unpack s =~ (symbolVal $ Proxy @regex)) (fail errorMsg)
+    where
+      errorMsg = "must match regex " <> (symbolVal $ Proxy @regex)
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -8,6 +8,9 @@
 
 import Data.Aeson
 import Data.Aeson.Deriving
+import Data.Foldable (for_)
+import Data.Aeson.Deriving.Text.Unsafe
+import Data.Text
 import GHC.Generics
 import Hedgehog
 import Hedgehog.Main (defaultMain)
@@ -186,3 +189,31 @@
   tripping (Reserved "Sen" 9 'x') encode decode
   Just (Reserved "Sen" 9 'x') ===
     decode "{\"control\":\"x\",\"module\":9,\"type\":\"Sen\"}"
+
+newtype DashSeparatedWords = DashSeparatedWords Text
+  deriving stock (Generic, Show, Eq)
+  deriving (FromJSON, ToJSON) via TextWithPattern "^([A-Za-z]+-)*[A-Za-z]+$"
+
+prop_accepts_matches :: Property
+prop_accepts_matches = once . property $ do
+  tripping (DashSeparatedWords "foo-bar-baz") encode decode
+  Just (DashSeparatedWords "foo-bar-baz") === decode "\"foo-bar-baz\""
+
+prop_rejects_non_matches :: Property
+prop_rejects_non_matches = once . property $ do
+  Left "Error in $: must match regex ^([A-Za-z]+-)*[A-Za-z]+$" === eitherDecode @DashSeparatedWords "\"foo.42\""
+
+
+data Heavy = BlackHole | NeutronStar
+ deriving stock (Generic, Show, Eq, Ord, Bounded, Enum)
+ deriving (FromJSON, ToJSON) via GenericEncoded '[ConstructorTagModifier := FirstChar Lowercase] Heavy
+
+prop_first_char_modifier_encodes_as_expected :: Property
+prop_first_char_modifier_encodes_as_expected = once . property $ do
+  encode BlackHole === "\"blackHole\""
+  encode NeutronStar === "\"neutronStar\""
+
+prop_first_char_modifier_decodes_as_expected :: Property
+prop_first_char_modifier_decodes_as_expected = once . property $ do
+  for_ [BlackHole ..] $ \x ->
+    tripping x encode decode
