confikure is an annotation-driven config gui library for forge 1.8.9 mods. it turns plain java config objects into a searchable in-game settings screen and a json-backed config file, so mod settings can live next to the code that uses them.

confikure gui preview
the built-in forge screen, generated from annotated java fields.

why it exists

legacy forge mods usually end up choosing between hand-built GuiScreen code and larger config libraries that bring extra dependencies, abstractions, and ui that does not feel like minecraft. confikure keeps that surface small. you define normal java objects, mark sections with @Category and @Group, expose fields with @Option, then let Confikure.scan produce a ConfigDefinition that can be rendered and persisted.

it supports booleans, numeric ranges, dropdowns, modes, argb colors, keybinds, single-line and multiline text, info rows, search tags, and buttons through annotations like @Range, @Dropdown, @Mode, @Color, @Keybind, @Text, @Multiline, @Info, @SearchTag, and @Button. option id values are generated from names by default, but shipped settings can pin explicit ids so saved json keys stay stable across renames.

install

confikure is published as re.tsuku:confikure and is meant to be shaded into the mod jar. relocation is recommended because 1.8.9 modpacks often load many mods into the same classpath.

repositories {
    maven("https://maven.tsuku.re/releases")
}

val shadowModImpl by configurations.creating {
    configurations.modImplementation.get().extendsFrom(this)
}

dependencies {
    shadowModImpl("re.tsuku:confikure:1.0.0")
}

tasks.shadowJar {
    configurations = listOf(shadowModImpl)
    relocate("re.tsuku.confikure", "your.mod.deps.confikure")
}

basic shape

a config starts as a regular object graph. @Config names the root config, @Category and @Group build the sidebar structure, and @Option exposes fields as controls.

import re.tsuku.confikure.annotations.Category;
import re.tsuku.confikure.annotations.Config;
import re.tsuku.confikure.annotations.Option;
import re.tsuku.confikure.annotations.Range;

@Config(name = "examplemod", id = "examplemod", description = "examplemod settings")
public final class ExampleConfig {
    @Category(name = "general")
    public final General general = new General();

    public static final class General {
        @Option(name = "enabled")
        public boolean enabled = true;

        @Option(name = "speed", description = "movement multiplier")
        @Range(min = 0.0D, max = 3.0D, step = 0.25D)
        public double speed = 1.0D;
    }
}

when the mod needs a definition, it scans the object:

ConfigDefinition definition = Confikure.scan(new ExampleConfig());

from there the forge adapter can open the built-in screen with ForgeConfig, or the core gui can be embedded into a custom GuiScreen.

external usage: openutils

OpenUtils is my own forge 1.8.9 mod, not a tsukure project, but it is a useful real-world example of confikure in a larger codebase. it shades re.tsuku:confikure:1.1.3, relocates it under org.afterlike.openutils.lib, scans one root config object, loads values from config/openutils/config.json, and saves whenever an option changes.

the root config wires existing feature instances into @Category and @Group sections:

@Config(name = "OpenUtils", id = "openutils", description = "Mod settings", version = 1)
public final class OpenUtilsConfig {
    @Category(name = "Movement", order = 0)
    public final Movement movement;

    @Category(name = "Render", order = 2)
    public final Render render;

    @Category(name = "Client", order = 6)
    public final Client client;

    public OpenUtilsConfig(final FeatureHandler features) {
        this.movement = new Movement(features);
        this.render = new Render(features);
        this.client = new Client(features);
    }

    public static final class Movement {
        @Group(name = "Snap Tap",
                description = "Resolves simultaneous opposite movement inputs per axis.")
        public final NullMoveFeature nullMove;

        private Movement(final FeatureHandler features) {
            this.nullMove = features.getFeature(NullMoveFeature.class);
        }
    }
}

individual features own their own settings. for example, Snap Tap exposes @Option, @Mode, and stable ids directly on the feature class:

public class NullMoveFeature extends ToggleableFeature {
    @Option(name = "Enable Snap Tap",
            description = "Resolves simultaneous opposite movement inputs per axis.")
    public boolean enabled;

    @Option(name = "Forward / Back SOCD",
            description = "Resolve forward/back when both are held.")
    @Mode(values = {"Last Input Priority", "Absolute Priority", "Neutral"})
    public String forwardBackMode = "Last Input Priority";

    @Option(id = "forward-back-priority", name = "Absolute Priority",
            description = "Direction kept when forward and backward are both held.")
    @Mode(values = {"Forward", "Backward"})
    public String forwardBackPriority = "Forward";
}

OpenUtils also uses runtime metadata on the scanned ConfigDefinition. it marks the absolute-priority fields as conditional with visibleWhen, binds listeners to every option, and saves after changes:

this.config = new OpenUtilsConfig(OpenUtils.get().getFeatureHandler());
this.definition = Confikure.scan(this.config);

final ConfigOption forwardBackPriority =
        this.definition.option("forward-back-priority");

if (forwardBackPriority != null) {
    forwardBackPriority.visibleWhen(snapTap::showsForwardBackPriority);
}

that leaves the mod-specific screen free to focus on layout and rendering while confikure owns scanning, state, option listeners, and json persistence.