luaguides

Dependency Management Patterns in Lua

Lua has no built-in package manager. That means dependency management is a question every Lua project eventually has to answer. The right choice depends on whether you’re writing a game plugin, a server application, or a library you’re distributing to others.

The Options

  1. Luarocks — Lua’s community package manager
  2. Git submodules — include a library as a subdirectory in your project
  3. Inline bundling — copy the library code directly into your project
  4. Manual download — fetch and place the library files yourself

Each approach has tradeoffs in reliability, portability, and maintenance burden.

Using Luarocks

Luarocks is the closest thing Lua has to a standard package manager. It installs packages to a user-configured path and lets you require them by name.

# Install a package
luarocks install luasocket

# Install to a specific prefix
luarocks install --local luasocket

To use a locally installed rock, add the path to package.cpath or use luarocks paths in your script:

luarocks path --bin
# Adds /home/user/.luarocks/share/lua/5.1 to LUA_PATH

In your code:

local socket = require("socket")

Luarocks handles versioning and dependency resolution. The catch: you need luarocks installed on every machine that runs your project, and not every library is available as a rock.

Git Submodules

If you need a specific version of a library and want it tracked in your repo, git submodules work well:

# Add a library as a submodule
git submodule add https://github.com/example/lua-library.git vendor/library

In your code, adjust the path:

package.path = package.path .. ";./vendor/library/?.lua"
local lib = require("library")

Submodules pin you to a specific commit, which is good for reproducibility. But they add friction to updates and can cause issues when collaborators clone a fresh copy.

Inline Bundling

For distribution or self-contained projects, many developers copy dependencies into a lib/ directory:

myproject/
  lib/
    sockets.lua
    lpeg.lua
  main.lua
package.path = package.path .. ";./lib/?.lua"
local socket = require("sockets")

The advantage: everything lives in one repo, no external dependencies. The downside: updating means manually replacing files, and you lose version history for the library.

This approach is common in Roblox plugins and game scripts where external package managers aren’t available.

Writing Your Own require Handler

For more control, you can implement a custom module loader:

local function add_search_path(path)
  package.path = package.path .. ";" .. path .. "/?.lua"
  package.path = package.path .. ";" .. path .. "/?/init.lua"
end

-- Add project-local lib directory
add_search_path("./lib")

-- Add vendor directory
add_search_path("./vendor")

This gives you fine-grained control over load order and lets you handle missing modules gracefully.

Detecting Installed Packages

Before requiring an optional dependency, check if it’s available:

local function package_available(name)
  local ok, err = pcall(require, name)
  return ok
end

if package_available("luasocket") then
  local socket = require("socket")
  -- use socket
else
  -- fallback or error
end

This pattern is how many libraries support optional dependencies without forcing them on users.

Working with OpenResty and LuaRocks

OpenResty has its own module path hierarchy:

-- OpenResty loads from these paths automatically
package.path = package.path .. ";/usr/local/openresty/lua/?.lua"

If you need a rock in OpenResty, install it to the OpenResty path:

luarocks --lua-version=5.1 install luasocket

Or configure the path explicitly:

luarocks configlua_path /usr/local/openresty/lua/?.lua

vendoring Libraries

A common pattern is to vendor libraries — copy them into your project and load them from a local path:

-- lib/mylib.lua
local mylib = {}

function mylib.foo()
  return "foo"
end

return mylib
-- main.lua
package.path = package.path .. ";./lib/?.lua"
local mylib = require("mylib")

Vendoring gives you a stable, reproducible copy that works without any package manager installed.

When to Use What

ApproachBest for
LuarocksServer apps, CI environments, libraries you distribute
Git submodulesProjects where you want version-pinned dependencies
Inline bundlingGames, plugins, single-file distributions
Custom requireWhen you need specific load order or fallbacks

See Also