---
title: "Module (asset type)"
description: "A module is a reusable, stateless (or globally-stateful) Luau library that other code pulls in with require(). Modules are the unit of factored-out logic in a world: helpers, math libraries, format…"
section: "Types"
slug: "types-module"
canonical: "https://origozero.ai/docs/types-module"
updated: "2026-09-05T16:41:47.225608290+00:00"
tags: ["asset-type", "reference"]
---

# Module (asset type)

## When to use one

- You have logic you want to share across multiple components,
  services, scenes, or tools.
- You have a registry, lookup table, or shared cache that callers all
  read from / write to.
- You want a stable API surface (`M.fn`) other code can depend on.

If the logic is bound to one entity, use a `.component`. If you want a
named UI of fields rather than functions, use a `.preset`.

## Where it lives

- Source: `/zero/source/.../<name>.module/`
- Identity: the source path with `/` written as `.` and each segment's
  type suffix dropped — `/zero/source/demo/beats.module` is
  `demo.beats`, and `/zero/source/kit.package/beats.module` is
  `kit.beats`, the container's name kept and its `.package` suffix
  gone. `asset.create` returns this string, `asset.resolve(...).identity`
  reads it back, and the `require` forms below write it as
  `<identity>`.
- Folder shape:
  - `init.luau` (or `init.lua`) — module body. **Required.** Returns
    a table (conventionally named `M`).
  - `README.md` — instance-level documentation. **Required.**
  - `.metadata` — agent-editable tags + free-form fields. **Required.**

## How to create one

```luau
asset.create("module", "<name>")
-- Creates: /zero/source/<name>.module/
--   init.luau   (canonical `local M = {} … return M`)
--   README.md   (instance README template)

-- `folder` places it in a subfolder of /source, and that subfolder
-- leads the identity: `{ folder = "lib" }` produces `lib.<name>`.
-- `into` authors it inside a resolved container (a package, a toolbox),
-- whose own identity leads the same way:
asset.create("module", "<name>", { folder = "lib" })
asset.create("module", "<name>", { into = asset.resolve("myPkg.package") })
```

## How it operates

1. **Registration.** Writing `init.luau` into a `.module/` folder
   indexes the asset and registers the module path with the resolver.
2. **Loading.** The first `require("<identity>")` runs
   the file's body and caches the returned table. Subsequent requires
   from anywhere return the **same table** — this is the
   shared-state lever for "module-local" registries.
3. **Hot reload.** Editing the file reloads the module body and
   invalidates the require cache. Live callers that captured the
   table via `require` still hold the old table until they re-require
   — design for this if you keep state in module-locals.
4. **Identity resolution.** A module is reached by its `<identity>`,
   and where the caller lives decides which spelling of it resolves.

   For a module in a **world**, from a caller in that same world —
   another module, a component, a scene entrypoint, or an `execute`
   chunk:
   - `require("<identity>")` — the identity on its own, resolved
     against the caller's own root. It carries every folder segment,
     so a module created with `{ folder = "demo" }` is
     `require("demo.beats")`.
   - `require("@root::<identity>")` — the same root-relative
     resolution, written out.
   - `require(".sibling")` — a module in the same folder, by its bare
     name; `..name` steps up one folder, `...name` two.

   Logs, stack traces and hot-reload notices name that same module
   `@local.source.<identity>` — its key under the world source root.
   The `<identity>` tail of that key is the form above, so a trace
   reading `@local.source.demo.beats` is `require("demo.beats")`.

   For a module in a **library** — content under
   `/zero/source/libs/@<lib>/` — `@<lib>::` addresses that library's
   root from anywhere: `require("@builtin::modules.transform")`.
   Mounting a world as a library moves its files under such a root and
   flips the caller root to `@<lib>::`, which is what keeps the
   root-relative forms above resolving across the move.

## Discovery

- `asset.list("module")` — every registered module.
- `asset.inspect("<name>")` — public functions, source path, this
  type README.
- `cat /zero/source/<name>.module` — same summary.

## Authoring conventions

- Return a single `M` table from `init.luau`. Top-level statements
  with side effects run on first require — useful for one-time
  initialization, dangerous if they touch the engine before it's
  ready.
- Write exported functions as `typed function`, not `function`: the
  argument types are enforced at the call and the checker knows the
  signature, so a caller's mistake is reported where they made it.
- Annotate exported functions with `--!desc` / `--!arg` / `--!return`
  / `--!example` so the LSP, `tools.list`, and the cat summary
  surface them. A function with no `--!desc` is reachable only by
  someone already reading this file.
- Declare a VALUE with `Field.<kind>(default, mode, description)`, and
  a table whose members are reached through a metatable with
  `--!members <TypeName>` — the latter gives the surface its accepted
  member set, its type, and its documentation from one table. See
  `guides { path: "core/authoring" }` for all four rules and
  `modules/api/engine/entity.module/members.module` for a worked
  member table.
- Keep module state in module-local upvalues. Globals leak across
  reloads; module-locals reset cleanly with each hot reload.
- Prefer focused modules over kitchen-sink modules. If two halves of a
  module have no shared state, split them.

## Common pitfalls

- **Cyclic requires.** A `require` chain that loops will return the
  partial table (the half built before the cycle was detected).
  Design for one-way dependencies.
- **Engine-time side effects.** Don't `entity.spawn` at module
  top-level — the engine may not be ready. Expose an `M.init` and
  call it from a scene entrypoint.
- **Hot-reload + module-local cache.** A module that caches
  expensive computation in upvalues loses that cache on reload —
  fine for dev, but be aware in performance work.
- **`init.lua` vs `init.luau`.** Either works, `.luau` is canonical.

## Related types

- `.component` — for entity-bound state + lifecycle hooks.
- `.service` — generates content you don't have yet (a mesh, sound, texture, …) via a metered provider.
- `.tool` — for a single agent-callable function with a YAML schema.
- `.package` — to group several modules + components + scenes into
  one shippable folder.
