Developers
Module Template
The expected shape of a module: identity, dependencies, contracts, metadata, docs, and release notes.
Purpose
The module template is the expected public shape for an ECHO module or addon. It makes identity, runtime targets, dependencies, permissions, entrypoints, docs, and release artifacts visible before the module is treated as part of the platform.
The source of truth is the descriptor and package metadata, not a prose-only checklist:
META-INF/echo.mod.jsondescribes module identity and runtime contract.META-INF/echo-addon-package.jsondescribes.echo-addon, NeoForge, Standalone, and sources artifacts when packaged through the SDK.build.gradleapplies the ECHO SDK Gradle plugin for validation, generation, packaging, parity, and release bundle tasks.
When To Use This Guide
Use this guide when you are:
- Creating a new ECHO addon.
- Moving an existing first-party system into a releaseable module shape.
- Adding public docs or release metadata to a module.
- Preparing Native, NeoForge, or Standalone artifacts.
- Checking whether a module has hidden dependencies or undeclared permissions.
For the wider platform model, read Module System, Core Modules, PackOS, and Native SDK Artifacts.
Required Module Identity
Every public module should define:
- Stable
id. - Human-readable
name. version.kindandrole.entrypoint.- Runtime targets.
- Permissions.
- Required and optional dependencies when applicable.
- Provided and consumed contract IDs when applicable.
- Docs path.
- Release notes.
- Runtime artifact family.
Stable IDs matter. Changing a module ID is a compatibility event because docs, manifests, saves, dependencies, package metadata, and release-index entries can all reference it.
META-INF/echo.mod.json
Use the SDK schema value echo.mod.v1. A small content addon looks like this:
{
"schema": "echo.mod.v1",
"id": "hello_content_addon",
"name": "Hello Content Add-on",
"version": "0.1.0",
"kind": "content",
"role": "sample",
"runtimeTargets": ["echo_native", "neoforge", "echo_runtime_standalone"],
"domains": ["items", "blocks", "recipes"],
"permissions": ["registry.items", "registry.blocks", "registry.recipes"],
"entrypoint": "dev.echo.samples.hello.HelloContentAddon"
}
First-party modules may include additional platform fields used by the website and release tooling:
{
"schema": "echo.mod.v1",
"id": "echoterminal",
"name": "ECHO: Terminal",
"version": "1.0.0",
"type": "addon",
"kind": "ui_pack",
"role": "terminal_surface",
"entrypoint": "com.knoxhack.echoterminal.EchoTerminal",
"channel": "beta",
"official": true,
"standalone": true,
"side": "common",
"requires": ["echoadaptercore", "echocore", "echonetcore"],
"optional": ["echodatacore", "echoholomap", "echoindex", "echolens", "echomissioncore"],
"provides": ["terminal.surface"],
"consumes": ["echo.core", "echo.net", "pack.profile"],
"permissions": ["ui.screens", "diagnostics.read", "pack.read"],
"apiStability": "beta"
}
Use the smaller SDK sample shape for third-party authoring unless you intentionally own first-party platform fields.
META-INF/echo-addon-package.json
The .echo-addon package descriptor uses schemaVersion: "echo.addon.package.v1" and names each runtime artifact explicitly.
{
"schemaVersion": "echo.addon.package.v1",
"id": "hello_content_addon",
"version": "0.1.0",
"publisher": {
"githubOwner": "knoxhack",
"githubRepo": "ECHO-SDK"
},
"targets": ["native", "neoforge", "standalone"],
"dependencies": [],
"entrypoint": "dev.echo.samples.hello.HelloContentAddon",
"artifacts": {
"native": "hello_content_addon-0.1.0.echo-addon",
"neoforge": "hello_content_addon-0.1.0-neoforge.jar",
"standalone": "hello_content_addon-0.1.0-standalone.jar",
"sources": "hello_content_addon-0.1.0-sources.jar"
}
}
Artifact names matter because the launcher and Release Index resolve exact runtime families:
- Native Edition consumes
.echo-addon. - NeoForge Edition consumes
-neoforge.jar. - Standalone Edition consumes
-standalone.jar. - Developers and QA use
-sources.jar.
Build File
Use the SDK Gradle plugin and current Maven coordinates:
plugins {
id 'dev.echo.native.echo-sdk-gradle-plugin' version '1.0.0-RC'
}
echoSdk {
addonId = 'hello_content_addon'
}
dependencies {
implementation 'dev.echo.native:echoaddonapi:1.0.0-RC'
implementation 'dev.echo.native:echo-native-contracts:1.0.0-RC'
testImplementation 'dev.echo.native:echo-native-testkit:1.0.0-RC'
}
Run echoValidate before packaging, then build only the runtime families that match your descriptor.
Dependencies
Dependencies should be explicit.
Use requires for modules that must load first. Use optional for integrations that must degrade cleanly when absent. Use provides and consumes to make service contracts visible to reviewers, diagnostics, and future validation.
Dependency metadata should answer:
- What must load before this module?
- Which contracts does it provide?
- Which contracts does it consume?
- Which optional integrations can be disabled safely?
- Which permissions explain its registry, data, networking, or UI access?
- Which runtime targets are actually supported?
Docs And Release Notes
Each public module should include public-facing docs and release notes. A platform registry is more useful when users and developers can see what changed, not only the latest version number.
Minimum docs should cover:
- What the module owns.
- Which modules it requires.
- Which optional integrations it uses.
- Which player-facing systems it contributes to.
- Which runtime targets are packaged.
- What changed in the current release.
Implementation Checklist
idis lowercase, stable, and unique.schemaorschemaVersionmatches the SDK schema.entrypointexists and points to the actual addon class.- Runtime targets match packaged artifacts.
- Required and optional dependencies are declared.
- Permissions match actual access.
providesandconsumesidentify service boundaries.- Public sample code imports
dev.echo.api.*and JDK types. - Docs and GitHub paths are present.
- Release notes are understandable.
- PackOS and Release Index metadata can describe the module without guessing.
Common Mistakes
- Treating a module folder as the source of truth while metadata stays stale.
- Marking a prototype module as stable because it builds.
- Adding a UI integration without documenting which surface owns presentation.
- Depending on another module's internal classes instead of using a contract.
- Leaving planned modules with versions that imply release readiness.
- Packaging only one runtime family while declaring all three targets.
Next Step
After the descriptor exists, define the service boundary in Service Contracts. If the module has player-facing information, connect it through UI Integration.