diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## [1.2.0](https://github.com/haskell-github-trust/effectful-config-file/compare/1.1.1...1.2.0) (2026-10-26)
+
+### Features
+
+* add `requireConfigValue` helper ([8ba0c9e](https://github.com/haskell-github-trust/effectful-config-file/commit/8ba0c9e9e09ac829f3e64f44ae519d81c137312b))
+
 ## [1.1.1](https://github.com/haskell-github-trust/effectful-config-file/compare/1.1.0...1.1.1) (2026-10-25)
 
 ## [1.1.0](https://github.com/haskell-github-trust/effectful-config-file/compare/1.0.2...1.1.0) (2026-10-25)
diff --git a/effectful-config-file.cabal b/effectful-config-file.cabal
--- a/effectful-config-file.cabal
+++ b/effectful-config-file.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           effectful-config-file
-version:        0.1.1.1
+version:        0.1.2.0
 synopsis:       Config-file effect for effectful
 description:    Config-file parser and utilities to use in conjunction with effectful
 category:       Configuration
diff --git a/src/Data/Config.hs b/src/Data/Config.hs
--- a/src/Data/Config.hs
+++ b/src/Data/Config.hs
@@ -7,6 +7,8 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeOperators #-}
 
+-- | Configuration values that can be literals or references to
+-- environment variables, files or secrets, and their TOML decoding.
 module Data.Config (
   ConfigParse (..),
   ConfigFetch (..),
diff --git a/src/Data/Error/Config.hs b/src/Data/Error/Config.hs
--- a/src/Data/Error/Config.hs
+++ b/src/Data/Error/Config.hs
@@ -1,8 +1,11 @@
+-- | Configuration decoding errors.
 module Data.Error.Config (
   ConfigError (..),
+  ConfigNotFound (..),
 ) where
 
 import Data.List (intercalate)
+import Data.Text (Text)
 import Toml (DecodeError, prettyDecodeError)
 
 -- | Errors raised while decoding a TOML configuration.
@@ -18,3 +21,9 @@
 
 instance Show ConfigError where
   show (ConfigError es) = "config error: " <> intercalate "; " (map prettyDecodeError es)
+
+-- | A configuration value whose @env://@ or @file://@ source is absent.
+--
+-- @since 0.1.2.0
+newtype ConfigNotFound = ConfigNotFound Text
+  deriving (Eq, Show)
diff --git a/src/Effectful/Config.hs b/src/Effectful/Config.hs
--- a/src/Effectful/Config.hs
+++ b/src/Effectful/Config.hs
@@ -5,18 +5,21 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeOperators #-}
 
+-- | Effect to read a TOML configuration and lazily resolve its values.
 module Effectful.Config (
   Config,
   getConfig,
   getConfigValue,
   readConfig,
   readConfig',
+  requireConfigValue,
 ) where
 
 import Control.Concurrent.MVar (MVar, modifyMVar, readMVar)
+import Control.Exception (IOException)
 import Control.Lens (ALens', cloneLens, (&), (.~), (^.))
 import Data.Config (ConfigFetch (fetch), ConfigParse, ConfigValue (..))
-import Data.Error.Config (ConfigError (..))
+import Data.Error.Config (ConfigError (..), ConfigNotFound (..))
 import Data.Text (Text)
 import qualified Data.Text as T (pack)
 import qualified Data.Text.IO as T (readFile)
@@ -24,6 +27,7 @@
 import Effectful.Config.Log (Log, logWarn)
 import Effectful.Config.Secrets (Secrets)
 import Effectful.Error.Static (Error, throwError)
+import Effectful.Exception (catch)
 import Effectful.Reader.Static (Reader, ask)
 import OpenTelemetry.Trace.Core (
   addAttribute,
@@ -78,6 +82,24 @@
               [ printf "tried to get raw config value from (%s)" (show v)
               , "this should be impossible and is most likely a bug in your ConfigFetch instance"
               ]
+
+-- | 'getConfigValue', raising t'ConfigNotFound' when an @env://@ or @file://@
+-- source is absent instead of letting the 'IOException' escape.
+--
+-- @since 0.1.2.0
+requireConfigValue
+  :: IOE :> es
+  => Config c :> es
+  => Log :> es
+  => Secrets :> es
+  => Error ConfigNotFound :> es
+  => ConfigParse a
+  => Show a
+  => Text
+  -> ALens' c (ConfigValue a)
+  -> Eff es a
+requireConfigValue name lens =
+  getConfigValue lens `catch` \(_ :: IOException) -> throwError (ConfigNotFound name)
 
 -- | Decode a configuration from TOML text. Warnings are ignored.
 --
diff --git a/src/Effectful/Config/Log.hs b/src/Effectful/Config/Log.hs
--- a/src/Effectful/Config/Log.hs
+++ b/src/Effectful/Config/Log.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
 
+-- | Minimal logging effect used by the configuration library.
 module Effectful.Config.Log (
   Log,
   runLog,
diff --git a/src/Effectful/Config/Secrets.hs b/src/Effectful/Config/Secrets.hs
--- a/src/Effectful/Config/Secrets.hs
+++ b/src/Effectful/Config/Secrets.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
 
+-- | Effect to resolve secrets referenced by configuration values.
 module Effectful.Config.Secrets (
   Secrets (..),
   getSecret,
diff --git a/test/Effectful/ConfigSpec.hs b/test/Effectful/ConfigSpec.hs
--- a/test/Effectful/ConfigSpec.hs
+++ b/test/Effectful/ConfigSpec.hs
@@ -13,11 +13,11 @@
 import Control.Lens (makeLenses, (^.))
 import Data.Bifunctor (Bifunctor (..), first)
 import Data.Config (ConfigValue (..), withSection)
-import Data.Error.Config (ConfigError (..))
+import Data.Error.Config (ConfigError (..), ConfigNotFound (..))
 import Data.Text (Text)
 import qualified Data.Text as T (isInfixOf, pack, unlines, unpack)
 import Effectful (runEff)
-import Effectful.Config (getConfigValue, readConfig, readConfig')
+import Effectful.Config (getConfigValue, readConfig, readConfig', requireConfigValue)
 import Effectful.Config.Log (runLog)
 import Effectful.Config.Secrets (SecretsError, runSecretsError)
 import Effectful.Error.Static (runErrorNoCallStack)
@@ -216,6 +216,20 @@
 
       cfg <- timeout 1000000 (readMVar p)
       cfg `shouldBe` Just (FileConfig (File "/nonexistent"))
+
+  describe "requireConfigValue" $ do
+    it "throws ConfigNotFound on a missing file" $ do
+      p <- newMVar (FileConfig (File "/nonexistent"))
+      result <-
+        runEff
+          . runErrorNoCallStack @ConfigNotFound
+          . runErrorNoCallStack @SecretsError
+          . runSecretsError
+          . runLog silentLog
+          . runReader p
+          $ requireConfigValue "file_val" file_val
+
+      result `shouldBe` Left (ConfigNotFound "file_val")
 
   describe "readConfig' OTel logging" $ do
     it "emits parse warnings as OTel log records" $ withSystemTempFile "effectful-config" $ \path h -> do
