Skip to content

Mod Developer Guide

Yao Chung Hu edited this page Jul 4, 2026 · 2 revisions

Mod Developer Guide

Greenlight is for client features that should stay off on multiplayer unless the server explicitly allows them. Your mod owns the feature, config UI, and behavior. Greenlight only answers: did the current server grant this feature, and with what limits?

Use:

me.flashyreese.mods.greenlight.feature

Pick an Integration Style

If your code imports Greenlight API classes, one of these must be true:

  • You bundle greenlight-api with your mod for optional compatibility.
  • You require the full Greenlight runtime mod, which already contains the API classes.

Do not use compileOnly("me.flashyreese.mods:greenlight-api:...") by itself for optional support. Users without Greenlight would be missing the API classes at runtime.

Optional Compatibility

Use this when your mod should still launch for players who do not have Greenlight installed.

Compile against greenlight-api and bundle that API jar with your mod. Do not declare the full greenlight loader mod as required.

When the full runtime is missing:

  • Greenlight.isAvailable() returns false.
  • Feature registration is accepted.
  • Custom policy sources are ignored.
  • Every query returns denied.

Hard Dependency

Use this when your mod requires Greenlight to be installed.

Depend on the loader jar for your platform and declare greenlight as required in your mod metadata. Do not bundle greenlight-api separately.

Maven Repositories

repositories {
    maven("https://maven.flashyreese.me/releases")

    // Only for snapshot testing.
    maven("https://maven.flashyreese.me/snapshots")
}

Modrinth Maven can be used as a fallback for released runtime jars:

repositories {
    exclusiveContent {
        forRepository {
            maven {
                name = "Modrinth"
                url = uri("https://api.modrinth.com/maven")
            }
        }
        filter {
            includeGroup("maven.modrinth")
        }
    }
}

Prefer FlashyReese Maven when possible. It has the split API artifact and normal Maven metadata.

Coordinates

val greenlightVersion = "0.1.0+mc26.2"

FlashyReese Maven artifacts:

me.flashyreese.mods:greenlight-api:<version>
me.flashyreese.mods:greenlight-fabric:<version>
me.flashyreese.mods:greenlight-neoforge:<version>

Use greenlight-api for optional API support. Use the loader-specific artifact for hard dependencies.

Modrinth Maven fallback:

dependencies {
    modImplementation("maven.modrinth:EE4vxUHj:<modrinth-version-or-version-id>")
}

Do not use Modrinth Maven for the optional API path.

Optional API Setup

Fabric Loom:

dependencies {
    compileOnly("me.flashyreese.mods:greenlight-api:$greenlightVersion")
    include("me.flashyreese.mods:greenlight-api:$greenlightVersion")
}

Fabric metadata:

{
  "suggests": {
    "greenlight": ">=0.1.0"
  }
}

NeoForge metadata:

[[dependencies.yourmod]]
modId = "greenlight"
type = "optional"
versionRange = "[0.1.0,)"
ordering = "AFTER"
side = "CLIENT"

Do not relocate the me.flashyreese.mods.greenlight package.

Hard Dependency Setup

Fabric Loom:

dependencies {
    modImplementation("me.flashyreese.mods:greenlight-fabric:$greenlightVersion")
}

Fabric metadata:

{
  "depends": {
    "greenlight": ">=0.1.0"
  }
}

NeoForge metadata:

[[dependencies.yourmod]]
modId = "greenlight"
type = "required"
versionRange = "[0.1.0,)"
ordering = "NONE"
side = "CLIENT"

Register a Feature

public static final ClientFeature<CaveTintPolicy> CAVE_TINT = Greenlight
        .feature(Identifier.fromNamespaceAndPath("examplemod", "cave_tint"))
        .decoder(1, CaveTintPolicy::fromJson)
        .register();

The feature ID maps to this server policy file:

assets/examplemod/client_features/v1/cave_tint.json

Query at the Point of Use

Optional<CaveTintPolicy> policy = CAVE_TINT.policy();
if (policy.isEmpty()) {
    return; // No trusted server grant.
}

float strength = Math.min(localConfig.caveTintStrength(), policy.get().maxStrength());
applyCaveTint(strength);

Do not cache an allowed result across worlds or servers. The handle already caches decoded settings until Greenlight sees a policy change.

Decoder Rules

Your decoder should be strict:

  • Missing policy means denied.
  • enabled: false means denied.
  • Unknown settings_version means denied.
  • A thrown decoder exception means denied.
  • A decoder that returns null means denied.

Treat a grant as permission plus limits, not as a command to force the player's local setting on.

What to Document for Server Owners

Document this for every Greenlight feature:

  • Feature ID.
  • Policy file path.
  • Supported settings_version.
  • Full JSON example.
  • Every key inside settings.
  • Most restrictive behavior when a key is missing.

Custom Policy Sources

Most mods should use the built-in required resource-pack source.

Only register a FeaturePolicySource if you have another trusted server channel:

Greenlight.registerSource(new MyPayloadPolicySource());

A custom source must clear or change its fingerprint when the server context ends. Stale grants must not survive disconnects.

Clone this wiki locally