diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 ## Changelog
 
+### 1.0.3
+
+Released 2019-05-04.
+
+- New module `Foreign.Lua.Module`, containing helper functions to
+  define and load modules from Haskell.
+
+- Improve documentation of `open<lib>` (many thanks to Christian
+  Charukiewicz.)
+
 ### 1.0.2
 
 Released 2019-01-05.
diff --git a/hslua.cabal b/hslua.cabal
--- a/hslua.cabal
+++ b/hslua.cabal
@@ -1,5 +1,5 @@
 name:                hslua
-version:             1.0.2
+version:             1.0.3
 synopsis:            Bindings to Lua, an embeddable scripting language
 description:         HsLua provides bindings, wrappers, types, and helper
                      functions to bridge Haskell and <https://www.lua.org/ Lua>.
@@ -79,6 +79,7 @@
                      , Foreign.Lua.Core.RawBindings
                      , Foreign.Lua.Core.Types
                      , Foreign.Lua.FunctionCalling
+                     , Foreign.Lua.Module
                      , Foreign.Lua.Types
                      , Foreign.Lua.Types.Peekable
                      , Foreign.Lua.Types.Pushable
@@ -186,6 +187,7 @@
                      , Foreign.Lua.Core.AuxiliaryTests
                      , Foreign.Lua.Core.ErrorTests
                      , Foreign.Lua.FunctionCallingTests
+                     , Foreign.Lua.ModuleTests
                      , Foreign.Lua.TypesTests
                      , Foreign.Lua.Types.PeekableTests
                      , Foreign.Lua.Types.PushableTests
diff --git a/src/Foreign/Lua.hs b/src/Foreign/Lua.hs
--- a/src/Foreign/Lua.hs
+++ b/src/Foreign/Lua.hs
@@ -67,14 +67,21 @@
   , setglobal'
   , raiseError
   , Optional (Optional, fromOptional)
-  -- ** retrieving values
+  -- ** Retrieving values
   , popValue
+  -- ** Modules
+  , requirehs
+  , preloadhs
+  , create
+  , addfield
+  , addfunction
   ) where
 
 import Prelude hiding (compare, concat)
 
 import Foreign.Lua.Core
 import Foreign.Lua.FunctionCalling
+import Foreign.Lua.Module
 import Foreign.Lua.Types
 import Foreign.Lua.Userdata ( pushAny, peekAny )
 import Foreign.Lua.Util
diff --git a/src/Foreign/Lua/Core/Functions.hs b/src/Foreign/Lua/Core/Functions.hs
--- a/src/Foreign/Lua/Core/Functions.hs
+++ b/src/Foreign/Lua/Core/Functions.hs
@@ -474,62 +474,63 @@
 next :: StackIndex -> Lua Bool
 next idx = boolFromFailable =<< liftLua (\l -> hslua_next l idx)
 
--- | Opens all standard Lua libraries into the current state.
+-- | Opens all standard Lua libraries into the current state and sets each
+-- library name as a global value.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#luaL_openlibs luaL_openlibs>.
 openlibs :: Lua ()
 openlibs = liftLua luaL_openlibs
 
--- | Opens all standard Lua libraries into the current state.
+-- | Pushes Lua's /base/ library onto the stack.
 --
--- | See <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_base luaopen_base>.
+-- See <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_base luaopen_base>.
 openbase :: Lua ()
 openbase = pushcfunction lua_open_base_ptr *> call 0 multret
 
--- | Opens Lua's /debug/ library into the current state.
+-- | Pushes Lua's /debug/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_debug luaopen_debug>.
 opendebug :: Lua ()
 opendebug = pushcfunction lua_open_debug_ptr *> call 0 multret
 
--- | Opens Lua's /io/ library into the current state.
+-- | Pushes Lua's /io/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_io luaopen_io>.
 openio :: Lua ()
 openio = pushcfunction lua_open_io_ptr *> call 0 multret
 
--- | Opens Lua's /math/ library into the current state.
+-- | Pushes Lua's /math/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_math luaopen_math>.
 openmath :: Lua ()
 openmath = pushcfunction lua_open_math_ptr *> call 0 multret
 
--- | Opens Lua's /os/ library into the current state.
+-- | Pushes Lua's /os/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_os luaopen_os>.
 openos :: Lua ()
 openos = pushcfunction lua_open_os_ptr *> call 0 multret
 
--- | Opens Lua's /package/ library into the current state.
+-- | Pushes Lua's /package/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_package luaopen_package>.
 openpackage :: Lua ()
 openpackage = pushcfunction lua_open_package_ptr *> call 0 multret
 
--- | Opens Lua's /string/ library into the current state.
+-- | Pushes Lua's /string/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_string luaopen_string>.
 openstring :: Lua ()
 openstring = pushcfunction lua_open_string_ptr *> call 0 multret
 
--- | Opens Lua's /table/ library into the current state.
+-- | Pushes Lua's /table/ library onto the stack.
 --
 -- See also:
 -- <https://www.lua.org/manual/5.3/manual.html#pdf-luaopen_table luaopen_table>.
diff --git a/src/Foreign/Lua/Module.hs b/src/Foreign/Lua/Module.hs
new file mode 100644
--- /dev/null
+++ b/src/Foreign/Lua/Module.hs
@@ -0,0 +1,81 @@
+{-|
+Module      : Foreign.Lua.Module
+Copyright   : © 2019 Albert Krewinkel
+License     : MIT
+Maintainer  : Albert Krewinkel <albert+hslua@zeitkraut.de>
+Stability   : alpha
+Portability : Requires GHC 8 or later.
+
+Utility functions for HsLua modules.
+-}
+module Foreign.Lua.Module
+  ( requirehs
+  , preloadhs
+  , addfield
+  , addfunction
+  , create
+  )
+where
+
+import Control.Monad (unless)
+import Foreign.Lua.Core
+import Foreign.Lua.Types (Pushable, push)
+import Foreign.Lua.FunctionCalling (ToHaskellFunction, pushHaskellFunction)
+
+-- | Load a module, defined by a Haskell action, under the given name.
+--
+-- Similar to @luaL_required@: After checking "loaded" table, calls
+-- @pushMod@ to push a module to the stack, and registers the result in
+-- @package.loaded@ table.
+--
+-- The @pushMod@ function must push exactly one element to the top of
+-- the stack. This is not checked, but failure to do so will lead to
+-- problems. Lua's @package@ module must have been loaded by the time
+-- this function is invoked.
+--
+-- Leaves a copy of the module on the stack.
+requirehs :: String -> Lua () -> Lua ()
+requirehs modname pushMod = do
+  -- get table of loaded modules
+  getfield registryindex loadedTableRegistryField
+
+  -- Check whether module has already been loaded.
+  getfield stackTop modname  -- LOADED[modname]
+  alreadyLoaded <- toboolean stackTop
+
+  unless alreadyLoaded $ do
+    pop 1  -- remove field
+    pushMod  -- push module
+    pushvalue stackTop  -- make copy of module
+    -- add module under the given name (LOADED[modname] = module)
+    setfield (nthFromTop 3) modname
+
+  remove (nthFromTop 2)  -- remove table of loaded modules
+
+-- | Registers a preloading function. Takes an module name and the Lua
+-- operation which produces the package.
+preloadhs :: String -> Lua NumResults -> Lua ()
+preloadhs name pushMod = do
+  getfield registryindex preloadTableRegistryField
+  pushHaskellFunction pushMod
+  setfield (nthFromTop 2) name
+  pop 1
+
+-- | Add a string-indexed field to the table at the top of the stack.
+addfield :: Pushable a => String -> a -> Lua ()
+addfield name value = do
+  push name
+  push value
+  rawset (nthFromTop 3)
+
+-- | Attach a function to the table at the top of the stack, using the
+-- given name.
+addfunction :: ToHaskellFunction a => String -> a -> Lua ()
+addfunction name fn = do
+  push name
+  pushHaskellFunction fn
+  rawset (nthFromTop 3)
+
+-- | Create a new module (i.e., a Lua table).
+create :: Lua ()
+create = newtable
diff --git a/test/Foreign/Lua/ModuleTests.hs b/test/Foreign/Lua/ModuleTests.hs
new file mode 100644
--- /dev/null
+++ b/test/Foreign/Lua/ModuleTests.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-|
+Module      : Foreign.Lua.ModuleTests
+Copyright   : © 2019 Albert Krewinkel
+License     : MIT
+Maintainer  : Albert Krewinkel <albert+hslua@zeitkraut.de>
+Stability   : alpha
+Portability : Requires GHC 8 or later.
+
+Tests creating and loading of modules with Haskell.
+-}
+module Foreign.Lua.ModuleTests (tests) where
+
+import Foreign.Lua (Lua)
+import Foreign.Lua.Module (addfield, addfunction, create, preloadhs, requirehs)
+import Test.HsLua.Util ((=:), pushLuaExpr, shouldBeResultOf)
+import Test.Tasty (TestTree, testGroup)
+
+import qualified Foreign.Lua as Lua
+
+-- | Specifications for Attributes parsing functions.
+tests :: TestTree
+tests = testGroup "Module"
+  [ testGroup "requirehs"
+    [ "pushes module to stack" =:
+      1 `shouldBeResultOf` do
+        Lua.openlibs
+        old <- Lua.gettop
+        requirehs "foo" (Lua.pushnumber 5.0)
+        new <- Lua.gettop
+        return (new - old)
+
+    , "module can be loaded with `require`" =:
+      let testModule = "string as a module" :: String
+      in testModule `shouldBeResultOf` do
+        Lua.openlibs
+        requirehs "test.module" (Lua.push testModule)
+        pushLuaExpr "require 'test.module'"
+        Lua.peek Lua.stackTop
+    ]
+
+  , testGroup "preloadhs"
+    [ "does not modify the stack" =:
+      0 `shouldBeResultOf` do
+        Lua.openlibs
+        old <- Lua.gettop
+        preloadhs "foo" (1 <$ Lua.pushnumber 5.0)
+        new <- Lua.gettop
+        return (new - old)
+
+    , "module can be loaded with `require`" =:
+      let testModule = "string as a module" :: String
+      in testModule `shouldBeResultOf` do
+        Lua.openlibs
+        preloadhs "test.module" (1 <$ Lua.push testModule)
+        pushLuaExpr "require 'test.module'"
+        Lua.peek Lua.stackTop
+    ]
+
+  , testGroup "creation helpers"
+    [ "create produces a table" =:
+      Lua.TypeTable `shouldBeResultOf` do
+        create
+        Lua.ltype Lua.stackTop
+
+    , "addfield modifies table" =:
+      Lua.Integer 23 `shouldBeResultOf` do
+        create
+        addfield "field_name" (23 :: Int)
+        Lua.getfield Lua.stackTop "field_name"
+        Lua.peek Lua.stackTop
+
+    , "addfunction modifies table" =:
+      Lua.Integer 5 `shouldBeResultOf` do
+        create
+        addfunction "minus18" (return . subtract 18 :: Int -> Lua Int)
+        Lua.getfield Lua.stackTop "minus18"
+        Lua.pushinteger 23
+        Lua.call 1 1
+        Lua.peek Lua.stackTop
+    ]
+  ]
diff --git a/test/test-hslua.hs b/test/test-hslua.hs
--- a/test/test-hslua.hs
+++ b/test/test-hslua.hs
@@ -24,6 +24,7 @@
 import qualified Foreign.LuaTests
 import qualified Foreign.Lua.CoreTests
 import qualified Foreign.Lua.FunctionCallingTests
+import qualified Foreign.Lua.ModuleTests
 import qualified Foreign.Lua.TypesTests
 import qualified Foreign.Lua.Types.PeekableTests
 import qualified Foreign.Lua.Types.PushableTests
@@ -45,5 +46,6 @@
     , Foreign.Lua.Types.PushableTests.tests
     ]
   , Foreign.Lua.UserdataTests.tests
+  , Foreign.Lua.ModuleTests.tests
   , Foreign.LuaTests.tests
   ]
