diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,27 @@
 
 `pandoc-lua-marshal` uses [PVP Versioning][].
 
+## 0.3.3
+
+Released 2026-11-11.
+
+  * Allow singleton TableBody as list of table bodies.
+
+    It's now possible to pass a bare TableBody value whenever a
+    list of TableBody elements is expected. This should simplify
+    the construction of table values.
+
+  * Allow rows to be given as simple lists of cells instead of
+    always having to use the `Row` constructor function.
+
+  * Fixed the failure method in `peekAttr`: peeking failures are
+    now kept within the *Peek* monad. Previously, this function
+    would raise Lua exceptions in certain cases. The change
+    un-breaks the use of this function within the *Peek* monad and
+    will result in better error messages in some cases.
+
+  * Raise upper bound for aeson, QuickCheck
+
 ## 0.3.2.1
 
 Released 2026-01-09.
diff --git a/pandoc-lua-marshal.cabal b/pandoc-lua-marshal.cabal
--- a/pandoc-lua-marshal.cabal
+++ b/pandoc-lua-marshal.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                pandoc-lua-marshal
-version:             0.3.2.1
+version:             0.3.3
 synopsis:            Use pandoc types in Lua
 description:         This package provides functions to marshal and unmarshal
                      pandoc document types to and from Lua.
@@ -45,7 +45,7 @@
 
 common common-options
   build-depends:       base                  >= 4.12     && < 5
-                     , aeson                 >= 1.5      && < 2.3
+                     , aeson                 >= 1.5      && < 2.4
                      , bytestring            >= 0.10     && < 0.13
                      , containers            >= 0.6      && < 0.9
                      , exceptions            >= 0.8      && < 0.11
@@ -109,7 +109,7 @@
   hs-source-dirs:      test
   main-is:             test-pandoc-lua-marshal.hs
   build-depends:       pandoc-lua-marshal
-                     , QuickCheck           >= 2.4     && < 2.17
+                     , QuickCheck           >= 2.4     && < 2.20
                      , tasty                >= 0.11
                      , tasty-hunit          >= 0.9
                      , tasty-lua            >= 1.0
diff --git a/src/Text/Pandoc/Lua/Marshal/Attr.hs b/src/Text/Pandoc/Lua/Marshal/Attr.hs
--- a/src/Text/Pandoc/Lua/Marshal/Attr.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Attr.hs
@@ -204,7 +204,7 @@
   TypeString -> (,[],[]) <$!> peekText idx -- treat string as ID
   TypeUserdata -> peekUD typeAttr idx
   TypeTable -> peekAttrTable idx
-  x -> liftLua . failLua $ "Cannot get Attr from " ++ show x
+  x -> fail $ "Cannot get Attr from " <> show x
 
 -- | Helper function which gets an Attr from a Lua table.
 peekAttrTable :: LuaError e => Peeker e Attr
diff --git a/src/Text/Pandoc/Lua/Marshal/Block.hs b/src/Text/Pandoc/Lua/Marshal/Block.hs
--- a/src/Text/Pandoc/Lua/Marshal/Block.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Block.hs
@@ -188,7 +188,7 @@
       (pushPandocList pushTableBody, \case
           Table _ _ _ _ bs _ -> Actual bs
           _                  -> Absent)
-      (peekList peekTableBodyFuzzy, \case
+      (peekTableBodies, \case
           Table attr c cs h _ f -> Actual . (\bs -> Table attr c cs h bs f)
           _                     -> const Absent)
   , possibleProperty "caption" "element caption"
@@ -455,8 +455,8 @@
     <#> parameter (peekList peekColSpec) "{ColSpec,...}" "colspecs"
                   "column alignments and widths"
     <#> parameter peekTableHead "TableHead" "head" "table head"
-    <#> parameter (peekList peekTableBodyFuzzy) "{TableBody,...}" "bodies"
-                  "table bodies"
+    <#> parameter peekTableBodies "TableBody|{TableBody,...}" "bodies"
+          "table bodies"
     <#> parameter peekTableFoot "TableFoot" "foot" "table foot"
     <#> optAttrParam
     =#> blockResult "Table element"
@@ -471,6 +471,11 @@
 
   optAttrParam = opt (parameter peekAttr "Attr" "attr" "element attributes")
 
+peekTableBodies :: LuaError e => Peeker e [TableBody]
+peekTableBodies = choice
+  [ fmap (:[]) . peekTableBodyFuzzy
+  , peekList peekTableBodyFuzzy
+  ]
 
 -- | Constructor for a list of `Block` values.
 mkBlocks :: LuaError e => DocumentedFunction e
diff --git a/src/Text/Pandoc/Lua/Marshal/Row.hs b/src/Text/Pandoc/Lua/Marshal/Row.hs
--- a/src/Text/Pandoc/Lua/Marshal/Row.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Row.hs
@@ -15,7 +15,7 @@
   , mkRow
   ) where
 
-import Control.Applicative (optional)
+import Control.Applicative ((<|>), optional)
 import Control.Monad ((<$!>))
 import Data.Aeson (encode)
 import Data.Maybe (fromMaybe)
@@ -42,8 +42,10 @@
 peekRowFuzzy :: LuaError e => Peeker e Row
 peekRowFuzzy idx = liftLua (ltype idx) >>= \case
   TypeUserdata -> peekRow idx
-  TypeTable -> uncurry Row <$!> peekPair peekAttr (peekList peekCellFuzzy) idx
-  _ -> failPeek =<< typeMismatchMessage "Cell or table" idx
+  TypeTable ->
+    (uncurry Row <$!> peekPair peekAttr (peekList peekCellFuzzy) idx) <|>
+    (Row nullAttr <$!> peekList peekCellFuzzy idx)
+  _ -> failPeek =<< typeMismatchMessage "Row or table" idx
 
 -- | Row object type.
 typeRow :: LuaError e => DocumentedType e Row
diff --git a/test/test-block.lua b/test/test-block.lua
--- a/test/test-block.lua
+++ b/test/test-block.lua
@@ -410,6 +410,18 @@
           tbl
         )
       end),
+      test('bodies field accepts single TableBody', function ()
+        local th, tf = TableHead(), TableFoot()
+        local tbl = Table({long={}}, {}, th, TableBody{}, tf)
+        assert.are_same(tbl.bodies, {TableBody{}})
+
+        tbl.bodies = TableBody{Row{}}
+
+        assert.are_equal(
+          Table({}, {}, th, {TableBody{Row{}}}, tf),
+          tbl
+        )
+      end),
     },
   },
   group "Blocks" {
diff --git a/test/test-table.lua b/test/test-table.lua
--- a/test/test-table.lua
+++ b/test/test-table.lua
@@ -74,4 +74,24 @@
       end),
     },
   },
+  group "Row" {
+    group "Constructor" {
+      test('Can be called without arguments', function ()
+        assert.are_equal(Row(), Row{})
+      end),
+    },
+    group "unmarshalling" {
+      test('A list can be used instead', function ()
+        assert.are_equal(
+          TableHead{Row({Cell "hi"}, "row-1")},
+          TableHead{{"row-1", {Cell "hi"}}}
+        )
+        assert.are_equal(
+          TableHead{Row({Cell "hi"})},
+          TableHead{{Cell "hi"}}
+        )
+        -- assert.are_equal(Row({Cell "hi"}, "row-1"), {"row-1", {Cell "hi"}})
+      end),
+    },
+  }
 }
