Skillshub minecraft-multiloader
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/Jahrome907/minecraft-codex-skills/minecraft-multiloader" ~/.claude/skills/comeonoliver-skillshub-minecraft-multiloader && rm -rf "$T"
manifest:
skills/Jahrome907/minecraft-codex-skills/minecraft-multiloader/SKILL.mdsource content
Minecraft Multiloader Skill (Architectury)
What Is Architectury?
Architectury is a framework that lets you write one mod codebase that compiles to both NeoForge and Fabric JARs. The common subproject has a shared API; platform subprojects implement platform-specific behavior behind the
@ExpectPlatform abstraction.
Routing Boundaries
: one shared codebase must build and ship both NeoForge and Fabric artifacts.Use when
: the project is single-loader only (Do not use when
for NeoForge/Fabric, not both).minecraft-modding
: the task is Paper/Bukkit plugin development (Do not use when
).minecraft-plugin-dev
| Component | Purpose |
|---|---|
| Gradle plugin — extends Fabric Loom for multiloader support |
| Runtime library — abstractions over both platforms |
| Annotation marking methods with platform-specific implementations |
| Shared code (no loader-specific APIs) |
| Fabric-specific code + entrypoint |
| NeoForge-specific code + entrypoint |
Versions (1.21.x)
# gradle.properties (root) minecraft_version=1.21.1 enabled_platforms=fabric,neoforge architectury_version=13.0.8 fabric_loader_version=0.16.9 fabric_api_version=0.114.0+1.21.1 neoforge_version=21.1.172 loom_version=1.9-SNAPSHOT
Root Project Layout
my-mod/ ├── build.gradle ← root build (shared config) ├── settings.gradle ├── gradle.properties ├── common/ │ ├── build.gradle │ └── src/main/java/com/example/mymod/ │ ├── MyMod.java ← shared init │ ├── registry/ │ │ └── ModItems.java ← shared registry declarations │ └── platform/ │ └── PlatformHelper.java ← @ExpectPlatform methods ├── fabric/ │ ├── build.gradle │ └── src/main/ │ ├── java/com/example/mymod/fabric/ │ │ ├── MyModFabric.java ← Fabric entrypoint │ │ └── platform/ │ │ └── PlatformHelperImpl.java ← Fabric implementation │ └── resources/ │ ├── fabric.mod.json │ └── assets/... └── neoforge/ ├── build.gradle └── src/main/ ├── java/com/example/mymod/neoforge/ │ ├── MyModNeoForge.java ← NeoForge @Mod entry │ └── platform/ │ └── PlatformHelperImpl.java ← NeoForge implementation └── resources/ ├── META-INF/neoforge.mods.toml └── assets/...
Root settings.gradle
settings.gradlepluginManagement { repositories { maven { url "https://maven.architectury.dev/" } maven { url "https://maven.fabricmc.net/" } maven { url "https://maven.neoforged.net/releases" } gradlePluginPortal() } } include "common" include "fabric" include "neoforge"
Root build.gradle
build.gradleplugins { id "architectury-plugin" version "3.4-SNAPSHOT" apply false id "dev.architectury.loom" version "${loom_version}" apply false id "com.github.johnrengelman.shadow" version "8.1.1" apply false } architectury { minecraft = rootProject.minecraft_version } subprojects { apply plugin: "java" apply plugin: "architectury-plugin" group = "com.example.mymod" version = "${mod_version}+${minecraft_version}" archivesBaseName = "my-mod-${project.name}" repositories { maven { url "https://maven.architectury.dev/" } maven { url "https://mod-buildtools.pkg.github.com/TerraformersMC/" } } java { withSourcesJar() sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } }
common/build.gradle
common/build.gradleplugins { id "dev.architectury.loom" apply true } architectury { common(rootProject.enabled_platforms.split(",")) } loom { // common project uses mappings only } dependencies { minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" mappings loom.officialMojangMappings() modImplementation "dev.architectury:architectury-api:${rootProject.architectury_version}" }
fabric/build.gradle
fabric/build.gradleplugins { id "com.github.johnrengelman.shadow" id "dev.architectury.loom" apply true } architectury { platformSetupLoomIde() fabric() } loom { accessWidenerPath = project(":common").loom.accessWidenerPath } configurations { common shadowCommon compileClasspath.extendsFrom common runtimeClasspath.extendsFrom common developmentFabric.extendsFrom common } dependencies { minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" mappings loom.officialMojangMappings() modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" modApi "dev.architectury:architectury-api:${rootProject.architectury_version}:fabric" common(project(path: ":common", configuration: "namedElements")) { transitive false } shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } } shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] archiveClassifier = "dev-shadow" } remapJar { injectAccessWidener = true input.fileValue shadowJar.archiveFile.get().asFile dependsOn shadowJar archiveClassifier = "" } jar { archiveClassifier = "dev" } sourcesJar { archiveClassifier = "dev-sources" } components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) { skip() }
neoforge/build.gradle
neoforge/build.gradleplugins { id "com.github.johnrengelman.shadow" id "dev.architectury.loom" apply true } architectury { platformSetupLoomIde() neoForge() } loom { accessWidenerPath = project(":common").loom.accessWidenerPath } configurations { common shadowCommon compileClasspath.extendsFrom common runtimeClasspath.extendsFrom common developmentNeoForge.extendsFrom common } dependencies { minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" mappings loom.officialMojangMappings() neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}" modApi "dev.architectury:architectury-api:${rootProject.architectury_version}:neoforge" common(project(path: ":common", configuration: "namedElements")) { transitive false } shadowCommon(project(path: ":common", configuration: "transformProductionNeoForge")) { transitive false } } shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] archiveClassifier = "dev-shadow" } remapJar { input.fileValue shadowJar.archiveFile.get().asFile dependsOn shadowJar archiveClassifier = "" } jar { archiveClassifier = "dev" }
Shared Common Code
common/.../MyMod.java
common/.../MyMod.javapackage com.example.mymod; import dev.architectury.registry.registries.DeferredRegister; import dev.architectury.registry.registries.RegistrySupplier; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; public class MyMod { public static final String MOD_ID = "mymod"; // Architectury's DeferredRegister — works on both platforms public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(MOD_ID, BuiltInRegistries.ITEM); public static final RegistrySupplier<Item> MY_ITEM = ITEMS.register("my_item", () -> new Item(new Item.Properties())); public static void init() { ITEMS.register(); // registers with both platforms } }
@ExpectPlatform
— platform-specific methods
@ExpectPlatformDefine the contract in
common/:
package com.example.mymod.platform; import dev.architectury.injectables.annotations.ExpectPlatform; import net.minecraft.world.level.material.Fluid; public class PlatformHelper { @ExpectPlatform public static boolean isModLoaded(String modId) { // This body is replaced at compile time by the platform implementation throw new AssertionError("ExpectPlatform implementation not found"); } @ExpectPlatform public static boolean isClient() { throw new AssertionError(); } }
Implement in
fabric/.../platform/PlatformHelperImpl.java:
package com.example.mymod.platform; import net.fabricmc.loader.api.FabricLoader; // Class name must match: <common class name>Impl public class PlatformHelperImpl { public static boolean isModLoaded(String modId) { return FabricLoader.getInstance().isModLoaded(modId); } public static boolean isClient() { return FabricLoader.getInstance().getEnvironmentType() == net.fabricmc.api.EnvType.CLIENT; } }
Implement in
neoforge/.../platform/PlatformHelperImpl.java:
package com.example.mymod.platform; import net.neoforged.fml.ModList; import net.neoforged.fml.loading.FMLEnvironment; public class PlatformHelperImpl { public static boolean isModLoaded(String modId) { return ModList.get().isLoaded(modId); } public static boolean isClient() { return FMLEnvironment.dist.isClient(); } }
Fabric Entrypoint
fabric/.../MyModFabric.java
fabric/.../MyModFabric.javapackage com.example.mymod.fabric; import com.example.mymod.MyMod; import net.fabricmc.api.ModInitializer; public class MyModFabric implements ModInitializer { @Override public void onInitialize() { MyMod.init(); } }
fabric/.../resources/fabric.mod.json
fabric/.../resources/fabric.mod.json{ "schemaVersion": 1, "id": "mymod", "version": "${version}", "name": "My Mod", "description": "A multiloader example mod", "license": "MIT", "environment": "*", "entrypoints": { "main": ["com.example.mymod.fabric.MyModFabric"] }, "depends": { "fabricloader": ">=0.16.0", "fabric-api": "*", "architectury": ">=13.0.0", "minecraft": "~1.21" } }
NeoForge Entrypoint
neoforge/.../MyModNeoForge.java
neoforge/.../MyModNeoForge.javapackage com.example.mymod.neoforge; import com.example.mymod.MyMod; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; @Mod(MyMod.MOD_ID) public class MyModNeoForge { public MyModNeoForge(IEventBus modEventBus) { MyMod.init(); } }
neoforge/.../resources/META-INF/neoforge.mods.toml
neoforge/.../resources/META-INF/neoforge.mods.tomlmodLoader = "javafml" loaderVersion = "[4,)" license = "MIT" [[mods]] modId = "mymod" version = "${file.jarVersion}" displayName = "My Mod" description = "A multiloader example mod" [[dependencies.mymod]] modId = "neoforge" type = "required" versionRange = "[21.1,)" ordering = "NONE" side = "BOTH" [[dependencies.mymod]] modId = "minecraft" type = "required" versionRange = "[1.21.1,)" ordering = "NONE" side = "BOTH"
Build Commands
# Build both JARs simultaneously ./gradlew build # Outputs: # fabric/build/libs/my-mod-fabric-1.0.0+1.21.1.jar # neoforge/build/libs/my-mod-neoforge-1.0.0+1.21.1.jar # Run in dev environment ./gradlew :fabric:runClient ./gradlew :neoforge:runClient ./gradlew :neoforge:runServer # Datagen (if applicable) ./gradlew :neoforge:runData
Common Pitfalls
| Pitfall | Solution |
|---|---|
Using / in | Only use vanilla MC and Architectury APIs in common |
Direct field access on (NeoForge style) in common | Use Architectury's |
Forgetting throws at runtime | Both and must have matching classes |
| Assets duplicated in fabric/ and neoforge/ | Keep assets in |
| Mixins in common — not supported on NeoForge | Put Mixins in the platform subprojects only |
| Accessing world/registry on mod init thread | Use events for setup; never access world on init |
References
- Architectury API GitHub: https://github.com/architectury/architectury-api
- Architectury Loom: https://github.com/architectury/architectury-loom
- Architectury templates: https://github.com/architectury/architectury-templates
- Architectury docs: https://docs.architectury.dev/