Skip to main content
Version: v1.4.2

plexus.mod.json schema

Put plexus.mod.json (schema 1) at the root of the mod jar. Plexus Mod Loader searches for plexus.mod.json first, while poly.mod.json remains 100% backward-compatible for existing mods. The validatePolyModJson task in build and the checker at Plexus Mod Loader launch use the same rules. They collect all problems at once and report them as PLEXUS-METADATA-*. All fields are PREVIEW.

Basic fields​

FieldRequiredContent
schemaVersionYes1
idYesMod ID. Lowercase letters, digits, and underscores. The reserved IDs plexus, poly, minecraft, and java cannot be used (PLEXUS-DEPENDENCY-008)
versionYesMod version (SemVer)
nameYesDisplay name
environmentYesboth, client, server
minecraftYesList of supported Minecraft versions. An exact version ("26.2"), a wildcard ("26.*"), or a range (">=26.2 <27")
entrypoints—A list of class names for each key: common, client, server
depends—Mod ID → version range. Example: "plexus": ">=1.0.0 <2.0.0" (or "poly")
conflicts—List of mod IDs that cannot be loaded together (PLEXUS-DEPENDENCY-005)

Capabilities — capabilities​

To use a risky or deep feature, you must declare its permission in capabilities first. A missing required permission gives PLEXUS-METADATA-012, and an unknown permission gives the warning PLEXUS-METADATA-013.

The permission IDs Plexus Mod Loader knows today: content.registry.register, gameplay.rules.extend, worldgen.provider.replace, render.pipeline.extend, system.provider.replace, internal.member.access, internal.callsite.intercept, internal.bytecode.transform, runtime.bootstrap.modify, network.protocol.extend, world.data.migrate.

Today Plexus Mod Loader actually checks the features below.

FeatureRequired permission
replacesSystemssystem.provider.replace
transformsinternal.bytecode.transform
redirect_call in transformsinternal.bytecode.transform, internal.callsite.intercept
accessWideningsinternal.member.access

System replacement — replacesSystems​

A mod changes the implementation of a game system. The primary system you can replace today is plexus:game_brand (the game brand string, previously poly:game_brand).

{
"capabilities": ["system.provider.replace"],
"replacesSystems": [
{ "system": "plexus:game_brand", "provider": "com.example.brand.ExampleBrandProvider" }
]
}
  • Format: {system, provider, override?}. provider is a class that implements SystemProvider.
  • Conflicts are resolved before any mod code runs. If there are 2 or more replacements, only one mod wins: the one with override: true that (transitively) depends on all the other replacing mods. In every other case, startup stops with PLEXUS-SYSTEM-001.
  • The active systems and providers are recorded in the log and in .poly/startup-report.json.

Example: examples/custom-system-mod in the Plexus Mod Loader repository.

Code transforms — transforms​

UNSAFE by nature

A transform declared by a mod changes game bytecode directly. You must recheck it for every game version. The only supported actions are a closed set reviewed by the engine.

{
"capabilities": ["internal.bytecode.transform"],
"transforms": [
{
"id": "transform_demo_mod:storage_format_name",
"minecraft": "26.2",
"sides": ["client", "dedicated_server"],
"target": {
"owner": "net/minecraft/world/level/storage/LevelStorageSource",
"method": "getName",
"descriptor": "()Ljava/lang/String;"
},
"action": {
"type": "replace_returned_string_constant",
"expected": "Anvil",
"replacement": "Anvil (Plexus Mod Loader transform demo)"
},
"onFailure": "disable_rule"
}
]
}
FieldContent
id<mod ID>:<path>. You cannot use another mod's namespace
minecraftThe exact game version the rule applies to
sidesclient, dedicated_server
targetowner (internal name), method, descriptor (JVM descriptor)
action.typeSee the action table below
afterA list of rule IDs that must run first on the same method (optional). You can also list Plexus Mod Loader built-in rules (plexus:… or poly:…)
onFailureabort_startup or disable_rule (default)
action.typeArgumentsMeaning
replace_returned_string_constantexpected, replacementReplaces the constant when the body is exactly LDC <expected>; ARETURN. Cannot be used on the same method as another rule
invoke_hook_at_headhookOwner, hookMethod, result (continue, cancel_if_true, return_if_non_null)Calls a static hook at the start of the method. The hook receives this (if not static) and the arguments
invoke_hook_before_returnhookOwner, hookMethodCalls the hook right before every return. The hook receives this, the arguments, and the return value, and returns the value to return
redirect_callinvokedOwner, invokedName, invokedDescriptor, hookOwner, hookMethod, ordinal (optional, "all" by default or a number counted from 0)Replaces that call inside the method with a static hook call. The hook receives the receiver (none for a static call) and the arguments, and returns the same type. Also needs the permission internal.callsite.intercept

Class names accept both com/example/Hooks and com.example.Hooks. A hook's descriptor must match the target exactly. If game types appear, that mod must be compiled against the game jar.

Several rules on the same method​

Rules that change the same method (including rules from other mods and Plexus Mod Loader's own) are applied together only when after defines a single order. Otherwise all of them are rejected (PLEXUS-TRANSFORM-004).

  • Start hooks and before-return hooks run in declaration order. A before-return hook receives the result of the previous hook.
  • If a start hook cancels or returns a value, the before-return hooks do not run.
  • Two redirect_call rules that change the same call cannot be combined, and neither can a constant replacement.

Access widening — accessWidenings​

Opens a game class, method, or field wider so mod code can use it directly. The meaning is the same as the Fabric access widener. It needs the permission internal.member.access.

{
"capabilities": ["internal.member.access"],
"accessWidenings": [
{
"minecraft": "26.2",
"sides": ["client", "dedicated_server"],
"owner": "net/minecraft/server/MinecraftServer",
"kind": "field",
"name": "tickCount",
"descriptor": "I",
"access": ["accessible"]
}
]
}
FieldMeaning
kindclass, method, field. A class has no name or descriptor
accessaccessible (make it public; a private method also becomes non-final), extendable (class or method: remove final; a method becomes at least protected, and calls inside the owner become virtual calls), mutable (field: remove final)
onFailureabort_startup (default) or skip. If the target is missing, the error is PLEXUS-TRANSFORM-010
  • If several mods widen the same target, the changes are merged and do not conflict.

  • It applies only at run time. A static final constant field has its value copied into the code that reads it, so changing it to mutable does not affect that code.

  • Mod rules go through the same planning stage as Plexus Mod Loader built-in rules: target exists, descriptor, bytecode preconditions, game version, and conflicts with all other rules.

  • If a mod changes the same method as a Plexus Mod Loader built-in rule, both rules are rejected (PLEXUS-TRANSFORM-004).

Example: examples/transform-demo-mod in the Plexus Mod Loader repository.

Reserved fields​

FieldStatus
providesReserved. If it is not empty, it is rejected with PLEXUS-METADATA-010

An unknown field gives the warning PLEXUS-METADATA-011.

Buy me a coffee