Skip to main content
Version: Minecraft 26.2

poly.mod.json schema

Put poly.mod.json (schema 1) at the root of the mod jar. The validatePolyModJson task in build and the checker at Poly Mod Loader launch use the same rules. They collect all problems at once and report them as POLY-METADATA-*. All fields are PREVIEW.

Basic fields​

FieldRequiredContent
schemaVersionYes1
idYesMod ID. Lowercase letters, digits, and underscores. The reserved IDs poly, minecraft, and java cannot be used (POLY-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: "poly": ">=0.1.0-alpha <0.2.0"
conflicts—List of mod IDs that cannot be loaded together (POLY-DEPENDENCY-005)

Capabilities — capabilities​

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

The permission IDs Poly 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 Poly 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 only system you can replace today is poly:game_brand (the game brand string).

{
"capabilities": ["system.provider.replace"],
"replacesSystems": [
{ "system": "poly:game_brand", "provider": "com.example.poly.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 POLY-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 Poly 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 (Poly 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 Poly Mod Loader built-in rules (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 Poly Mod Loader's own) are applied together only when after defines a single order. Otherwise all of them are rejected (POLY-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 POLY-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 Poly 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 Poly Mod Loader built-in rule, both rules are rejected (POLY-TRANSFORM-004).

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

Reserved fields​

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

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

Buy me a coffee