diff --git a/pandoc-lua-engine.cabal b/pandoc-lua-engine.cabal
--- a/pandoc-lua-engine.cabal
+++ b/pandoc-lua-engine.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                pandoc-lua-engine
-version:             0.5.1
+version:             0.5.2
 build-type:          Simple
 license:             GPL-2.0-or-later
 license-file:        COPYING.md
@@ -109,7 +109,7 @@
 
   build-depends:       aeson
                      , bytestring            >= 0.9     && < 0.13
-                     , crypton               >= 0.30    && < 1.1
+                     , crypton               >= 0.30    && < 1.2
                      , citeproc              >= 0.8     && < 0.14
                      , containers            >= 0.6.0.1 && < 0.9
                      , data-default          >= 0.4     && < 0.9
diff --git a/src/Text/Pandoc/Lua/Marshal/Sources.hs b/src/Text/Pandoc/Lua/Marshal/Sources.hs
--- a/src/Text/Pandoc/Lua/Marshal/Sources.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Sources.hs
@@ -1,17 +1,17 @@
 {-# LANGUAGE LambdaCase           #-}
 {-# LANGUAGE OverloadedStrings    #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE TupleSections        #-}
 {- |
 Module      : Text.Pandoc.Lua.Marshaling.Sources
-Copyright   : © 2021-2024 Albert Krewinkel
-License     : GNU GPL, version 2 or above
-Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
+Copyright   : © 2021-2026 Albert Krewinkel <albert+pandoc@tarleb.com>
+License     : GPL-2.0-or-later
 
 Marshal 'Sources'.
 -}
 module Text.Pandoc.Lua.Marshal.Sources
   ( peekSources
   , pushSources
+  , typeSource
   ) where
 
 import Control.Monad ((<$!>))
@@ -37,17 +37,31 @@
 -- | Retrieves sources from the stack.
 peekSources :: LuaError e => Peeker e Sources
 peekSources idx = liftLua (ltype idx) >>= \case
-  TypeString -> toSources <$!> peekText idx
-  TypeTable  -> Sources <$!> peekList (peekUD typeSource) idx
-  _          -> Sources . (:[]) <$!> peekUD typeSource idx
+  TypeTable  -> mconcat <$!> peekList peekSourcesSingleton idx
+  _          -> peekSourcesSingleton idx
 
+-- | Retrieves a Sources singleton, i.e., a list with exactly one item.
+peekSourcesSingleton :: LuaError e => Peeker e Sources
+peekSourcesSingleton = choice
+  [ fmap toSources . peekText
+  , fmap (Sources . (:[])) . peekUD typeSource
+  , fmap (toSources . (:[])) . peekPair peekString peekText
+  , fmap (toSources . (:[])) .
+    (\idx -> (,)
+      <$> peekFieldRaw peekString "name" idx
+      <*> peekFieldRaw peekText "text" idx)
+  ]
+
+-- | A @Sources@ item.
+type Source = (SourcePos, Text)
+
 -- | Source object type.
-typeSource :: LuaError e => DocumentedType e (SourcePos, Text)
+typeSource :: LuaError e => DocumentedType e Source
 typeSource = deftype "Source"
   [ operation Tostring $ lambda
     ### liftPure snd
     <#> udparam typeSource "srcs" "Source to print in native format"
-    =#> functionResult pushText "string" "Haskell representation"
+    =#> functionResult pushText "string" "source contents"
   ]
   [ readonly "name" "source name"
       (pushString, sourceName . fst)
diff --git a/src/Text/Pandoc/Lua/Module.hs b/src/Text/Pandoc/Lua/Module.hs
--- a/src/Text/Pandoc/Lua/Module.hs
+++ b/src/Text/Pandoc/Lua/Module.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module
-   Copyright   : © 2017-2024 Albert Krewinkel
+   Copyright   : © 2017-2026 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
 
diff --git a/src/Text/Pandoc/Lua/Module/Types.hs b/src/Text/Pandoc/Lua/Module/Types.hs
--- a/src/Text/Pandoc/Lua/Module/Types.hs
+++ b/src/Text/Pandoc/Lua/Module/Types.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Types
-   Copyright   : © 2019-2024 Albert Krewinkel
+   Copyright   : © 2019-2026 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
@@ -13,13 +13,15 @@
   ( documentedModule
   ) where
 
-import Data.Version (makeVersion)
-import HsLua ( Module (..), (###), (<#>), (=#>)
+import Data.Version (Version, makeVersion)
+import HsLua ( DocumentedFunction, Module (..)
+             , (###), (<#>), (=#>), (#?), associateType
              , defmodule, defun, functionResult, parameter, since
              , withDescription, withFunctions
              )
-import HsLua.Module.Version (peekVersionFuzzy, pushVersion)
+import HsLua.Module.Version (peekVersionFuzzy, pushVersion, typeVersion)
 import Text.Pandoc.Error (PandocError)
+import Text.Pandoc.Lua.Marshal.Sources (peekSources, pushSources, typeSource)
 import Text.Pandoc.Lua.PandocLua ()
 
 -- | Push the pandoc.types module on the Lua stack.
@@ -28,15 +30,47 @@
   `withDescription`
       "Constructors for types that are not part of the pandoc AST."
   `withFunctions`
-      [ defun "Version"
-        ### return
-        <#> parameter peekVersionFuzzy "string|number|{integer,...}|Version"
-              "version_specifier"
-              (mconcat [ "A version string like `'2.7.3'`, "
-                       , "a Lua number like `2.0`, "
-                       , "a list of integers like `{2,7,3}`, "
-                       , "or a Version object."
-                       ])
-        =#> functionResult pushVersion "Version" "New Version object."
-        `since` makeVersion [2,7,3]
-      ]
+    [ mkVersion `since` v[2,7,3]
+    , mkSources `since` v[3,9,1]
+    ]
+  `associateType` typeSource
+  `associateType` typeVersion
+ where
+  v :: [Int] -> Version
+  v = makeVersion
+
+mkVersion :: DocumentedFunction PandocError
+mkVersion = defun "Version"
+  ### return
+  <#> parameter peekVersionFuzzy "string|number|{integer,...}|Version"
+        "version_specifier"
+        (mconcat [ "A version string like `'2.7.3'`, "
+                 , "a Lua number like `2.0`, "
+                 , "a list of integers like `{2,7,3}`, "
+                 , "or a Version object."
+                 ])
+  =#> functionResult pushVersion "Version" "New Version object."
+
+mkSources :: DocumentedFunction PandocError
+mkSources = defun "Sources"
+  ### pure
+  <#> parameter peekSources "string|{string,...}|table" "srcs"
+        "sources"
+  =#> functionResult pushSources "{Source,...}" "new Sources object"
+  #?
+    "Creates a new Sources element, i.e., a list of [[Source]] items.\n\
+    \\n\
+    \ Pandoc's text readers expect the input text to be paired\
+    \ with information on where the text originated, e.g., a\
+    \ file name. This abstraction is provided via the `Sources` type.\n\
+    \\n\
+    \Pandoc accepts a range of objects wherever a *Sources* list is\
+    \ expected:\n\
+    \\n\
+    \  - a list of [[Source]] items;\n\
+    \  - a simple string, which becomes an unnamed source;\n\
+    \  - a list of table objects, where each table contains the fields\n\
+    \    `name` (the filepath) and `text` (the file contents)\n\
+    \\n\
+    \A Sources list can be converted to a string via the default `tostring`\
+    \ Lua function. This will concatenate all source items."
diff --git a/test/lua/module/pandoc-types.lua b/test/lua/module/pandoc-types.lua
--- a/test/lua/module/pandoc-types.lua
+++ b/test/lua/module/pandoc-types.lua
@@ -1,5 +1,7 @@
 local tasty = require 'tasty'
+local system = require 'pandoc.system'
 local types = require 'pandoc.types'
+local Sources = types.Sources
 local Version = types.Version
 
 local assert = tasty.assert
@@ -7,6 +9,32 @@
 local group = tasty.test_group
 
 return {
+  group 'Sources' {
+    group 'constructor' {
+      test('has type `table`', function ()
+        assert.are_same(type(Sources ""), 'table')
+      end),
+      test('accepts a single string', function ()
+        assert.are_same(type(Sources('a')), 'table')
+      end),
+      test('accepts a list of strings', function ()
+        local srcs = Sources{'first text\n', 'second text\n'}
+        assert.are_equal(srcs[1].name, '')
+        assert.are_equal(srcs[1].text, 'first text\n')
+      end),
+      test('accepts list of filepath/content tuples', function ()
+        local srcs = Sources{{'test.txt', 'semi-random content'}}
+        assert.are_equal(srcs[1].name, 'test.txt')
+        assert.are_equal(srcs[1].text, 'semi-random content\n')
+      end),
+      test('accepts lists of Source-like tables', function ()
+        local srcs = Sources{{name = 'test.txt', text = 'semi-random content'}}
+        assert.are_equal(srcs[1].name, 'test.txt')
+        assert.are_equal(srcs[1].text, 'semi-random content\n')
+      end),
+    },
+  },
+
   group 'Version' {
 
     group 'constructor' {
