diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for dataframe
 
+## 3.1.0.0
+* Fix toSeparated's handling of nulls
+* Fix build failing.
+
 ## 3.0.0.0
 
 A breaking release that removes internal functions from the public API.
diff --git a/dataframe.cabal b/dataframe.cabal
--- a/dataframe.cabal
+++ b/dataframe.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.4
 name:               dataframe
-version:            3.0.0.0
+version:            3.1.0.0
 synopsis: A fast, safe, and intuitive DataFrame library.
 
 description: A fast, safe, and intuitive DataFrame library for exploratory data analysis.
@@ -128,18 +128,18 @@
                         DataFrame.Typed.Record,
                         DataFrame.Typed.Generic
     build-depends:    base >= 4 && <5,
-                      dataframe-core >= 2.0 && < 2.1,
-                      dataframe-json >= 1.1 && < 1.2,
-                      dataframe-expr-serializer >= 1.1 && < 1.2,
-                      dataframe-operations >= 2.0 && < 2.1,
-                      dataframe-parsing >= 2.0 && < 2.1,
-                      dataframe-viz >= 1.1 && < 1.2,
-                      dataframe-learn >= 2.0 && < 2.1
+                      dataframe-core >= 2.1 && < 2.2,
+                      dataframe-json >= 1.2 && < 1.3,
+                      dataframe-expr-serializer >= 1.2 && < 1.3,
+                      dataframe-operations >= 2.1 && < 2.2,
+                      dataframe-parsing >= 2.1 && < 2.2,
+                      dataframe-viz >= 1.2 && < 1.3,
+                      dataframe-learn >= 2.1 && < 2.2
 
     if !flag(no-csv)
         reexported-modules: DataFrame.IO.CSV,
                             DataFrame.Typed.IO.CSV
-        build-depends:   dataframe-csv >= 2.0 && < 2.1
+        build-depends:   dataframe-csv >= 2.1 && < 2.2
         cpp-options:     -DWITH_CSV
 
     if !flag(no-parquet)
@@ -157,7 +157,7 @@
                             DataFrame.IO.Parquet.Time,
                             DataFrame.IO.Utils.RandomAccess,
                             DataFrame.Typed.IO.Parquet
-        build-depends:   dataframe-parquet >= 1.2 && < 1.3
+        build-depends:   dataframe-parquet >= 1.3 && < 1.4
         cpp-options:     -DWITH_PARQUET
 
     -- The lazy executor calls both CSV and Parquet readers directly, so
@@ -167,21 +167,21 @@
                             DataFrame.Lazy.IO.Binary,
                             DataFrame.Lazy.IO.CSV,
                             DataFrame.Typed.Lazy
-        build-depends:   dataframe-lazy >= 2.0 && < 2.1
+        build-depends:   dataframe-lazy >= 2.1 && < 2.2
         cpp-options:     -DWITH_LAZY
 
     if !flag(no-th)
-        build-depends:   dataframe-th >= 2.0 && < 2.1
+        build-depends:   dataframe-th >= 2.1 && < 2.2
         cpp-options:     -DWITH_TH
         exposed-modules: DataFrame.TH,
                          DataFrame.Typed.TH
 
     if !flag(no-th) && !flag(no-csv)
-        build-depends:   dataframe-csv-th >= 1.1 && < 1.2
+        build-depends:   dataframe-csv-th >= 1.2 && < 1.3
         cpp-options:     -DWITH_CSV_TH
 
     if !flag(no-th) && !flag(no-parquet)
-        build-depends:   dataframe-parquet-th >= 1.1 && < 1.2
+        build-depends:   dataframe-parquet-th >= 1.2 && < 1.3
         cpp-options:     -DWITH_PARQUET_TH
 
     hs-source-dirs:   src
@@ -203,14 +203,14 @@
         buildable: False
     build-depends:
         base        >= 4   && < 5,
-        dataframe-core:internal >= 2.0 && < 2.1,
-        dataframe-expr-serializer >= 1.1 && < 1.2,
-        dataframe-csv >= 2.0 && < 2.1,
-        dataframe-json >= 1.1 && < 1.2,
-        dataframe-lazy >= 2.0 && < 2.1,
-        dataframe-operations >= 2.0 && < 2.1,
-        dataframe-parquet >= 1.2 && < 1.3,
-        dataframe-parsing >= 2.0 && < 2.1,
+        dataframe-core >= 2.1 && < 2.2,
+        dataframe-expr-serializer >= 1.2 && < 1.3,
+        dataframe-csv >= 2.1 && < 2.2,
+        dataframe-json >= 1.2 && < 1.3,
+        dataframe-lazy >= 2.1 && < 2.2,
+        dataframe-operations >= 2.1 && < 2.2,
+        dataframe-parquet >= 1.3 && < 1.4,
+        dataframe-parsing >= 2.1 && < 2.2,
         text        >= 2.1 && < 3,
         aeson       >= 0.11 && < 3,
         bytestring  >= 0.11 && < 0.14,
@@ -223,8 +223,8 @@
     import: warnings
     main-is: Benchmark.hs
     build-depends:    base >= 4 && < 5,
-                      dataframe >= 3.0 && < 3.1,
-                      dataframe-operations >= 2.0 && < 2.1,
+                      dataframe >= 3.1 && < 3.2,
+                      dataframe-operations >= 2.1 && < 2.2,
                       random >= 1 && < 2,
                       time >= 1.12 && < 2,
                       vector >= 0.13 && < 0.15,
@@ -236,10 +236,10 @@
     import: warnings
     main-is: Synthesis.hs
     build-depends:    base >= 4 && < 5,
-                      dataframe >= 3.0 && < 3.1,
-                      dataframe-core >= 2.0 && < 2.1,
-                      dataframe-learn >= 2.0 && < 2.1,
-                      dataframe-operations >= 2.0 && < 2.1,
+                      dataframe >= 3.1 && < 3.2,
+                      dataframe-core >= 2.1 && < 2.2,
+                      dataframe-learn >= 2.1 && < 2.2,
+                      dataframe-operations >= 2.1 && < 2.2,
                       random >= 1 && < 2,
                       text >= 2.1 && < 3
     hs-source-dirs:   app
@@ -270,10 +270,10 @@
     build-depends:    base >= 4 && < 5,
                       bytestring >= 0.11 && < 0.14,
                       containers >= 0.6.7 && < 0.10,
-                      dataframe >= 3.0 && < 3.1,
-                      dataframe-core >= 2.0 && < 2.1,
-                      dataframe-lazy >= 2.0 && < 2.1,
-                      dataframe-parsing >= 2.0 && < 2.1,
+                      dataframe >= 3.1 && < 3.2,
+                      dataframe-core >= 2.1 && < 2.2,
+                      dataframe-lazy >= 2.1 && < 2.2,
+                      dataframe-parsing >= 2.1 && < 2.2,
                       directory >= 1.3.0.0 && < 2,
                       random >= 1 && < 2,
                       text >= 2.1 && < 3,
@@ -291,9 +291,9 @@
                    criterion >= 1 && < 2,
                    deepseq >= 1.4 && < 2,
                    process >= 1.6 && < 2,
-                   dataframe >= 3.0 && < 3.1,
-                   dataframe-core:internal >= 2.0 && < 2.1,
-                   dataframe-operations >= 2.0 && < 2.1,
+                   dataframe >= 3.1 && < 3.2,
+                   dataframe-core >= 2.1 && < 2.2,
+                   dataframe-operations >= 2.1 && < 2.2,
                    random >= 1 && < 2,
     default-language: Haskell2010
     ghc-options:
@@ -376,19 +376,17 @@
     build-depends:  base >= 4 && < 5,
                     aeson >= 0.11.0.0 && < 3,
                     bytestring >= 0.11 && < 0.14,
-                    dataframe >= 3.0 && < 3.1,
-                    dataframe-core >= 2.0 && < 2.1,
-                    dataframe-core:internal >= 2.0 && < 2.1,
-                    dataframe-csv >= 2.0 && < 2.1,
-                    dataframe-expr-serializer >= 1.1 && < 1.2,
-                    dataframe-json >= 1.1 && < 1.2,
-                    dataframe-lazy >= 2.0 && < 2.1,
-                    dataframe-learn >= 2.0 && < 2.1,
-                    dataframe-operations >= 2.0 && < 2.1,
-                    dataframe-operations:internal >= 2.0 && < 2.1,
-                    dataframe-parquet >= 1.2 && < 1.3,
-                    dataframe-parsing >= 2.0 && < 2.1,
-                    dataframe-parsing:internal >= 2.0 && < 2.1,
+                    dataframe >= 3.1 && < 3.2,
+                    dataframe-core >= 2.1 && < 2.2,
+                    dataframe-core >= 2.1 && < 2.2,
+                    dataframe-csv >= 2.1 && < 2.2,
+                    dataframe-expr-serializer >= 1.2 && < 1.3,
+                    dataframe-json >= 1.2 && < 1.3,
+                    dataframe-lazy >= 2.1 && < 2.2,
+                    dataframe-learn >= 2.1 && < 2.2,
+                    dataframe-operations >= 2.1 && < 2.2,
+                    dataframe-parquet >= 1.3 && < 1.4,
+                    dataframe-parsing >= 2.1 && < 2.2,
                     HUnit >= 1.6 && < 1.8,
                     QuickCheck >= 2 && < 3,
                     random-shuffle >= 0.0.4 && < 1,
@@ -413,9 +411,9 @@
     other-modules: Internal.PackedText
     build-depends:  base >= 4 && < 5,
                     bytestring >= 0.11 && < 0.14,
-                    dataframe >= 3.0 && < 3.1,
-                    dataframe-core:internal >= 2.0 && < 2.1,
-                    dataframe-operations >= 2.0 && < 2.1,
+                    dataframe >= 3.1 && < 3.2,
+                    dataframe-core >= 2.1 && < 2.2,
+                    dataframe-operations >= 2.1 && < 2.2,
                     HUnit >= 1.6 && < 1.8,
                     text >= 2.1 && < 3,
                     vector >= 0.13 && < 0.15
diff --git a/tests/Operations/WriteCsv.hs b/tests/Operations/WriteCsv.hs
--- a/tests/Operations/WriteCsv.hs
+++ b/tests/Operations/WriteCsv.hs
@@ -21,6 +21,16 @@
         expected = "name,age\nAlice,30\nBob,25\nCharlie,35\n"
     assertEqual "basic toCsv" expected (toCsv df)
 
+toCsvWithNulls :: Test
+toCsvWithNulls = TestLabel "toCsv_withNulls" $ TestCase $ do
+    let df =
+            D.fromNamedColumns
+                [ ("name", DI.fromList @(Maybe T.Text) [Nothing, Just "Alice", Just "Bob", Just "Charlie"])
+                , ("age", DI.fromList @(Maybe Int) [Just 30, Nothing, Just 25, Just 35])
+                ]
+        expected = "name,age\nnull,30\nAlice,null\nBob,25\nCharlie,35\n"
+    assertEqual "toCsv with nulls" expected (toCsv df)
+
 -- Empty DataFrame produces empty text
 toCsvEmpty :: Test
 toCsvEmpty =
@@ -80,6 +90,7 @@
 tests :: [Test]
 tests =
     [ toCsvBasic
+    , toCsvWithNulls
     , toCsvEmpty
     , toSeparatedTab
     , toCsvDouble
