Package (asset type)
A library is the addressable root scope content resolves under
(@builtin, @<name>) — a whole world mounted into this one under a
namespace, via zm.installLib / world.installLibrary. A package is
an asset that lives inside such a root, and one library holds as many
packages as its author put there. A package is installed by guid the
way every asset is (zeromind.install, zm.installAsset).
When to use one
- You're authoring a coherent feature suite (character controller stack, voxel engine, UI kit, animation library) and want to ship it as one install target.
- You want a stable identity worlds can install and resolve sub-assets
against (
@<lib>::pkg.sub_module). - You want versioning + a per-package metadata layer that travels with the contents.
If you're authoring inside a single world and don't need a separate
install lifecycle, you don't need a package — drop your .component
/ .module / etc. directly in /zero/source/. Packages are for
cross-world distribution.
Where it lives
- Source:
/zero/source/.../<Name>.package/(or, for legacy packages, a folder containingpackage.yamlat its root). - Identity:
<Name>(the.packagesuffix collapses in identity — mirrors.module). A file at<Name>.package/<sub>/init.luauresolves under the same identity it would at<Name>/<sub>/init.luau. - Folder shape (container-style — open child set):
package.yaml— metadata (name, version, description). Required.README.md— instance documentation. Required..metadata— agent-editable tags + free-form fields. Required.- Sub-asset folders: any combination of
.component/,.module/,.scene/,.preset/,.tool/,.toolbox/,.material/,.shader/, etc.
The validator permits unlisted children (allow_unlisted: true) by
design — packages are open containers.
How to create one
asset.create("package", "<Name>")
-- Creates: /zero/source/<Name>.package/
-- package.yaml (metadata scaffold)
-- README.md (instance README template)
-- `folder` places it in a subfolder of /source instead of the root:
asset.create("package", "<Name>", { folder = "systems" })
Then scaffold sub-assets inside it:
local pkg = asset.resolve("<Name>.package")
asset.create("component", "Spinner", { into = pkg })
asset.create("module", "util", { into = pkg })
How it operates
- Registration. Writing
package.yamlindexes the package with the package registry. - Install. A package is installed by guid, the way every asset
is:
zeromind.installfrom the shared library, ortools.use("zm", "installAsset", <guid>)inside the engine. The install writes the package's files (and the deps its.refsrecord) into this world's source, so its sub-assets resolve here.tools.use("zm", "preview", <guid>)reports what an install would write before it writes anything. - Sub-asset resolution. Because
.packagecollapses in identity, sub-asset paths work whether the package folder is suffixed (<Name>.package/<sub>) or not (<Name>/<sub>). - Updates.
tools.use("zm", "updates")lists installed content with upstream changes available; re-running the install pulls them. - Metadata.
package.yaml::nameoverrides the folder stem if present (rare);version+descriptionare surfaced in the package index. - Hot reload. Edits to sub-assets hot-reload as normal; edits to
package.yamlreload package metadata.
Discovery
asset.list("package")— every registered package.asset.inspect("<name>")— sub-asset catalog, source, this type README.cat /zero/source/<Name>.package— same summary.library.list()— the library assets registered in this world, including the ones packages under an installed library contribute.
Authoring conventions
- Name packages by their feature (
voxels,cameraRigs,dialogSystem), not by their owner — the host library is the@<lib>::scope prefix. - Cluster cohesive sub-assets together. A "character controller" package should ship the controller component, its preset configs, its supporting modules, and a demo scene — not just the component.
- Document the package's surface in
README.md— what install installs, what each sub-asset does, how they relate. Agents readingcat /zero/source/<Name>.packageget the full picture in one shot. - Version conservatively — breaking a package means breaking every world that installed it. Use semantic version bumps.
Common pitfalls
- Missing
package.yaml. Without it the folder isn't recognized as a package even if it has a.package/suffix. - Dependency installs. A package that depends on another package
must declare it (in
package.yaml::dependenciesonce supported) and the parent world must install dependencies in order. - Identity collapse. Sub-asset paths skip the
.packagesuffix. Don't include.packageinrequirestrings.
Referencing inside a package: the ~ rule
~ means this package, resolved structurally from the referring file's own
location. It is the only package-specific syntax. It is rename-safe (no package
name is ever written) and library-agnostic (it never names the host library, so
it survives @builtin being renamed or the package being mounted elsewhere).
~.tail maps to <pkg>.package/tail from anywhere inside the package.
| Referencing from | Use | Example |
|---|---|---|
| An asset/module inside this package | ~.tail | require("~.modules.myThing"), shader: "~.shaders.myThingBlend" |
| A different package under the same root | root-relative identity | require("@root::systems.otherPkg.modules.x") |
| A different library's public API (a deliberate external dependency) | @otherlib::… | require("@physics::modules.rigidbody") |
Never write @builtin:: (or any host-library name) to reference your own
package — that couples the package to the library it currently lives in and
breaks if the library is renamed or the files move. Use ~.. It works from
Luau require() and from asset-reference strings in data files (a material's
mat.yaml shader: field, a component's default asset ref); the engine
resolves it against the referring file's own package. In a .luau file holding
a ~. ref as a string, resolve it explicitly against your own path:
asset.resolve("~.materials.myThingStandard", "material", __FILE__).
~. is the package-relative self-reference; @root:: is the root-relative one
(the library root, or the world source tree for non-library content). The
core/requiring-modules guide covers the root model in full.
A package that ships substantial functionality should include a .guide asset
(a <name>.guide/guide.md folder anywhere under the package); the guides tool
discovers it live, so collaborators find the system the way they find built-in
guides. A package's test suite reaches the shared Test framework through its
enclosing asset: local Test = asset.containing(__FILE__).modules.shared
(asset.containing returns the deepest enclosing typed asset, the .testSuite,
not the outer .package).
Related types
- Any of
.component,.module,.scene,.preset,.tool,.toolbox,.material,.shader,.style,.service,.importer— packages bundle these.