Search is available after the production docs build.

Browse Docs
DocsSDKECHO Native Mod Author Guide

SDK

ECHO Native Mod Author Guide

my-addon/ build.gradle src/main/java/.../MyAddon.java src/main/resources/META-INF/echo.mod.json src/main/resources/data/myaddon/... src/test/java/.../MyAddonTest.java

ECHO Native Mod Author Guide

Getting Started

  1. Clone or download the ECHO Native SDK.
  2. Apply the echo-sdk-gradle-plugin to your addon project.
  3. Pick a template matching your addon type.
  4. Write your addon descriptor, service registrations, and content.
  5. Validate with ./gradlew check and package with ./gradlew packageEchoNativeAddon.

Project Structure

my-addon/
  build.gradle
  src/main/java/.../MyAddon.java
  src/main/resources/META-INF/echo.mod.json
  src/main/resources/data/myaddon/...
  src/test/java/.../MyAddonTest.java

Descriptor

Every addon needs echo.mod.json:

{
  "schema": "echo.mod.v1",
  "id": "myaddon",
  "name": "My Addon",
  "version": "1.0.0-RC1",
  "entrypoint": "com.example.myaddon.MyAddon",
  "side": "common",
  "provides": ["myaddon.registry"],
  "access": {
    "nativeClasspath": ["addon.jar"]
  },
  "apiStability": "beta"
}

| Field | Required | Description | |---|---|---| | id | Yes | Unique modid. Lowercase, no spaces. | | name | Yes | Human-readable name. | | version | Yes | Semver string. | | entrypoint | Yes | Class implementing EchoNativeModuleEntrypoint. | | side | Yes | common, client, or server. | | provides | No | Feature IDs this addon provides. | | access.nativeClasspath | Yes | Release-mode classpath entries, usually addon.jar. |

Service Registration

Use EchoNativeModuleLoadContext to register addon services, then call typed host services for runtime mutations. Do not mint MUTATED receipts yourself; record the receipt returned by the host service.

public class MyAddon implements EchoNativeModuleEntrypoint {
    private static final String SERVICE_ID = "myaddon:registry_service";

    @Override
    public void registerServices(EchoNativeModuleLoadContext context) {
        context.registerService(SERVICE_ID, new MyRegistryService(), "registry");
    }

    @Override
    public void registerContent(EchoNativeModuleLoadContext context) {
        EchoNativeServiceMutation mutation = EchoNativeServiceMutation.of(
                "myaddon", "registry", "declare_content", "myaddon:example", EchoNativeRuntimeSide.COMMON);
        context.serviceRegistry()
                .service("echo.native.registry", EchoNativeRegistryService.class)
                .map(registry -> registry.register(mutation))
                .ifPresent(context::recordMutation);
    }
}

Keep registrations idempotent and safe to call multiple times during reloads.

Optional Integration

Never hard-reference optional addons. Use service lookup:

Optional<IndexService> index = EchoOptionalServices.index();
index.ifPresent(i -> i.registerProvider(myDocsProvider));

Build & Package

# Compile and run tests
./gradlew build

# Produce .echo-addon distribution
./gradlew packageEchoNativeAddon

Output lands in build/echo-native/addons/&lt;id&gt;-&lt;version&gt;.echo-addon.

Testing

Use the echo-native-testkit dependency for in-memory loader tests:

testImplementation 'dev.echo.native:echo-native-testkit:1.0.0-RC1'
@Test
public void testBootstrap() {
    EchoNativeSdkTestkit.Environment env = EchoNativeSdkTestkit.common("myaddon");
    EchoNativeModuleLoadContext context = env.loadEntrypoint(new MyAddon());
    assertTrue(context.serviceRegistry().hasService("myaddon:registry_service"));
    env.goldenParity().requireOnlyTypedReceipts();
}

Publishing

See Release Packaging Guide for artifact naming, checksums, and metadata requirements.