Skip to main content

FAQ

Can I use Kotlin?

Yes. Add kotlin() to your version block:

version("1.21.1") {
kotlin() // uses 2.1.20 by default
kotlin("2.0.21") // or pick a version
}

This applies the Kotlin JVM plugin to common and all loader subprojects, with the JVM target matching the Minecraft version.

Can I use Yarn mappings?

Yes, for Fabric on obfuscated versions (pre-26.x):

fabric {
loaderVersion = "0.18.6"
yarn("1.21.1+build.3")
}

If yarn() is not set, Prism uses official Mojang mappings by default. On 26.x (unobfuscated), no mappings are needed and Yarn is not applicable.

Do I need common() for every version?

No. If a version only targets one loader, skip common():

version("1.21.1") {
neoforge() // single-loader mode, no common/loader split
}

The version folder itself becomes the project (versions/1.21.1/src/main/java/). No subdirectories needed. Build with ./gradlew :1.21.1:build.

Use common() only when you have multiple loaders and want shared code between them.

Can I use Fabric API in common code?

No. The version-specific common project (versions/{mc}/common/) only has access to vanilla Minecraft classes. Loader APIs like Fabric API, NeoForge events, or Forge events are only available in the loader-specific folders.

Put shared logic that doesn't touch loader APIs in common, and put loader-specific code (event handlers, registration) in the loader folders.

Can I share code between versions?

By default, no. Each version is fully independent.

If you have pure Java code (interfaces, annotations, utilities) that doesn't touch Minecraft APIs, enable the shared common:

// settings.gradle.kts
prism {
sharedCommon()
version("1.20.1") { ... }
version("1.21.1") { ... }
}

This creates a root common/ folder compiled with the lowest Java version across your targets. It has no access to Minecraft classes. See Project Structure for details.

How do I add dependencies?

Use the dependencies block inside each loader config, or common for shared dependencies:

version("1.21.1") {
common {
implementation("some:shared-lib:1.0")
}
fabric {
loaderVersion = "0.18.6"
dependencies {
modImplementation("curse.maven:jei-238222:4613379")
}
}
}

See Dependencies for details.

How do I use CurseMaven or Modrinth Maven?

prism {
curseMaven()
modrinthMaven()
}

Then use curse.maven:slug-projectId:fileId or maven.modrinth:slug:version in your dependency blocks.

How do I embed a library in my JAR?

Use jarJar() in the dependency block:

fabric {
dependencies {
jarJar("some:library:1.0") // uses Fabric's include
}
}
neoforge {
dependencies {
jarJar("some:library:[1.0,2.0)") // uses NeoForge's jarJar
}
}

How does datagen work?

Call datagen() in your fabric {} block (requires Fabric API). NeoForge and Forge datagen runs are registered automatically. On NeoForge 1.21.4+, Prism registers split clientData and serverData runs instead of a single data run. See Loaders for details.

How do I add custom run configurations?

Use the runs {} block inside any loader config. See Loaders — Custom run configurations for the full DSL including all run types and properties.

How do I escape to raw Gradle or the underlying plugin?

Use the raw hooks when Prism's DSL does not expose a setting yet:

version("1.21.1") {
rawCommonProject { project -> }

fabric {
rawLoom { loom -> }
rawProject { project -> }
}

forge {
rawLegacyForge { ext -> }
rawProject { project -> }
}

neoforge {
rawNeoForge { ext -> }
rawProject { project -> }
}
}

sharedCommon {
rawProject { project -> }
}

The underlying-plugin hooks run after Prism's own configuration, so they are suitable for overrides.

How do mixins work on 1.12.2?

On 1.12.2 Sponge Mixin is loaded via a coremod, and MixinBooter is the de-facto provider. Prism auto-wires everything when it detects mixin sources in a legacyForge { } project:

  • MixinBooter is added by default (pinned to 10.7) as both implementation and annotationProcessor, non-transitive. The CleanroomMC maven is registered automatically. Disable with legacyForge { mixinBooter = false } or pin a different version with mixinBooterVersion = "...".
  • Mixin JSONs under src/main/resources/*.mixins.json are registered via the MixinConfigs jar manifest attribute.
  • FML manifest attributes (FMLCorePlugin, FMLCorePluginContainsFMLMod, ForceLoadAsMod) are written to the jar so Forge actually loads your coremod.

FMLCorePlugin is auto-detected by scanning src/main/{java,kotlin} for a class with @IFMLLoadingPlugin.Name or implements IFMLLoadingPlugin. The detection is a text-based scan — if your loading plugin is nested, generated, or otherwise hard to match, set it explicitly:

legacyForge {
coreMod("com.example.mymod.MyLoadingPlugin")
}

If you have no IFMLLoadingPlugin at all (late-mixin mods via ILateMixinLoader), Prism still writes the other two manifest flags but skips FMLCorePlugin. No extra configuration is needed.

Non-mixin 1.12.2 mods (no @Mixin annotations anywhere) get nothing added — the auto-wire is gated on actually having mixins.

How do I control mixin auto-detection?

Use the mixins {} block on any loader. By default Prism auto-detects *.mixins.json files from src/main/resources recursively. See Loaders for the full mixins {} DSL.

How does publishing work?

See Publishing. Key points:

  • Display name on CurseForge/Modrinth defaults to the JAR filename (e.g. mymod-1.21.1-NeoForge-1.0.0.jar)
  • Override with displayName in the publishing block
  • Override the uploaded artifact with artifactTask() or artifactFile() when needed
  • Publishing dependencies (requires, optional, incompatible) can be set globally, per version, or per loader
  • All three levels stack

What build preconditions does Prism enforce?

Prism fails the build at configuration time when any of the following are detected:

  1. Mixin without refmap (MDG Legacy only). Any *.mixins.json under versions/{mc}/common/src/main/resources/ or versions/{mc}/forge/src/main/resources/ that declares non-empty mixins/client/server arrays must include a top-level "refmap": "<modid>.refmap.json" field. This applies to versions using forge { } (Forge < 1.21.1). Fabric does not need this because Loom writes the refmap itself.

  2. Missing pack.mcmeta. For every version, each per-version subproject (common, fabric, forge, lexforge, neoforge, legacyforge, or the single-loader root) whose src/main/resources/assets/ or src/main/resources/data/ contains any file must also have a src/main/resources/pack.mcmeta. Empty or missing assets/data folders are fine. Only the per-version projects are checked — the cross-version sharedCommon root :common is not.

  3. Forge mods on non-mod configurations (MDG Legacy only). On versions using forge { }, any dependency declared on implementation/api/compileOnly(Api)/runtimeOnly/custom configurations whose jar contains META-INF/mods.toml is rejected. Use modImplementation/modApi/modCompileOnly/modRuntimeOnly instead so MDG Legacy remaps the mod jar. The common project is also checked, because NeoForm-remapped common still pulls Forge mod jars through the loader.

All three errors print the offending file or dependency and the fix. Resolution happens through Gradle's normal artifact cache — the first build downloads the jar that would have been downloaded anyway; subsequent checks are free.

Is there a diagnostic task?

Yes:

./gradlew prismDoctor

It prints a full report per version and loader: the Gradle project path, underlying plugin, mapping mode, chosen publish artifact task, mod* configurations, and mixin auto-detect status. See DSL Reference — Doctor task for the full output format.

How do I add access wideners or access transformers?

Place the file in the appropriate location — Prism auto-detects it. Use accessWidener(path) in the version block for a single unified file shared across all loaders (auto-converted to AT format for Forge/NeoForge). See Loaders for placement details per loader and the class-tweaker format on 26.x+.

Can I use a single access widener for all loaders?

Yes, via accessWidener(path) in the version block. Prism uses it as-is for Fabric and converts it to accesstransformer.cfg for NeoForge and Forge. See Loaders for details.

How does the common project compile?

For 1.20.2+, the common subproject uses ModDevGradle with neoFormVersion (vanilla Minecraft, no loader).

For 1.20.1 and older, it uses MDG Legacy with mcpVersion set to the Minecraft version.

In both cases, common has full access to vanilla Minecraft classes but not loader APIs.

Common source files are recompiled as part of each loader's compilation, so they have access to the full loader classpath at build time.

Can I have multiple mods in one project?

Yes. Use mod() to create a multi-mod workspace where each module is an independent mod with its own modId, metadata, and publishing:

// settings.gradle.kts
prism {
mod("corpse-curios") {
version("1.21.1") { common(); neoforge(); fabric() }
}
mod("corpse-cosmetic") {
version("1.21.1") { neoforge() }
}
}

// build.gradle.kts
prism {
curseMaven()

mod("corpse-curios") {
metadata {
modId = "corpse_curios_compat"
name = "Corpse x Curios Compat"
}
version("1.21.1") {
neoforge { loaderVersion = "21.1.26" }
fabric { loaderVersion = "0.18.6"; fabricApi("0.102.1") }
}
publishing {
curseforge { projectId = "111111" }
}
}

mod("corpse-cosmetic") {
metadata {
modId = "corpse_cosmetic_compat"
name = "Corpse x Cosmetic Armor Compat"
}
version("1.21.1") {
neoforge { loaderVersion = "21.1.26" }
}
publishing {
curseforge { projectId = "222222" }
}
}
}

Directory layout:

modules/
corpse-curios/versions/1.21.1/common/
corpse-curios/versions/1.21.1/neoforge/
corpse-curios/versions/1.21.1/fabric/
corpse-cosmetic/versions/1.21.1/neoforge/

Gradle subprojects: :corpse-curios:1.21.1:neoforge, :corpse-cosmetic:1.21.1:neoforge, etc. Build a single module with ./gradlew :corpse-curios:1.21.1:neoforge:build or all with ./gradlew build.

Modules can coexist with the standard single-mod version() blocks if needed.

Inter-module dependencies

If one module needs compile-time access to another module's code, use dependsOn():

mod("core-lib") {
metadata { modId = "core_lib" }
version("1.21.1") { common(); neoforge(); fabric() }
}

mod("addon") {
dependsOn("core-lib")
metadata { modId = "addon" }
version("1.21.1") { common(); neoforge(); fabric() }
}

This gives the addon module compile-time visibility of core-lib's common code across all matching versions and loaders. At runtime, both mods must be installed. You can pass multiple module names: dependsOn("core-lib", "other-mod").

Prism wires the dependency using compiled class output from the dependency module's common project, so there are no remapping issues across Fabric, Forge, and NeoForge.

See Project Structure — Multi-mod workspace for the full directory layout reference and how modules combine with standard version() blocks.

NeoForm version resolution fails

  1. Check your internet connection. Prism fetches from maven.neoforged.net.
  2. If offline, set neoFormVersion manually in the version block.
  3. Cache is at ~/.gradle/caches/prism/neoform-versions.txt (24h TTL). Delete to refresh.

Run configurations are missing

Reload the Gradle project in IntelliJ after changing the Prism configuration. Runs are generated during Gradle sync.

What Gradle version do I need?

Gradle 8.8 or newer. 9.x recommended.

What Java version do I need?

You need the highest JDK required by any of your targets. Prism sets the correct toolchain per version automatically:

MinecraftJava
1.18.x - 1.20.x17
1.21.x21
26.x25

Add the foojay toolchain resolver so Gradle auto-downloads missing JDKs:

plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
}

Override with javaVersion in the version block.

If you need to compile against a dependency that uses newer bytecode than your target Minecraft version supports, set compileJdk higher than javaVersion. Prism will use the newer JDK to run the compiler but emit class files at javaVersion:

version("1.20.1") {
// ship class file 17 for 1.20.1 users on Java 17
javaVersion = 17
// but compile on JDK 21 so we can read a Java-21 library on the classpath
compileJdk = 21
}