Skip to content
Back to skills

Fabric Project Setup

ASecurity

Set up or repair a Minecraft Fabric mod project - build.gradle, gradle.properties, settings.gradle, fabric.mod.json, entrypoints, mixin configs, mappings choice and the client/common source split. Use when creating a new Fabric mod, when a Fabric build fails to configure, when choosing Loom/Loader/Fabric API versions, or when code ends up on the wrong side (client vs server).

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentsrustgojavabashgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add vvs1off/fabric-skills --skill fabric-project-setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fabric Project Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fabric Project Setup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vvs1off-fabric-project-setup/badge)](https://www.skillsdirectory.com/skills/vvs1off-fabric-project-setup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fabric-project-setup
description: Set up or repair a Minecraft Fabric mod project - build.gradle, gradle.properties, settings.gradle, fabric.mod.json, entrypoints, mixin configs, mappings choice and the client/common source split. Use when creating a new Fabric mod, when a Fabric build fails to configure, when choosing Loom/Loader/Fabric API versions, or when code ends up on the wrong side (client vs server).
---

# Fabric project setup

This skill gets a Fabric mod project into a state where `./gradlew build` works and the
structure matches what current Fabric tooling expects.

## Step 0 - Find out which era you are in (do this first)

Fabric changed fundamentally at **Minecraft 26.1**: the game ships **unobfuscated**, Yarn is
**discontinued**, and Loom has a new plugin id. Almost every "it doesn't compile" problem an
agent causes comes from mixing the two eras.

| | Minecraft **26.1 and newer** | Minecraft **1.21.11 and older** |
|---|---|---|
| Names in code | Mojang official names (`Minecraft`, `GuiGraphicsExtractor`, `KeyMapping`) | Yarn (`MinecraftClient`, `DrawContext`, `KeyBinding`) **or** Mojang mappings |
| `mappings` line in `build.gradle` | **none** - there is nothing to map | `mappings "net.fabricmc:yarn:..."` or `mappings loom.officialMojangMappings()` |
| Loom plugin id | `net.fabricmc.fabric-loom` | `net.fabricmc.fabric-loom-remap` (legacy alias: `fabric-loom`) |
| Mod dependency configs | plain `implementation`, `compileOnly`, `runtimeOnly` | `modImplementation`, `modCompileOnly`, `modLocalRuntime` |
| Output jar task | `jar` | `remapJar` |
| Java | check the template (26.x templates use Java 25) | Java 21 for 1.20.5 - 1.21.x, Java 17 for 1.18 - 1.20.4 |

How to tell which one an **existing** project is in:

```bash
grep -nE "fabric-loom|mappings|modImplementation" build.gradle
grep -nE "minecraft_version|yarn_mappings" gradle.properties
```

- `yarn_mappings` / `mappings "net.fabricmc:yarn` -> Yarn era. Do not add Mojang names to it.
- No `mappings` line and plugin `net.fabricmc.fabric-loom` -> unobfuscated era.
- If you are asked to move a Yarn project to 26.1+, that is a port: use the
  `fabric-version-porting` skill, not this one.

## Step 1 - Look up current versions (never hardcode from memory)

Version numbers in your training data are stale. Fetch them every time:

| What | Where |
|---|---|
| Minecraft, Loader, Loom, Fabric API for a given MC version | https://fabricmc.net/develop/ (pick the version in the dropdown) |
| Machine-readable game/loader versions | `https://meta.fabricmc.net/v2/versions/game` and `https://meta.fabricmc.net/v2/versions/loader` |
| Loader + mappings for one game version | `https://meta.fabricmc.net/v2/versions/loader/<mc_version>` |
| Yarn builds (pre-26.1 only) | `https://meta.fabricmc.net/v2/versions/yarn/<mc_version>` |
| Fabric API builds | https://modrinth.com/mod/fabric-api/versions (version looks like `0.x.y+<mc_version>`) |
| Loom | the `loom_version` in https://github.com/FabricMC/fabric-example-mod (`gradle.properties`) |
| Canonical template | https://github.com/FabricMC/fabric-example-mod or the generator at https://fabricmc.net/develop/template/ |
| Docs for your exact version | https://docs.fabricmc.net/develop/ (use the version selector in the header) |

| Latest Minecraft release vs snapshot (fallback if meta is unreachable) | `https://piston-meta.mojang.com/mc/game/version_manifest_v2.json` -> `latest.release` |
| Fabric API builds as Maven metadata | `https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml` (pick versions ending in `+<your mc version>`) |

If you cannot reach the network, say so and leave clearly marked placeholders
(`minecraft_version=<CHECK fabricmc.net/develop>`) instead of guessing.

Prefer the **latest stable** Minecraft release (meta marks it `"stable": true`; Mojang's
manifest lists it as `latest.release`); do not pick a snapshot, pre-release or release
candidate unless asked. Beware: Fabric API is also built for snapshots, so the `<latest>` in
the Maven metadata is often a snapshot build (e.g. `+26.4` while 26.4 is still a snapshot).
Never treat "a Fabric API build exists" as "this Minecraft version is released".

## Step 2 - Standard layout

```
examplemod/
├── build.gradle
├── gradle.properties
├── settings.gradle
├── gradle/wrapper/...            # commit the wrapper; never hand-edit the jar
├── gradlew / gradlew.bat
└── src/
    ├── main/                     # common code: runs on BOTH client and dedicated server
    │   ├── java/com/example/examplemod/
    │   │   ├── ExampleMod.java           # ModInitializer
    │   │   └── mixin/                    # common mixins
    │   └── resources/
    │       ├── fabric.mod.json
    │       ├── examplemod.mixins.json
    │       └── assets/examplemod/{icon.png, lang/en_us.json}
    └── client/                   # client-only code: rendering, screens, keybinds, HUD
        ├── java/com/example/examplemod/client/
        │   ├── ExampleModClient.java     # ClientModInitializer
        │   └── mixin/                    # client mixins
        └── resources/
            └── examplemod.client.mixins.json
```

The `src/client` source set is created by `splitEnvironmentSourceSets()`. Code in `src/main`
**cannot** import anything from `src/client` - the compiler enforces the side split, which is
the whole point. If common code needs to trigger client behaviour, send a packet or use an
interface implemented on the client side.

## Step 3 - Build files (unobfuscated era, 26.1+)

`gradle.properties` - fill every value from Step 1:

```properties
org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true
org.gradle.configuration-cache=true

# Fabric - look these up on https://fabricmc.net/develop
minecraft_version=<from fabricmc.net/develop>
loader_version=<from fabricmc.net/develop>
loom_version=<from fabric-example-mod>
fabric_api_version=<from fabricmc.net/develop>

# Mod
mod_version=0.1.0
maven_group=com.example
archives_base_name=examplemod
```

`settings.gradle`:

```groovy
pluginManagement {
    repositories {
        maven { name = 'Fabric'; url = 'https://maven.fabricmc.net/' }
        mavenCentral()
        gradlePluginPortal()
    }
}
rootProject.name = 'examplemod'
```

`build.gradle`:

```groovy
plugins {
    id 'net.fabricmc.fabric-loom' version "${loom_version}"
    id 'maven-publish'
}

version = project.mod_version
group = project.maven_group
base { archivesName = project.archives_base_name }

repositories {
    // Only add repositories for other mods you depend on (Mod Menu, YACL, ...).
    // Loom already adds Fabric, Mojang and Maven Central.
}

loom {
    splitEnvironmentSourceSets()
    mods {
        "examplemod" {                      // must equal the "id" in fabric.mod.json
            sourceSet sourceSets.main
            sourceSet sourceSets.client
        }
    }
}

dependencies {
    minecraft "com.mojang:minecraft:${project.minecraft_version}"
    implementation "net.fabricmc:fabric-loader:${project.loader_version}"
    implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
}

processResources {
    def version = project.version
    inputs.property "version", version
    filesMatching("fabric.mod.json") { expand "version": version }
}

tasks.withType(JavaCompile).configureEach {
    it.options.release = 25                 // match the template for your MC version
}

java {
    withSourcesJar()
    sourceCompatibility = JavaVersion.VERSION_25
    targetCompatibility = JavaVersion.VERSION_25
}

jar {
    def projectName = project.name
    inputs.property "projectName", projectName
    from("LICENSE") { rename { "${it}_${projectName}" } }
}
```

### Differences for 1.21.11 and older (remapping Loom)

```groovy
plugins {
    id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"   // older templates: 'fabric-loom'
}
dependencies {
    minecraft "com.mojang:minecraft:${project.minecraft_version}"
    mappings loom.officialMojangMappings()          // or: "net.fabricmc:yarn:${project.yarn_mappings}:v2"
    modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
    modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
}
```

Java release: 21 for 1.20.5 - 1.21.x, 17 for 1.18 - 1.20.4. The distributable jar is produced by
`remapJar` (the plain `jar` output is the `-dev` jar and will not load in a real game).

### Choosing mappings for pre-26.1 projects

- **New** pre-26.1 project: prefer Mojang mappings (`loom.officialMojangMappings()`). It makes
  a later port to 26.1+ a version bump instead of a rename of every symbol.
- **Existing** Yarn project: keep Yarn until you port; switching mappings is a separate,
  deliberate migration (`migrateMappings`), never a side effect of another change.
- Never mix: a Yarn project cannot reference `GuiGraphics`, a Mojang project cannot reference
  `DrawContext`.

## Step 4 - fabric.mod.json

```json
{
  "schemaVersion": 1,
  "id": "examplemod",
  "version": "${version}",
  "name": "Example Mod",
  "description": "What the mod does, in one sentence.",
  "authors": ["YOUR_NAME"],
  "contact": {
    "homepage": "https://example.com",
    "sources": "https://github.com/YOUR_NAME/examplemod",
    "issues": "https://github.com/YOUR_NAME/examplemod/issues"
  },
  "license": "MIT",
  "icon": "assets/examplemod/icon.png",
  "environment": "*",
  "entrypoints": {
    "main": ["com.example.examplemod.ExampleMod"],
    "client": ["com.example.examplemod.client.ExampleModClient"]
  },
  "mixins": [
    "examplemod.mixins.json",
    { "config": "examplemod.client.mixins.json", "environment": "client" }
  ],
  "depends": {
    "fabricloader": ">=<loader_version>",
    "minecraft": "~<minecraft_version>",
    "java": ">=<java_release>",
    "fabric-api": ">=<fabric_api_version without +mc suffix>"
  }
}
```

Rules that are easy to get wrong:

- `id`: 2-64 chars, lowercase `a-z`, digits, `-`, `_`; must start with a letter. It must match
  the `loom { mods { ... } }` key and your asset namespace (`assets/<id>/`).
- `"version": "${version}"` is expanded by `processResources`; do not hardcode it.
- `"environment"`: `"*"` (both), `"client"` (client-only mod, e.g. HUD/minimap mods) or
  `"server"`. A client-only mod should still keep client code in `src/client`.
- `"fabric-api"`: prefer a lower bound such as `">=0.165.0"` (the version you build against)
  over `"*"`, so players with an older Fabric API get a clear Loader error instead of a
  `NoClassDefFoundError` crash.
- `"minecraft"` range: `~1.21.4` means `>=1.21.4 <1.22`; for year-based versions `~26.3` means
  `>=26.3 <26.4`. Use an exact or tilde range - `"*"` lets users crash on incompatible versions.
- Every entrypoint class must exist at that exact fully-qualified name.
- Optional integrations go in `"suggests"` (soft) or `"recommends"`, never in `"depends"`.
  Example: `"suggests": { "modmenu": "*" }` plus a `"modmenu"` entrypoint.
- Schema reference: https://docs.fabricmc.net/develop/loader/fabric-mod-json

## Step 5 - Entrypoints

```java
package com.example.examplemod;

import net.fabricmc.api.ModInitializer;
import net.minecraft.resources.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExampleMod implements ModInitializer {
    public static final String MOD_ID = "examplemod";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);

    @Override
    public void onInitialize() {
        // Common setup: registries, payload types, server events, commands.
        LOGGER.info("Initializing {}", MOD_ID);
    }

    public static Identifier id(String path) {
        return Identifier.fromNamespaceAndPath(MOD_ID, path);
    }
}
```

```java
package com.example.examplemod.client;

import net.fabricmc.api.ClientModInitializer;

public class ExampleModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        // Client setup: key mappings, HUD elements, screens, client packet receivers.
    }
}
```

Name check for older versions: `Identifier.fromNamespaceAndPath` is the Mojang name from
1.21.11 onward. Mojang-mapped 1.21 - 1.21.10 call it `ResourceLocation.fromNamespaceAndPath`,
and Yarn calls it `Identifier.of(...)`. Verify against your generated sources instead of
trusting this line.

## Step 6 - Mixin configs

`src/main/resources/examplemod.mixins.json`:

```json
{
  "required": true,
  "package": "com.example.examplemod.mixin",
  "compatibilityLevel": "JAVA_25",
  "mixins": [],
  "injectors": { "defaultRequire": 1 },
  "overwrites": { "requireAnnotations": true }
}
```

`src/client/resources/examplemod.client.mixins.json` is the same with
`"package": "com.example.examplemod.client.mixin"` and the classes listed under `"client"`
instead of `"mixins"`. Set `compatibilityLevel` to your Java release (`JAVA_21` for 1.21.x).
Mixin classes listed here must live **in exactly that package**, and the package must contain
**only** mixin classes. See the `fabric-mixins` skill before writing any.

## Step 7 - Verify

```bash
./gradlew build          # must pass before you claim the setup works
./gradlew genSources     # decompiled Minecraft sources: the source of truth for names
./gradlew runClient      # or runServer, to smoke-test the entrypoints
```

After `genSources`, look up any vanilla class or method name in the generated sources (or with
your IDE's "go to class") **before** writing code against it. Names you remember may be from
another version or another mapping set.

## Common mistakes

- Copying a 1.20/1.21 Yarn tutorial into a 26.x project (`MinecraftClient`, `DrawContext`,
  `Text.literal`, `modImplementation`, a `mappings` line). None of it compiles on 26.1+.
- Adding `mappings loom.officialMojangMappings()` to a 26.1+ project. There is nothing to map;
  remove the line.
- Using `id 'fabric-loom'` on 26.1+ - it is the legacy remapping plugin id.
- Using `modImplementation` with non-remapping Loom, or plain `implementation` for a mod
  dependency with remapping Loom (the mod will not be remapped and will crash at runtime).
- Hardcoding versions from memory, or picking a snapshot as "latest".
- Putting client classes (`Minecraft`, `GuiGraphicsExtractor`, `KeyMapping`, anything in
  `net.minecraft.client`) in `src/main`. It compiles in a single-source-set project and then
  crashes a dedicated server with `ClassNotFoundException` / "attempted to load class ... for
  invalid dist".
- Mod id mismatch between `fabric.mod.json`, `loom.mods`, `MOD_ID` and the `assets/` folder.
- A mixin config `package` that does not match where the classes are, or non-mixin classes
  inside the mixin package.
- Declaring the client mixin config without `"environment": "client"`.
- Forgetting `processResources` expansion and shipping a jar whose version is literally
  `${version}`.
- Distributing the `-dev` jar (pre-26.1) instead of the `remapJar` output.
- Committing `run/`, `build/`, `.gradle/` or anything containing credentials.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.