Skip to content

Packaged Lua Mods

Piggo41 edited this page Oct 16, 2026 · 9 revisions

Packaged Lua mods

Home / Packaged Lua mods

Use a packaged mod when one mod needs several private Lua modules, assets, or a distributable ZIP. Single-file mods remain supported for small additions.

Directory layout

A packaged mod is a directory directly inside .minecraft/betamoon/scripts with betamoon.mod.json at its root:

betamoon/
  scripts/
    clockwork/
      betamoon.mod.json
      clockwork.lua
      content/
        items.lua
        machines.lua
      assets/
        clockwork/
          textures/
          models/

The entrypoint may have any filename or nested path. It does not need to be main.lua.

{
  "entrypoint": "clockwork.lua",
  "name": "Clockwork",
  "version": "1.0.0",
  "description": "Clockwork machines and tools.",
  "dependencies": ["Shared Materials"],
  "image": "icon.png"
}

entrypoint is required and must select a .lua file inside the package. The other fields are optional. They have the same meaning as entrypoint metadata. Metadata may be declared in the manifest, the entrypoint, or both. When both declare a field, the values must match exactly; a conflict rejects the package instead of silently choosing one source.

For packaged mods, prefer keeping all metadata in betamoon.mod.json and leave the entrypoint focused on lifecycle functions and registration. This gives each field one source of truth and avoids exact-match conflicts during edits.

Unknown manifest fields, absolute paths, .., backslashes, duplicate dependencies, and missing entrypoints are rejected.

Private modules

Use ordinary Lua require inside a packaged mod:

local items = require("content.items")
local machines = require("content.machines")

function modInit()
    items.register()
    machines.register()
end

require("content.items") checks content/items.lua and then content/items/init.lua inside the same package. Modules are cached for that mod generation, so repeated calls return the same value without evaluating the file again.

Private module names cannot escape the package and cannot resolve files from another mod. BetaMoon checks the syntax of every .lua file in a package before loading it, including modules that the entrypoint does not currently require.

Use betamoon.modules for deliberate sharing between different mods. Ordinary require is for files that belong to the same packaged mod and does not establish dependency order.

The distributed examples include a minimal beginner package and eight complete advanced packages. The beginner lesson shows only the manifest, entrypoint, and a no-op private module. Basic Storage keeps plain data and layout declarations in private modules. The recipe-machine packages return registration functions from their type and recipe modules, then call those functions from modInit so every registration remains inside the normal package lifecycle. The world-generation packages keep JSON structure gameplay data beside their entrypoints; the capstone also separates features, structures, and biome composition into private modules.

ZIP distribution

A packaged mod may be distributed as one ZIP directly inside betamoon/scripts:

betamoon/
  scripts/
    clockwork.zip

clockwork.zip contents:
  betamoon.mod.json
  clockwork.lua
  content/items.lua
  assets/clockwork/textures/...

The manifest must be at the ZIP root. Do not wrap the package in an extra top-level directory. ZIP entry names are case-sensitive and packages containing unsafe, duplicate, or case-colliding paths are rejected.

The directory and ZIP forms have the same Lua module and asset behavior. Changing anything inside a ZIP replaces the archive as one package on the next manual or automatic reload.

Assets

Game asset paths are relative to the package's assets/ directory. A packaged mod can keep its code and assets together while separating them from Lua modules and manifest metadata:

betamoon.assets.textures:add {
    key = "clockwork:block/gearbox",
    path = "clockwork/textures/gearbox.png"
}

For the directory example, the default is clockwork/assets/clockwork/textures/gearbox.png; inside the ZIP it is the entry assets/clockwork/textures/gearbox.png. The texture-pack override remains bm_assets/clockwork/textures/gearbox.png because explicit asset paths retain their structure beneath bm_assets/.

Assets registered without path use their ordinary key-derived package path. See Assets and texture-pack overrides.

Each package resolves defaults only from its own assets/ directory. Different packages may use the same relative path without colliding. Files beside betamoon.mod.json, Lua modules, and manifest images are not implicitly exposed as game assets.

Identity and conflicts

The top-level directory name or ZIP filename without .zip is the package's storage identity. A loose script, directory package, and ZIP package cannot use the same identity, including names that differ only by letter case.

The mod's dependency identity remains its name metadata. Storage identity, metadata name, registered content keys, and exported module names serve different purposes and do not replace one another.

Reload behavior

Manual reload checks the manifest and all Lua sources before unloading the active generation. Directory package Lua files and assets participate in optional reload-on-save detection; a ZIP reloads as one package when its file changes. Required structural content still follows the ordinary hot-reload compatibility rules.

Clone this wiki locally