Writing a Module File
Create a module by returning a table of functions from a .lua file.
Writing a Module File is a free Lua Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Basic Module Pattern
The standard Lua module pattern: create a local table M, add functions and values to it, and return it at the end. The table is the module's public API. All other locals in the file are private.
-- stringutils.lua
local M = {}
function M.trim(s)
return s:match("^%s*(.-)%s*$")
end
function M.split(s, sep)
local t = {}
for p in s:gmatch("[^"..sep.."]+") do t[#t+1]=p end
return t
end
return M
-- Usage:
-- local su = require("stringutils")
-- print(su.trim(" hello "))Private State
Variables declared local in the module file are private — callers cannot access them. Functions in the module can access private state as upvalues. This is Lua's encapsulation mechanism.
-- counter.lua
local M = {}
local count = 0 -- private state
function M.increment(n)
count = count + (n or 1)
end
function M.reset()
count = 0
end
function M.get()
return count
end
return MModule with Initialization
Some modules need initialization (config, connections). Put initialization code at the module level (top of file) or in an explicit M.init() function. The module-level approach runs once when first required; init() requires explicit calling.
-- cache.lua
local M = {}
local store = {} -- initialized at load time
local hits = 0
local misses = 0
function M.get(key)
if store[key] ~= nil then
hits = hits + 1
return store[key]
end
misses = misses + 1
return nil
end
function M.set(key, val) store[key] = val end
function M.stats() return {hits=hits, misses=misses} end
return MModule with Class
A module can export a class: a table with a constructor function. The returned module table contains the new() function and may also serve as the class metatable.
-- point.lua
local Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({x=x, y=y}, Point)
end
function Point:distance(other)
local dx, dy = self.x-other.x, self.y-other.y
return math.sqrt(dx*dx + dy*dy)
end
function Point:__tostring()
return string.format("(%g,%g)", self.x, self.y)
end
return Point
-- Usage:
-- local Point = require("point")
-- local p = Point.new(3, 4)Module Constants
Export constants by adding them to the module table. By convention, constants are uppercase. Since Lua has no const, users can technically modify them, but the uppercase naming signals "don't change."
-- colors.lua
local M = {}
M.RED = {r=255, g=0, b=0}
M.GREEN = {r=0, g=255, b=0}
M.BLUE = {r=0, g=0, b=255}
M.WHITE = {r=255, g=255, b=255}
M.BLACK = {r=0, g=0, b=0}
function M.toHex(c)
return string.format("#%02X%02X%02X", c.r, c.g, c.b)
end
return MModule Versioning
Include a version field in your module. Callers can check the version to ensure compatibility. Use semantic versioning (major.minor.patch).
-- mylib.lua
local M = {}
M._VERSION = "1.2.3"
M._NAME = "mylib"
M._DESCRIPTION = "My Lua library"
function M.hello(name)
return "Hello, " .. (name or "World") .. "!"
end
return M
-- Usage:
local mylib = require("mylib")
print(mylib._VERSION) -- 1.2.3
print(mylib.hello("Lua"))Sub-modules
Large libraries split into sub-modules. The main module may require and re-export sub-modules, or each sub-module is used independently. Organize files in directories matching the module path.
-- mylib/init.lua (loaded by require("mylib"))
local M = {}
M.strings = require("mylib.strings")
M.tables = require("mylib.tables")
M.math = require("mylib.math")
M._VERSION = "2.0.0"
return M
-- Users can require the whole library:
-- local mylib = require("mylib")
-- mylib.strings.trim(...)
-- Or individual sub-modules:
-- local strs = require("mylib.strings")Module with Metatable
Make a module callable by giving it a metatable with __call. This is useful for modules that are primarily functions but also have utilities — the "main" operation is the call, utilities are fields.
-- format.lua
local M = {}
setmetatable(M, {__call = function(_, fmt, ...)
return string.format(fmt, ...)
end})
function M.pad(s, width, char)
char = char or " "
return string.rep(char, math.max(0, width - #s)) .. s
end
return M
-- Usage:
-- local fmt = require("format")
-- print(fmt("%.2f", 3.14)) -- 3.14
-- print(fmt.pad("42", 5)) -- 42Lazy Loading Sub-modules
Use __index to load sub-modules lazily — only when first accessed. This speeds up startup for large libraries with many sub-modules.
-- biglib.lua
local M = {}
local submodules = {"strings", "tables", "math", "io"}
setmetatable(M, {
__index = function(t, k)
for _, name in ipairs(submodules) do
if name == k then
local mod = require("biglib." .. k)
rawset(t, k, mod)
return mod
end
end
return nil
end
})
return M
-- Loads biglib.strings only when accessed:
-- local lib = require("biglib")
-- lib.strings.trim(...)Module Testing Pattern
Add a test function or block at the bottom of the module file, conditionally executed only when the file is run directly (not required). This keeps unit tests colocated with the code.
-- utils.lua
local M = {}
function M.clamp(v, lo, hi)
return math.max(lo, math.min(hi, v))
end
-- Self-test: only runs when executed directly
if debug.getinfo(2, "S") == nil then
-- Running as main script, not required
print("Testing clamp...")
assert(M.clamp(5, 0, 10) == 5)
assert(M.clamp(-1, 0, 10) == 0)
assert(M.clamp(15, 0, 10) == 10)
print("All tests passed!")
end
return MQuick Check
What is the standard way to define private state in a Lua module?
Recap: Writing Modules
Summary:
- Pattern:
local M = {} ... return M - Locals in the file = private; M fields = public API
- Module-level code runs once at first require
- Export classes via constructor in module table
- Lazy sub-module loading via
__index - Include
_VERSIONfor compatibility checks
Frequently asked questions
Is the “Writing a Module File” lesson free?
Yes — the full text of “Writing a Module File” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Writing a Module File”?
Create a module by returning a table of functions from a .lua file. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Writing a Module File” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The require Function
- Writing a Module File
- package.path and package.cpath
- Module Patterns and Best Practices