diff --git a/dataframe-csv-th.cabal b/dataframe-csv-th.cabal
--- a/dataframe-csv-th.cabal
+++ b/dataframe-csv-th.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               dataframe-csv-th
-version:            1.3.0.0
+version:            1.4.0.0
 synopsis:           CSV-file-based Template Haskell splices for the dataframe ecosystem.
 description:
     Splices that read CSV files at compile time and emit per-column
@@ -30,8 +30,9 @@
                         DataFrame.TH.CSV
                         DataFrame.Typed.TH.CSV
     build-depends:      base >= 4 && < 5,
+                        bytestring >= 0.11 && < 0.14,
                         dataframe-csv >= 2.3 && < 2.4,
-                        dataframe-th >= 2.2 && < 2.3,
+                        dataframe-th >= 2.3 && < 2.4,
                         template-haskell >= 2.0 && < 3
     hs-source-dirs:     src
     default-language:   Haskell2010
diff --git a/src/DataFrame/TH/CSV.hs b/src/DataFrame/TH/CSV.hs
--- a/src/DataFrame/TH/CSV.hs
+++ b/src/DataFrame/TH/CSV.hs
@@ -1,27 +1,25 @@
 {-# LANGUAGE FlexibleContexts #-}
-
-{- |
-Module      : DataFrame.TH.CSV
-License     : MIT
+{-# LANGUAGE OverloadedStrings #-}
 
-CSV-file-based 'DataFrame.TH' splices. Splits out the CSV ingest path so
-@dataframe-th@ stays IO-agnostic.
--}
+-- | Splices that derive column bindings from a CSV file.
 module DataFrame.TH.CSV (
     declareColumnsFromCsvFile,
     declareColumnsFromCsvWithOpts,
+    deriveSchemaValuesFromCsvFile,
+    deriveSchemaValuesFromCsvWithOpts,
 ) where
 
+import qualified Data.ByteString as BS
+
 import Control.Monad.IO.Class (liftIO)
+import Data.Maybe (fromMaybe)
 import Language.Haskell.TH
+import System.IO (IOMode (ReadMode), withFile)
 
 import qualified DataFrame.IO.CSV as CSV
-import DataFrame.TH.Records (declareColumns)
+import DataFrame.TH.Records (declareColumns, declareSchemaValues)
 
-{- | Splice a binding for every column of the 'DataFrame' read from a CSV
-file. Each binding has type @Expr T@ where @T@ is the inferred column
-type.
--}
+-- | Splice an @Expr T@ binding for every column of a CSV file.
 declareColumnsFromCsvFile :: String -> DecsQ
 declareColumnsFromCsvFile path = do
     df <-
@@ -37,3 +35,31 @@
 declareColumnsFromCsvWithOpts opts path = do
     df <- liftIO (CSV.readSeparated opts path)
     declareColumns df
+
+deriveSchemaValuesFromCsvFile :: String -> String -> DecsQ
+deriveSchemaValuesFromCsvFile =
+    deriveSchemaValuesFromCsvWithOpts
+        CSV.defaultReadOptions{CSV.numRowsToRead = Just 100}
+
+deriveSchemaValuesFromCsvWithOpts ::
+    CSV.ReadOptions -> String -> String -> DecsQ
+deriveSchemaValuesFromCsvWithOpts opts prefix path = do
+    df <- liftIO $ do
+        sample <- readCsvSample sampleWindowBytes path
+        CSV.decodeSeparatedStrict opts sample
+    declareSchemaValues prefix df
+
+sampleWindowBytes :: Int
+sampleWindowBytes = 1024 * 1024
+
+readCsvSample :: Int -> FilePath -> IO BS.ByteString
+readCsvSample n path = do
+    chunk <- withFile path ReadMode (\h -> BS.hGet h (n + 1))
+    let stripBom b = fromMaybe b (BS.stripPrefix "\xEF\xBB\xBF" b)
+    pure . stripBom $
+        if BS.length chunk <= n
+            then chunk
+            else
+                let window = BS.take n chunk
+                 in maybe window (\i -> BS.take (i + 1) window) $
+                        BS.elemIndexEnd 0x0a window
diff --git a/src/DataFrame/Typed/TH/CSV.hs b/src/DataFrame/Typed/TH/CSV.hs
--- a/src/DataFrame/Typed/TH/CSV.hs
+++ b/src/DataFrame/Typed/TH/CSV.hs
@@ -1,10 +1,4 @@
-{- |
-Module      : DataFrame.Typed.TH.CSV
-License     : MIT
-
-CSV-file-based typed-schema derivation. Splits the CSV path out of
-@DataFrame.Typed.TH@ so it can live in @dataframe-csv-th@.
--}
+-- | Derives a typed schema from a CSV file.
 module DataFrame.Typed.TH.CSV (
     deriveSchemaFromCsvFile,
     deriveSchemaFromCsvFileWith,
