Metadata
Define nows.mod.kdl metadata, dependencies, side declarations and runtime entrypoints.
Metadata
KDL is the recommended human-facing metadata format today, but it is replaceable. integrations/kdl turns nows.mod.kdl into the generic ModDescriptor model owned by core.
Nows looks for this file at the root of the mod jar:
nows.mod.kdl
The KDL reader requires a root mod node. id and version are required. name falls back to the id, minecraft falls back to *, and side falls back to both.
mod id="my_mod" name="My Mod" version="1.0.0" minecraft="26.2" side="client" {
info {
description "Short description shown to tools and companion UI."
author "YourName"
license "Apache-2.0"
icon "assets/my_mod/icon.png"
}
links {
homepage "https://example.com"
sources "https://github.com/example/my-mod"
}
compatibility {
requires "minecraft" version="26.2"
depends "cloth-config" version=">=11.0.0"
recommends "modmenu" version=">=1.0.0"
incompatible-with "bad_mod" reason="Breaks the same screen"
}
load-order {
after "cloth-config"
before "late_mod"
}
properties {
channel "stable"
}
runtime {
network-channel "my_mod:main"
listener "com.example.MyLifecycleListener"
entrypoint "com.example.MyMod"
transformer "com.example.MyTransformer"
mixin "my_mod.mixins.json"
}
}
Grouped KDL is the recommended style, but equivalent flat nodes remain supported. Runtime-provided ids such as minecraft, nows and nows-loader can be used in dependency declarations.
side accepts client, server, both or common. The current launcher runtime is client-side and rejects server-only mods before loading mod classes.
Parsed Descriptor Fields
The KDL reader normalizes metadata into ModDescriptor, so code that consumes metadata does not depend on KDL directly.
| Descriptor field | Source |
|---|---|
id |
required mod id="..."; must match [a-z][a-z0-9_-]{1,63} |
name |
mod name, defaulting to id |
version |
required mod version |
minecraft |
mod minecraft, defaulting to * |
side |
side or environment, defaulting to both |
description, icon |
root properties or info/metadata children |
authors, contributors, licenses |
singular or plural metadata nodes/properties |
contacts |
links, contact, homepage, sources, issues, wiki, discord, email |
properties |
custom root properties, properties, custom, or declaration properties |
dependencies |
dependency, compatibility and load-order nodes |
declarations |
unknown nodes plus runtime declarations such as entrypoint, listener, mixin, network-channel |
Dependency Semantics
Dependency nodes are validated by ModDependencyResolver before mods load. Required dependencies must be present and match the declared version range. Optional dependencies can influence order when the target mod is present. Conflict/incompatible declarations fail loading when the target version matches.
Load order declarations are edges in the same resolver:
requires,depends,dependencyandrequiremake the target load before the current mod.recommendsandsuggestsare optional by default.breaks,conflicts,conflict,incompatibleandincompatible-withdescribe incompatible targets.beforeandload-beforemake the current mod load before the target when the target is loaded.afterandload-aftermake the target load before the current mod when the target is loaded.
Version constraints support exact versions and range-style constraints through Nows’ core version constraint matcher. Use * when the relationship is not version-specific.
Runtime Declarations
Declarations are intentionally generic. Integrations decide which keys they care about:
| Declaration | Used for |
|---|---|
entrypoint |
classes instantiated as mod entrypoints |
listener |
GEB/Nows lifecycle listener classes |
mixin |
mod-owned Mixin configuration json files |
network-channel or network |
bidirectional network channels |
clientbound-channel |
channel that receives clientbound packets |
serverbound-channel |
channel that receives serverbound packets |
transformer |
custom class transformer declarations |
Mods can query loaded metadata through NowsContext:
if (context.isModLoaded("other_mod")) {
String name = context.requireModDescriptor("other_mod").name();
}
boolean clientRuntime = context.side() == NowsSide.CLIENT;
Use context.modDescriptors() for a snapshot of every loaded descriptor, context.mod(id) for the loaded container, and context.modsById() when a manager needs stable map lookup by mod id.