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
| Field | Required | Content |
|---|---|---|
schemaVersion | Yes | 1 |
id | Yes | Mod ID. Lowercase letters, digits, and underscores. The reserved IDs plexus, poly, minecraft, and java cannot be used (PLEXUS-DEPENDENCY-008) |
version | Yes | Mod version (SemVer) |
name | Yes | Display name |
environment | Yes | both, client, server |
minecraft | Yes | List 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.
| Feature | Required permission |
|---|---|
replacesSystems | system.provider.replace |
transforms | internal.bytecode.transform |
redirect_call in transforms | internal.bytecode.transform, internal.callsite.intercept |
accessWidenings | internal.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?}.provideris a class that implementsSystemProvider. - Conflicts are resolved before any mod code runs. If there are 2 or more replacements, only one mod wins: the one with
override: truethat (transitively) depends on all the other replacing mods. In every other case, startup stops withPLEXUS-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
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"
}
]
}
| Field | Content |
|---|---|
id | <mod ID>:<path>. You cannot use another mod's namespace |
minecraft | The exact game version the rule applies to |
sides | client, dedicated_server |
target | owner (internal name), method, descriptor (JVM descriptor) |
action.type | See the action table below |
after | A 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:…) |
onFailure | abort_startup or disable_rule (default) |
action.type | Arguments | Meaning |
|---|---|---|
replace_returned_string_constant | expected, replacement | Replaces the constant when the body is exactly LDC <expected>; ARETURN. Cannot be used on the same method as another rule |
invoke_hook_at_head | hookOwner, 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_return | hookOwner, hookMethod | Calls the hook right before every return. The hook receives this, the arguments, and the return value, and returns the value to return |
redirect_call | invokedOwner, 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_callrules 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"]
}
]
}
| Field | Meaning |
|---|---|
kind | class, method, field. A class has no name or descriptor |
access | accessible (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) |
onFailure | abort_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 finalconstant field has its value copied into the code that reads it, so changing it tomutabledoes 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
| Field | Status |
|---|---|
provides | Reserved. If it is not empty, it is rejected with PLEXUS-METADATA-010 |
An unknown field gives the warning PLEXUS-METADATA-011.