diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
 # Revision history for mmzk-env
 
 
+## 0.2.1.0 -- 2025-11-30
+
+* Add the missing `validateEnvW` and `validateEnvWWith` functions for validating environment variables with witness types.
+
+* Add `DefaultString` witness type for providing default string values.
+
+* Add more texts and examples.
+
+
 ## 0.2.0.0 -- 2025-11-29
 
 * Witnessed record parsing (witness types carry type-level information that determines parsing behaviour):
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
 - [mmzk-env](#mmzk-env)
   - [Contents](#contents)
   - [Quick Start](#quick-start)
+    - [Custom Environment Variable Mapping](#custom-environment-variable-mapping)
   - [Enum Support](#enum-support)
   - [Witness Types: Avoiding Newtype Boilerplate](#witness-types-avoiding-newtype-boilerplate)
     - [The Problem: Newtype Boilerplate](#the-problem-newtype-boilerplate)
@@ -18,7 +19,7 @@
 
 ## Quick Start
 
-**[Full example →](app/QuickstartExample.hs)**
+**[Full example →][quickstart-example]**
 
 ```Haskell
 {-# LANGUAGE DeriveAnyClass #-}
@@ -49,9 +50,37 @@
 
 If any variable is missing or has an incorrect type, the validation will fail, and an error message will be printed.
 
+### Custom Environment Variable Mapping
+
+**[Full example →][custom-mapping-example]**
+
+By default, the library converts camelCase field names to UPPER_SNAKE_CASE (e.g., `mainHost` → `MAIN_HOST`).
+
+If you want to use uppercase environment variable names without underscores (like `MAINHOST` instead of `MAIN_HOST`), you can use `validateEnvWith` (or `validateEnvWWith` for witness types) with a custom mapping function:
+
+```Haskell
+data Config = Config
+    { port      :: Int
+    , name      :: String
+    , main_host :: String  -- Will map to "MAINHOST" with custom mapping
+    , debug     :: Maybe Bool }
+    deriving (Show, Generic, EnvSchema)
+
+main :: IO ()
+main = do
+  errOrEnv <- validateEnvWith @Config (map toUpper)
+  case errOrEnv of
+    Left err  -> putStrLn $ "Validation failed: " ++ err
+    Right cfg -> putStrLn $ "Config loaded successfully: " ++ show cfg
+```
+
+With `validateEnvWith (map toUpper)`, the field `main_host` will look for the environment variable `MAINHOST` instead of `MAIN_HOST`.
+
+You can provide any custom mapping function to `validateEnvWith` to transform field names to environment variable names according to your needs.
+
 ## Enum Support
 
-**[Full example →](app/EnumExample.hs)**
+**[Full example →][enum-example]**
 
 The library also supports automatic parsing of enumerated types. You can define an enum and derive the `TypeParser` instance using the helper type `EnumParser`.
 
@@ -75,7 +104,7 @@
 
 ### The Problem: Newtype Boilerplate
 
-**[Full example →](app/NewtypeExample.hs)**
+**[Full example →][newtype-example]**
 
 Let's say you want to parse a PostgreSQL port that defaults to 5432. Without witnesses, you might create a newtype wrapper:
 
@@ -122,7 +151,7 @@
 
 ### The Solution: Witnesses
 
-**[Full example →](app/WitnessExample.hs)**
+**[Full example →][witness-example]**
 
 With witness types, you can specify parsing behaviour at the type level while keeping the final value unwrapped:
 
@@ -134,7 +163,7 @@
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TypeApplications #-}
 
-import Data.Env.RecordParserW
+import Data.Env
 import Data.Env.Witness.DefaultNum
 import Data.Word
 import GHC.Generics
@@ -143,16 +172,17 @@
 data Config c = Config
   { psqlPort :: Column c (DefaultNum 5432 Word16) Word16  -- Defaults to 5432
   , dbName   :: Column c (Solo String) String }
-  deriving (Generic)
+  deriving (Generic, EnvSchemaW)
 
+instance EnvSchemaW (Config \'Dec)
 deriving stock instance Show (Config 'Res)  -- For printing the result
 
--- Parse with defaults
+-- Validate environment variables with defaults
 main :: IO ()
 main = do
-  errOrConfig <- parseRecordW @(Config 'Dec) mempty
+  errOrConfig <- validateEnvW @(Config 'Dec)
   case errOrConfig of
-    Left err  -> putStrLn $ "Parse failed: " ++ err
+    Left err  -> putStrLn $ "Validation failed: " ++ err
     Right cfg -> connectToDatabase cfg  -- cfg :: Config 'Res
 ```
 
@@ -161,7 +191,7 @@
 - **`Config 'Dec`** (Declaration): The type used for parsing, where each field is `(witness, value)`
   - This works under the hood for the generic instances and users typically don't interact with it directly
 - **`Config 'Res`** (Result): The type you work with, where each field is just `value`
-- **`Column c witness a`**: Expands to `(witness a, a)` when `c = 'Dec`, or just `a` when `c = 'Res`
+- **`Column c witness a`**: Expands to `(witness, a)` when `c = 'Dec`, or just `a` when `c = 'Res`
 
 Now your final config has no wrappers:
 
@@ -180,10 +210,17 @@
 
 ### Available Witnesses
 
-- **`DefaultNum n a`**: Numeric types with a type-level default value `n`
 - **`Solo a`**: Standard parsing without special behaviour (equivalent to `TypeParser`)
+- **`DefaultNum n a`**: Numeric types with a type-level default value `n`
+- - **`DefaultString s a`**: String types with a type-level default value `s`
 - **Custom witnesses**: You can define your own by implementing the `TypeParserW` class
 
 More built-in witnesses will be provided.
 
 For more complex parsing needs, witnesses provide a way to augment behaviour without polluting your domain types with wrapper noise.
+
+[quickstart-example]: https://github.com/MMZK1526/mmzk-env/blob/v0.2.1.0/app/QuickstartExample.hs
+[custom-mapping-example]: https://github.com/MMZK1526/mmzk-env/blob/v0.2.1.0/app/CustomMappingExample.hs
+[enum-example]: https://github.com/MMZK1526/mmzk-env/blob/v0.2.1.0/app/EnumExample.hs
+[newtype-example]: https://github.com/MMZK1526/mmzk-env/blob/v0.2.1.0/app/NewtypeExample.hs
+[witness-example]: https://github.com/MMZK1526/mmzk-env/blob/v0.2.1.0/app/WitnessExample.hs
diff --git a/app/CustomMappingExample.hs b/app/CustomMappingExample.hs
new file mode 100644
--- /dev/null
+++ b/app/CustomMappingExample.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Main where
+
+import Data.Char (toUpper)
+import Data.Env
+import GHC.Generics
+
+-- | Example: Define an environment schema with custom mapping
+data Config = Config
+    { port      :: Int
+    , name      :: String
+    , main_host :: String
+    , debug     :: Maybe Bool }
+    deriving (Show, Generic, EnvSchema)
+
+-- | Run the validation with custom mapping
+-- This will look for environment variables: PORT, NAME, MAINHOST, DEBUG
+-- Note: main_host maps to MAINHOST (not MAIN_HOST) with map toUpper
+main :: IO ()
+main = do
+  errOrEnv <- validateEnvWith @Config (map toUpper)
+  case errOrEnv of
+    Left err  -> putStrLn $ "Validation failed: " ++ err
+    Right cfg -> putStrLn $ "Config loaded successfully: " ++ show cfg
diff --git a/app/WitnessExample.hs b/app/WitnessExample.hs
--- a/app/WitnessExample.hs
+++ b/app/WitnessExample.hs
@@ -7,11 +7,11 @@
 
 module Main where
 
-import Data.Env.RecordParserW
+import Data.Env
 import Data.Env.Witness.DefaultNum
-import qualified Data.Map as M
 import Data.Word
 import GHC.Generics
+import Data.Env.RecordParserW
 import Data.Env.TypeParserW
 
 data Config c = Config
@@ -19,19 +19,13 @@
   , dbName   :: Column c (Solo String) String }
   deriving (Generic)
 
+instance EnvSchemaW (Config 'Dec)
 deriving stock instance Show (Config 'Res)  -- For printing the result
 
--- Parse with defaults
+-- Validate environment variables with defaults
 main :: IO ()
 main = do
-  -- Example 1: Empty environment (uses defaults)
-  let emptyEnv = M.empty
-  case parseRecordW @(Config 'Dec) emptyEnv of
-    Left err  -> putStrLn $ "Parse failed: " ++ err
+  errOrConfig <- validateEnvW @(Config 'Dec)
+  case errOrConfig of
+    Left err  -> putStrLn $ "Validation failed: " ++ err
     Right cfg -> putStrLn $ "Config with defaults: " ++ show cfg
-
-  -- Example 2: Custom values
-  let customEnv = M.fromList [("psqlPort", "8080"), ("dbName", "mydb")]
-  case parseRecordW @(Config 'Dec) customEnv of
-    Left err  -> putStrLn $ "Parse failed: " ++ err
-    Right cfg -> putStrLn $ "Config with custom values: " ++ show cfg
diff --git a/mmzk-env.cabal b/mmzk-env.cabal
--- a/mmzk-env.cabal
+++ b/mmzk-env.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               mmzk-env
-version:            0.2.0.0
+version:            0.2.1.0
 
 synopsis:           Read environment variables into a user-defined data type
 description:
@@ -58,6 +58,7 @@
         Data.Env.TypeParser
         Data.Env.TypeParserW
         Data.Env.Witness.DefaultNum
+        Data.Env.Witness.DefaultString
     build-depends:
         base >=4.16 && <5,
         containers >= 0.6.7 && < 0.7,
@@ -70,7 +71,9 @@
     import:           settings
     main-is:          Spec.hs
     other-modules:
-        TypeParserSpec
+        BasicTypeParserSpec
+        DefaultWitnessSpec
+        EnumParserSpec
         RecordParserSpec
         RecordParserWSpec
     type:             exitcode-stdio-1.0
@@ -87,6 +90,15 @@
 
 executable quickstart-example
     main-is:          QuickstartExample.hs
+    build-depends:
+        base >=4.16 && <5,
+        mmzk-env,
+    hs-source-dirs:   app
+    default-language: Haskell2010
+
+
+executable custom-mapping-example
+    main-is:          CustomMappingExample.hs
     build-depends:
         base >=4.16 && <5,
         mmzk-env,
diff --git a/src/Data/Env.hs b/src/Data/Env.hs
--- a/src/Data/Env.hs
+++ b/src/Data/Env.hs
@@ -1,14 +1,17 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+
 -- |
 -- Module      : Data.Env
 -- Description : Environment schema validation
 --
 -- This module provides functionality to validate environment variables against
 -- a schema (a type that implements the `EnvSchema` class).
-module Data.Env ( EnvSchema(..) ) where
+module Data.Env ( EnvSchema(..), EnvSchemaW(..) ) where
 
 import Control.Monad.IO.Class
 import Data.Env.ExtractFields
 import Data.Env.RecordParser
+import Data.Env.RecordParserW
 
 -- | Type class for validating environment schemas.
 class (ExtractFields a, RecordParser a) => EnvSchema a where
@@ -26,3 +29,35 @@
   validateEnvWith transform = do
     envRaw <- getEnvRaw @a transform
     return $ parseRecord envRaw
+
+-- | Type class for validating environment schemas using witness types.
+--
+-- Uses the witness pattern to enhance parsing (e.g., default values) without
+-- wrapping final values in newtypes. Use @Config \'Dec@ for parsing and get
+-- @Config \'Res@ with unwrapped values as the result.
+--
+-- @
+-- data Config c = Config
+--   { psqlPort :: Column c (DefaultNum 5432 Word16) Word16
+--   , dbName   :: Column c (Solo String) String }
+--   deriving (Generic)
+--
+-- instance EnvSchemaW (Config \'Dec)
+--
+-- loadConfig :: IO (Either String (Config \'Res))
+-- loadConfig = validateEnvW \@(Config \'Dec)
+-- @
+class (ExtractFields a, RecordParserW a) => EnvSchemaW a where
+  -- | Validate environment variables, transforming field names from camelCase
+  -- to UPPER_SNAKE_CASE. Returns the parsed configuration with unwrapped values.
+  validateEnvW :: MonadIO m => m (Either String (RecordParsedType a))
+  validateEnvW = do
+    envRaw <- getEnvRawCamelCaseToUpperSnake @a
+    return $ parseRecordW @a envRaw
+
+  -- | Validate environment variables with a custom field name transformation
+  -- function. Returns the parsed configuration with unwrapped values.
+  validateEnvWWith :: MonadIO m => (String -> String) -> m (Either String (RecordParsedType a))
+  validateEnvWWith transform = do
+    envRaw <- getEnvRaw @a transform
+    return $ parseRecordW @a envRaw
diff --git a/src/Data/Env/ExtractFields.hs b/src/Data/Env/ExtractFields.hs
--- a/src/Data/Env/ExtractFields.hs
+++ b/src/Data/Env/ExtractFields.hs
@@ -13,7 +13,6 @@
   ExtractFields (..),
   extractFields,
   getEnvRaw,
-  getEnvRawLowerToUpperSnake,
   getEnvRawCamelCaseToUpperSnake,
 ) where
 
@@ -29,7 +28,7 @@
 
 -- | Type class for extracting field names from a record type.
 class ExtractFields a where
-  -- | Extract field names from a record type. It uses a 'Proxy' to avoid
+  -- | Extract field names from a record type. It uses a 'Data.Proxy.Proxy' to avoid
   -- needing a value of type $a$, and we recommend using 'extractFields'
   -- instead.
   extractFields' :: Proxy a -> [String]
@@ -48,13 +47,6 @@
   value <- lookupEnv (mapper field)
   return (field, fromMaybe "" value)
 {-# INLINE getEnvRaw #-}
-
--- | Retrieve environment variables based on field names, converting them to
--- upper case.
-getEnvRawLowerToUpperSnake :: forall a m. (MonadIO m, ExtractFields a)
-                           => m (Map String String)
-getEnvRawLowerToUpperSnake = getEnvRaw @a (map toUpper)
-{-# INLINE getEnvRawLowerToUpperSnake #-}
 
 -- | Retrieve environment variables based on field names, converting them from
 -- camel case (record field naming) to upper snake case (environment variable
diff --git a/src/Data/Env/RecordParser.hs b/src/Data/Env/RecordParser.hs
--- a/src/Data/Env/RecordParser.hs
+++ b/src/Data/Env/RecordParser.hs
@@ -46,12 +46,12 @@
 class GRecordParser f where
   gParseRecord :: Map String String -> Either String (f p)
 
--- | Handle metadata (wrapping fields in `M1`)
+-- | Handle metadata (wrapping fields in 'GHC.Generics.M1')
 instance GRecordParser f => GRecordParser (M1 D c f) where
   gParseRecord :: Map String String -> Either String (M1 D c f p)
   gParseRecord env = M1 <$> gParseRecord env
 
--- | Handle metadata (wrapping fields in `M1`)
+-- | Handle metadata (wrapping fields in 'GHC.Generics.M1')
 instance GRecordParser f => GRecordParser (M1 C c f) where
   gParseRecord :: Map String String -> Either String (M1 C c f p)
   gParseRecord env = M1 <$> gParseRecord env
diff --git a/src/Data/Env/RecordParserW.hs b/src/Data/Env/RecordParserW.hs
--- a/src/Data/Env/RecordParserW.hs
+++ b/src/Data/Env/RecordParserW.hs
@@ -82,14 +82,14 @@
 
   gParseRecord :: Map String String -> Either String ((GRecordParsedType f) r)
 
--- | Handle metadata (wrapping fields in `M1`)
+-- | Handle metadata (wrapping fields in 'GHC.Generics.M1')
 instance GRecordParserW f => GRecordParserW (M1 D c f) where
   type GRecordParsedType (M1 D c f) = M1 D c (GRecordParsedType f)
 
   gParseRecord :: Map String String -> Either String (GRecordParsedType (M1 D c f) r)
   gParseRecord env = M1 <$> gParseRecord @f env
 
--- | Handle metadata (wrapping fields in `M1`)
+-- | Handle metadata (wrapping fields in 'GHC.Generics.M1')
 instance GRecordParserW f => GRecordParserW (M1 C c f) where
   type GRecordParsedType (M1 C c f) = M1 C c (GRecordParsedType f)
 
diff --git a/src/Data/Env/TypeParserW.hs b/src/Data/Env/TypeParserW.hs
--- a/src/Data/Env/TypeParserW.hs
+++ b/src/Data/Env/TypeParserW.hs
@@ -1,5 +1,13 @@
 {-# LANGUAGE FunctionalDependencies #-}
 
+-- |
+-- Module: Data.Env.TypeParserW
+-- Description: Type class provides parsers for types parameterized by witness types.
+--
+-- This module provides the 'TypeParserW' type class, which allows parsing
+-- environment variables using witness types to control parsing behavior.
+-- This enables multiple parsing strategies for the same type by using
+-- different witness types.
 module Data.Env.TypeParserW (
   TypeParserW (..),
   Solo,
@@ -29,7 +37,7 @@
 class TypeParserW p a | p -> a where
   -- | Parse a value by its string representation using the witness type @p@.
   --
-  -- The 'Proxy' parameter carries the witness type information that determines
+  -- The 'Data.Proxy.Proxy' parameter carries the witness type information that determines
   -- how the parsing should be performed.
   parseTypeW :: Proxy p -> String -> Either String a
 
diff --git a/src/Data/Env/Witness/DefaultNum.hs b/src/Data/Env/Witness/DefaultNum.hs
--- a/src/Data/Env/Witness/DefaultNum.hs
+++ b/src/Data/Env/Witness/DefaultNum.hs
@@ -1,38 +1,7 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 
 {- |
-Module      : Data.Env.Witness.DefaultNum
-Description : Witness type for parsing numeric types with default values
-Copyright   : (c) 2025
-License     : MIT
-Maintainer  : maintainer@example.com
-Stability   : experimental
-
-This module provides the 'DefaultNum' witness type, which allows you to parse
-numeric environment variables with a type-level default value. When the environment
-variable is missing or empty, the default value is used instead.
-
-== Usage Example
-
-@
-import Data.Env.RecordParserW
-import Data.Env.Witness.DefaultNum
-import GHC.Generics
-
-data Config c = Config
-  { port :: Di (DefaultNum 5432) c Int
-  , timeout :: Di (DefaultNum 30) c Int
-  }
-  deriving (Generic)
-
--- When parsing with an empty map, defaults are used:
--- parseRecordW \@(Config 'Dec) mempty
--- => Right (Config 5432 30)
-
--- When values are provided, they override the defaults:
--- parseRecordW \@(Config 'Dec) (fromList [("port", "8080")])
--- => Right (Config 8080 30)
-@
+Witness type for parsing numeric types with type-level default values.
 -}
 module Data.Env.Witness.DefaultNum (
   DefaultNum,
diff --git a/src/Data/Env/Witness/DefaultString.hs b/src/Data/Env/Witness/DefaultString.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Env/Witness/DefaultString.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+{- |
+Witness type for parsing string types with type-level default values.
+-}
+module Data.Env.Witness.DefaultString (
+  DefaultString,
+) where
+
+import Data.Env.TypeParser
+import Data.Env.TypeParserW
+import Data.Proxy
+import Data.String
+import GHC.TypeLits
+
+-- | Witness type for parsing string types with default values.
+--
+-- The type parameter @s@ is a type-level symbol that specifies the
+-- default value. The type parameter @a@ is the string type to parse.
+--
+-- This witness allows you to specify a default string value that will be used
+-- when the environment variable is missing or empty.
+--
+-- ==== __Examples__
+--
+-- >>> parseTypeW (Proxy @(DefaultString "localhost" String)) ""
+-- Right "localhost"
+--
+-- >>> parseTypeW (Proxy @(DefaultString "localhost" String)) "example.com"
+-- Right "example.com"
+--
+data DefaultString (s :: Symbol) a
+
+-- | Parse a string value with a default fallback.
+--
+-- When the input string is empty, returns the default value specified by the
+-- type-level symbol @s@. Otherwise, attempts to parse the string as type @a@.
+instance (TypeParser a, IsString a, KnownSymbol s) => TypeParserW (DefaultString s a) a where
+  parseTypeW :: Proxy (DefaultString s a) -> String -> Either String a
+  parseTypeW _ ""  = Right (fromString $ symbolVal @s Proxy)
+  parseTypeW _ str = parseType str
+  {-# INLINE parseTypeW #-}
diff --git a/test/BasicTypeParserSpec.hs b/test/BasicTypeParserSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/BasicTypeParserSpec.hs
@@ -0,0 +1,151 @@
+module BasicTypeParserSpec ( spec ) where
+
+import Data.Either ( isLeft )
+import Data.Env.TypeParser
+import Data.Int ( Int8, Int16, Int32, Int64 )
+import Data.Text qualified as T
+import Data.Text.Lazy qualified as TL
+import Data.Word ( Word8, Word16, Word32, Word64 )
+import Test.Hspec
+
+spec :: Spec
+spec = describe "parseType" do
+  describe "parse String" do
+    it "parses non-empty string" do
+      parseType @String "hello" `shouldBe` Right "hello"
+    it "does not parse empty string" do
+      parseType @String "" `shouldSatisfy` isLeft
+  describe "parse strict Text" do
+    it "parses non-empty string" do
+      parseType @T.Text "hello" `shouldBe` Right "hello"
+    it "does not parse empty string" do
+      parseType @T.Text "" `shouldSatisfy` isLeft
+  describe "parse lazy Text" do
+    it "parses non-empty string" do
+      parseType @TL.Text "hello" `shouldBe` Right "hello"
+    it "does not parse empty string" do
+      parseType @TL.Text "" `shouldSatisfy` isLeft
+  describe "parse Bool" do
+    it "parses True" do
+      parseType @Bool "True" `shouldBe` Right True
+    it "parses False" do
+      parseType @Bool "False" `shouldBe` Right False
+    it "fails to parse other values" do
+      parseType @Bool "1" `shouldSatisfy` isLeft
+      parseType @Bool "true" `shouldSatisfy` isLeft
+      parseType @Bool "false" `shouldSatisfy` isLeft
+  describe "parse ()" do
+    it "parses ()" do
+      parseType @() "()" `shouldBe` Right ()
+    it "fails to parse other values" do
+      parseType @() "" `shouldSatisfy` isLeft
+      parseType @() "1" `shouldSatisfy` isLeft
+  describe "parse Int8" do
+    it "parses 0" do
+      parseType @Int8 "0" `shouldBe` Right 0
+    it "parses -128" do
+      parseType @Int8 "-128" `shouldBe` Right (-128)
+    it "parses 127" do
+      parseType @Int8 "127" `shouldBe` Right 127
+    it "fails to parse other values" do
+      parseType @Int8 "128" `shouldSatisfy` isLeft
+      parseType @Int8 "-129" `shouldSatisfy` isLeft
+      parseType @Int8 "1.0" `shouldSatisfy` isLeft
+  describe "parse Int16" do
+    it "parses 0" do
+      parseType @Int16 "0" `shouldBe` Right 0
+    it "parses -32768" do
+      parseType @Int16 "-32768" `shouldBe` Right (-32768)
+    it "parses 32767" do
+      parseType @Int16 "32767" `shouldBe` Right 32767
+    it "fails to parse other values" do
+      parseType @Int16 "32768" `shouldSatisfy` isLeft
+      parseType @Int16 "-32769" `shouldSatisfy` isLeft
+      parseType @Int16 "1.0" `shouldSatisfy` isLeft
+  describe "parse Int32" do
+    it "parses 0" do
+      parseType @Int32 "0" `shouldBe` Right 0
+    it "parses -2147483648" do
+      parseType @Int32 "-2147483648" `shouldBe` Right (-2147483648)
+    it "parses 2147483647" do
+      parseType @Int32 "2147483647" `shouldBe` Right 2147483647
+    it "fails to parse other values" do
+      parseType @Int32 "2147483648" `shouldSatisfy` isLeft
+      parseType @Int32 "-2147483649" `shouldSatisfy` isLeft
+      parseType @Int32 "1.0" `shouldSatisfy` isLeft
+  describe "parse Int64" do
+    it "parses 0" do
+      parseType @Int64 "0" `shouldBe` Right 0
+    it "parses -9223372036854775808" do
+      parseType @Int64 "-9223372036854775808" `shouldBe` Right (-9223372036854775808)
+    it "parses 9223372036854775807" do
+      parseType @Int64 "9223372036854775807" `shouldBe` Right 9223372036854775807
+    it "fails to parse other values" do
+      parseType @Int64 "9223372036854775808" `shouldSatisfy` isLeft
+      parseType @Int64 "-9223372036854775809" `shouldSatisfy` isLeft
+      parseType @Int64 "1.0" `shouldSatisfy` isLeft
+  describe "parse Int" do
+    it "parses 0" do
+      parseType @Int "0" `shouldBe` Right 0
+    it "parses min Int" do
+      parseType @Int (show (minBound :: Int)) `shouldBe` Right (minBound :: Int)
+    it "parses max Int" do
+      parseType @Int (show (maxBound :: Int)) `shouldBe` Right (maxBound :: Int)
+    it "fails to parse other values" do
+      parseType @Int (show ((fromIntegral (maxBound :: Int) :: Integer) + 1)) `shouldSatisfy` isLeft
+      parseType @Int (show ((fromIntegral (minBound :: Int) :: Integer) - 1)) `shouldSatisfy` isLeft
+      parseType @Int "1.0" `shouldSatisfy` isLeft
+  describe "parse Word8" do
+    it "parses 0" do
+      parseType @Word8 "0" `shouldBe` Right 0
+    it "parses 255" do
+      parseType @Word8 "255" `shouldBe` Right 255
+    it "fails to parse other values" do
+      parseType @Word8 "256" `shouldSatisfy` isLeft
+      parseType @Word8 "-1" `shouldSatisfy` isLeft
+      parseType @Word8 "1.0" `shouldSatisfy` isLeft
+  describe "parse Word16" do
+    it "parses 0" do
+      parseType @Word16 "0" `shouldBe` Right 0
+    it "parses 65535" do
+      parseType @Word16 "65535" `shouldBe` Right 65535
+    it "fails to parse other values" do
+      parseType @Word16 "65536" `shouldSatisfy` isLeft
+      parseType @Word16 "-1" `shouldSatisfy` isLeft
+      parseType @Word16 "1.0" `shouldSatisfy` isLeft
+  describe "parse Word32" do
+    it "parses 0" do
+      parseType @Word32 "0" `shouldBe` Right 0
+    it "parses 4294967295" do
+      parseType @Word32 "4294967295" `shouldBe` Right 4294967295
+    it "fails to parse other values" do
+      parseType @Word32 "4294967296" `shouldSatisfy` isLeft
+      parseType @Word32 "-1" `shouldSatisfy` isLeft
+      parseType @Word32 "1.0" `shouldSatisfy` isLeft
+  describe "parse Word64" do
+    it "parses 0" do
+      parseType @Word64 "0" `shouldBe` Right 0
+    it "parses 18446744073709551615" do
+      parseType @Word64 "18446744073709551615" `shouldBe` Right 18446744073709551615
+    it "fails to parse other values" do
+      parseType @Word64 "18446744073709551616" `shouldSatisfy` isLeft
+      parseType @Word64 "-1" `shouldSatisfy` isLeft
+      parseType @Word64 "1.0" `shouldSatisfy` isLeft
+  describe "parse Word" do
+    it "parses 0" do
+      parseType @Word "0" `shouldBe` Right 0
+    it "parses max Word" do
+      parseType @Word (show (maxBound :: Word)) `shouldBe` Right (maxBound :: Word)
+    it "fails to parse other values" do
+      parseType @Word (show ((fromIntegral (maxBound :: Word) :: Integer) + 1)) `shouldSatisfy` isLeft
+      parseType @Word "-1" `shouldSatisfy` isLeft
+      parseType @Word "1.0" `shouldSatisfy` isLeft
+  describe "parse Maybe" do
+    it "should success" do
+      parseType @(Maybe Int) "1" `shouldBe` Right (Just 1)
+      parseType @(Maybe Int) "" `shouldBe` Right Nothing
+      parseType @(Maybe String) "hello" `shouldBe` Right (Just "hello")
+      parseType @(Maybe String) "" `shouldBe` Right Nothing
+    it "should fail" do
+      parseType @(Maybe Int) "hello" `shouldSatisfy` isLeft
+      parseType @(Maybe Bool) "fALSE" `shouldSatisfy` isLeft
diff --git a/test/DefaultWitnessSpec.hs b/test/DefaultWitnessSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/DefaultWitnessSpec.hs
@@ -0,0 +1,96 @@
+module DefaultWitnessSpec ( spec ) where
+
+import Data.Either ( isLeft )
+import Data.Env.TypeParserW
+import Data.Env.Witness.DefaultNum
+import Data.Env.Witness.DefaultString
+import Data.Int ( Int16, Int32, Int64 )
+import Data.Proxy
+import Data.Text qualified as T
+import Data.Text.Lazy qualified as TL
+import Data.Word ( Word16, Word32, Word64 )
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "DefaultNum witness" do
+    describe "with Int" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 5432 Int)) "" `shouldBe` Right 5432
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultNum 5432 Int)) "8080" `shouldBe` Right 8080
+      it "parses negative value" do
+        parseTypeW (Proxy @(DefaultNum 5432 Int)) "-100" `shouldBe` Right (-100)
+      it "fails to parse invalid value" do
+        parseTypeW (Proxy @(DefaultNum 5432 Int)) "invalid" `shouldSatisfy` isLeft
+        parseTypeW (Proxy @(DefaultNum 5432 Int)) "1.5" `shouldSatisfy` isLeft
+
+    describe "with Word16" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 3000 Word16)) "" `shouldBe` Right 3000
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultNum 3000 Word16)) "8080" `shouldBe` Right 8080
+      it "fails to parse out-of-range value" do
+        parseTypeW (Proxy @(DefaultNum 3000 Word16)) "70000" `shouldSatisfy` isLeft
+        parseTypeW (Proxy @(DefaultNum 3000 Word16)) "-1" `shouldSatisfy` isLeft
+
+    describe "with Int16" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 100 Int16)) "" `shouldBe` Right 100
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultNum 100 Int16)) "32000" `shouldBe` Right 32000
+      it "fails to parse out-of-range value" do
+        parseTypeW (Proxy @(DefaultNum 100 Int16)) "40000" `shouldSatisfy` isLeft
+
+    describe "with Int32" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 1000 Int32)) "" `shouldBe` Right 1000
+      it "parses large value" do
+        parseTypeW (Proxy @(DefaultNum 1000 Int32)) "2000000000" `shouldBe` Right 2000000000
+
+    describe "with Int64" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 999 Int64)) "" `shouldBe` Right 999
+      it "parses very large value" do
+        parseTypeW (Proxy @(DefaultNum 999 Int64)) "9000000000000" `shouldBe` Right 9000000000000
+
+    describe "with Word32" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 500 Word32)) "" `shouldBe` Right 500
+      it "parses large value" do
+        parseTypeW (Proxy @(DefaultNum 500 Word32)) "4000000000" `shouldBe` Right 4000000000
+
+    describe "with Word64" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultNum 777 Word64)) "" `shouldBe` Right 777
+      it "parses very large value" do
+        parseTypeW (Proxy @(DefaultNum 777 Word64)) "18000000000000000000" `shouldBe` Right 18000000000000000000
+
+  describe "DefaultString witness" do
+    describe "with String" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultString "localhost" String)) "" `shouldBe` Right "localhost"
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultString "localhost" String)) "example.com" `shouldBe` Right "example.com"
+      it "handles special characters" do
+        parseTypeW (Proxy @(DefaultString "default" String)) "hello world!" `shouldBe` Right "hello world!"
+
+    describe "with strict Text" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultString "default-host" T.Text)) "" `shouldBe` Right "default-host"
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultString "default-host" T.Text)) "production.com" `shouldBe` Right "production.com"
+
+    describe "with lazy Text" do
+      it "returns default value for empty string" do
+        parseTypeW (Proxy @(DefaultString "lazy-default" TL.Text)) "" `shouldBe` Right "lazy-default"
+      it "parses non-empty valid value" do
+        parseTypeW (Proxy @(DefaultString "lazy-default" TL.Text)) "actual-value" `shouldBe` Right "actual-value"
+
+    describe "different default values" do
+      it "handles empty default string" do
+        parseTypeW (Proxy @(DefaultString "" String)) "" `shouldBe` Right ""
+      it "handles URL as default" do
+        parseTypeW (Proxy @(DefaultString "http://localhost:8080" String)) "" `shouldBe` Right "http://localhost:8080"
+      it "handles path as default" do
+        parseTypeW (Proxy @(DefaultString "/var/log/app.log" String)) "" `shouldBe` Right "/var/log/app.log"
diff --git a/test/EnumParserSpec.hs b/test/EnumParserSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/EnumParserSpec.hs
@@ -0,0 +1,43 @@
+module EnumParserSpec ( spec ) where
+
+import Data.Either ( isLeft )
+import Data.Env.EnumParser
+import Data.Env.TypeParser
+import Test.Hspec
+
+-- Test enum types
+data Gender = Male | Female
+  deriving (Show, Eq, Enum, Bounded)
+  deriving TypeParser via (EnumParser Gender)
+
+data LogLevel = Debug | Info | Warning | Error
+  deriving (Show, Eq, Enum, Bounded)
+  deriving TypeParser via (EnumParser LogLevel)
+
+spec :: Spec
+spec = describe "EnumParser" do
+  describe "parse Gender enum" do
+    it "parses Male" do
+      parseType @Gender "Male" `shouldBe` Right Male
+    it "parses Female" do
+      parseType @Gender "Female" `shouldBe` Right Female
+    it "fails to parse invalid value" do
+      parseType @Gender "Other" `shouldSatisfy` isLeft
+      parseType @Gender "male" `shouldSatisfy` isLeft
+      parseType @Gender "MALE" `shouldSatisfy` isLeft
+      parseType @Gender "" `shouldSatisfy` isLeft
+
+  describe "parse LogLevel enum" do
+    it "parses Debug" do
+      parseType @LogLevel "Debug" `shouldBe` Right Debug
+    it "parses Info" do
+      parseType @LogLevel "Info" `shouldBe` Right Info
+    it "parses Warning" do
+      parseType @LogLevel "Warning" `shouldBe` Right Warning
+    it "parses Error" do
+      parseType @LogLevel "Error" `shouldBe` Right Error
+    it "fails to parse invalid values" do
+      parseType @LogLevel "debug" `shouldSatisfy` isLeft
+      parseType @LogLevel "INFO" `shouldSatisfy` isLeft
+      parseType @LogLevel "Warn" `shouldSatisfy` isLeft
+      parseType @LogLevel "" `shouldSatisfy` isLeft
diff --git a/test/TypeParserSpec.hs b/test/TypeParserSpec.hs
deleted file mode 100644
--- a/test/TypeParserSpec.hs
+++ /dev/null
@@ -1,151 +0,0 @@
-module TypeParserSpec (spec) where
-
-import Data.Either ( isLeft )
-import Data.Env.TypeParser
-import Data.Int ( Int8, Int16, Int32, Int64 )
-import Data.Text qualified as T
-import Data.Text.Lazy qualified as TL
-import Data.Word ( Word8, Word16, Word32, Word64 )
-import Test.Hspec
-
-spec :: Spec
-spec = describe "parseType" do
-  describe "parse String" do
-    it "parses non-empty string" do
-      parseType @String "hello" `shouldBe` Right "hello"
-    it "does not parse empty string" do
-      parseType @String "" `shouldSatisfy` isLeft
-  describe "parse strict Text" do
-    it "parses non-empty string" do
-      parseType @T.Text "hello" `shouldBe` Right "hello"
-    it "does not parse empty string" do
-      parseType @T.Text "" `shouldSatisfy` isLeft
-  describe "parse lazy Text" do
-    it "parses non-empty string" do
-      parseType @TL.Text "hello" `shouldBe` Right "hello"
-    it "does not parse empty string" do
-      parseType @TL.Text "" `shouldSatisfy` isLeft
-  describe "parse Bool" do
-    it "parses True" do
-      parseType @Bool "True" `shouldBe` Right True
-    it "parses False" do
-      parseType @Bool "False" `shouldBe` Right False
-    it "fails to parse other values" do
-      parseType @Bool "1" `shouldSatisfy` isLeft
-      parseType @Bool "true" `shouldSatisfy` isLeft
-      parseType @Bool "false" `shouldSatisfy` isLeft
-  describe "parse ()" do
-    it "parses ()" do
-      parseType @() "()" `shouldBe` Right ()
-    it "fails to parse other values" do
-      parseType @() "" `shouldSatisfy` isLeft
-      parseType @() "1" `shouldSatisfy` isLeft
-  describe "parse Int8" do
-    it "parses 0" do
-      parseType @Int8 "0" `shouldBe` Right 0
-    it "parses -128" do
-      parseType @Int8 "-128" `shouldBe` Right (-128)
-    it "parses 127" do
-      parseType @Int8 "127" `shouldBe` Right 127
-    it "fails to parse other values" do
-      parseType @Int8 "128" `shouldSatisfy` isLeft
-      parseType @Int8 "-129" `shouldSatisfy` isLeft
-      parseType @Int8 "1.0" `shouldSatisfy` isLeft
-  describe "parse Int16" do
-    it "parses 0" do
-      parseType @Int16 "0" `shouldBe` Right 0
-    it "parses -32768" do
-      parseType @Int16 "-32768" `shouldBe` Right (-32768)
-    it "parses 32767" do
-      parseType @Int16 "32767" `shouldBe` Right 32767
-    it "fails to parse other values" do
-      parseType @Int16 "32768" `shouldSatisfy` isLeft
-      parseType @Int16 "-32769" `shouldSatisfy` isLeft
-      parseType @Int16 "1.0" `shouldSatisfy` isLeft
-  describe "parse Int32" do
-    it "parses 0" do
-      parseType @Int32 "0" `shouldBe` Right 0
-    it "parses -2147483648" do
-      parseType @Int32 "-2147483648" `shouldBe` Right (-2147483648)
-    it "parses 2147483647" do
-      parseType @Int32 "2147483647" `shouldBe` Right 2147483647
-    it "fails to parse other values" do
-      parseType @Int32 "2147483648" `shouldSatisfy` isLeft
-      parseType @Int32 "-2147483649" `shouldSatisfy` isLeft
-      parseType @Int32 "1.0" `shouldSatisfy` isLeft
-  describe "parse Int64" do
-    it "parses 0" do
-      parseType @Int64 "0" `shouldBe` Right 0
-    it "parses -9223372036854775808" do
-      parseType @Int64 "-9223372036854775808" `shouldBe` Right (-9223372036854775808)
-    it "parses 9223372036854775807" do
-      parseType @Int64 "9223372036854775807" `shouldBe` Right 9223372036854775807
-    it "fails to parse other values" do
-      parseType @Int64 "9223372036854775808" `shouldSatisfy` isLeft
-      parseType @Int64 "-9223372036854775809" `shouldSatisfy` isLeft
-      parseType @Int64 "1.0" `shouldSatisfy` isLeft
-  describe "parse Int" do
-    it "parses 0" do
-      parseType @Int "0" `shouldBe` Right 0
-    it "parses min Int" do
-      parseType @Int (show (minBound :: Int)) `shouldBe` Right (minBound :: Int)
-    it "parses max Int" do
-      parseType @Int (show (maxBound :: Int)) `shouldBe` Right (maxBound :: Int)
-    it "fails to parse other values" do
-      parseType @Int (show ((fromIntegral (maxBound :: Int) :: Integer) + 1)) `shouldSatisfy` isLeft
-      parseType @Int (show ((fromIntegral (minBound :: Int) :: Integer) - 1)) `shouldSatisfy` isLeft
-      parseType @Int "1.0" `shouldSatisfy` isLeft
-  describe "parse Word8" do
-    it "parses 0" do
-      parseType @Word8 "0" `shouldBe` Right 0
-    it "parses 255" do
-      parseType @Word8 "255" `shouldBe` Right 255
-    it "fails to parse other values" do
-      parseType @Word8 "256" `shouldSatisfy` isLeft
-      parseType @Word8 "-1" `shouldSatisfy` isLeft
-      parseType @Word8 "1.0" `shouldSatisfy` isLeft
-  describe "parse Word16" do
-    it "parses 0" do
-      parseType @Word16 "0" `shouldBe` Right 0
-    it "parses 65535" do
-      parseType @Word16 "65535" `shouldBe` Right 65535
-    it "fails to parse other values" do
-      parseType @Word16 "65536" `shouldSatisfy` isLeft
-      parseType @Word16 "-1" `shouldSatisfy` isLeft
-      parseType @Word16 "1.0" `shouldSatisfy` isLeft
-  describe "parse Word32" do
-    it "parses 0" do
-      parseType @Word32 "0" `shouldBe` Right 0
-    it "parses 4294967295" do
-      parseType @Word32 "4294967295" `shouldBe` Right 4294967295
-    it "fails to parse other values" do
-      parseType @Word32 "4294967296" `shouldSatisfy` isLeft
-      parseType @Word32 "-1" `shouldSatisfy` isLeft
-      parseType @Word32 "1.0" `shouldSatisfy` isLeft
-  describe "parse Word64" do
-    it "parses 0" do
-      parseType @Word64 "0" `shouldBe` Right 0
-    it "parses 18446744073709551615" do
-      parseType @Word64 "18446744073709551615" `shouldBe` Right 18446744073709551615
-    it "fails to parse other values" do
-      parseType @Word64 "18446744073709551616" `shouldSatisfy` isLeft
-      parseType @Word64 "-1" `shouldSatisfy` isLeft
-      parseType @Word64 "1.0" `shouldSatisfy` isLeft
-  describe "parse Word" do
-    it "parses 0" do
-      parseType @Word "0" `shouldBe` Right 0
-    it "parses max Word" do
-      parseType @Word (show (maxBound :: Word)) `shouldBe` Right (maxBound :: Word)
-    it "fails to parse other values" do
-      parseType @Word (show ((fromIntegral (maxBound :: Word) :: Integer) + 1)) `shouldSatisfy` isLeft
-      parseType @Word "-1" `shouldSatisfy` isLeft
-      parseType @Word "1.0" `shouldSatisfy` isLeft
-  describe "parse Maybe" do
-    it "should success" do
-      parseType @(Maybe Int) "1" `shouldBe` Right (Just 1)
-      parseType @(Maybe Int) "" `shouldBe` Right Nothing
-      parseType @(Maybe String) "hello" `shouldBe` Right (Just "hello")
-      parseType @(Maybe String) "" `shouldBe` Right Nothing
-    it "should fail" do
-      parseType @(Maybe Int) "hello" `shouldSatisfy` isLeft
-      parseType @(Maybe Bool) "fALSE" `shouldSatisfy` isLeft
