Dynamic Registry 26.2
An introduction to dynamic registries - what they are, when they are useful, and how to create your own with the Fabric API.
A registry is a centralized "phonebook" that maps unique IDs, such as minecraft:items, to specific objects.
There are two kinds of registries: static registries, such as the block and item registries, are frozen during startup, whereas dynamic registries, or custom registries, are populated at runtime from JSON files in data packs.
They are useful for many reasons:
- They separate logic from content.
- Other modders can add new content through data packs instead of patching your code.
- Players can override default values, such as mana cost or upgrade price, by replacing data entries in a data pack.
- Dynamic registry data is world-specific. It loads when a world opens and is cleared when that world closes.
- Dynamic registries solve the "hardcoded content" problem. Instead of baking every skill, quest, or upgrade directly into Java code with enums or static lists, you define a blueprint in code and let the actual content come from data.
Let's create a dynamic registry for a magic skill system.
Class Setup
First, create the class that represents a registry entry. It is a simple data holder for values tied to each magic skill like name, mana cost, etc. A Codec is required to encode and decode the entry.
java
public record MagicSkillsRegistryEntry(String name, int manaCost, Optional<CacheableFunction> onUseMcFunction) {
public static final Codec<MagicSkillsRegistryEntry> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.STRING.fieldOf("name").forGetter(MagicSkillsRegistryEntry::name),
Codec.INT.fieldOf("mana_cost").forGetter(MagicSkillsRegistryEntry::manaCost),
CacheableFunction.CODEC.optionalFieldOf("on_use_mc_function").forGetter(MagicSkillsRegistryEntry::onUseMcFunction)
).apply(instance, MagicSkillsRegistryEntry::new)
);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
nameis the name of the skill.manaCostis the mana cost of the skill.onUseMcFunctionis a function that the server can execute when the skill is used. Having this in the registry will let other datapacks customize the logic of any skill, or add new skills with custom functions.
Registering the Registry
Each registry is registered with a key that uniquely identifies it, so let's create that key and a class to hold it. We'll call this class ExampleModRegistries:
TIP
Declaring the registry keys in a common class is recommended because it will make it easier to manage multiple registries.
java
public class ExampleModRegistries {
public static final ResourceKey<Registry<MagicSkillsRegistryEntry>> MAGIC_SKILLS_REGISTRY_KEY =
ResourceKey.createRegistryKey(ExampleMod.id("magic_skills_registry"));
public static void initialize() {
// Register Code Here
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Call ExampleModRegistries.initialize() from your mod's initializer.
java
public class ExampleModDynamicRegistries implements ModInitializer {
@Override
public void onInitialize() {
ExampleModRegistries.initialize();
}
}1
2
3
4
5
6
2
3
4
5
6
Then register it with Fabric API's DynamicRegistries, which provides two distinct strategies: DynamicRegistries.register(), or DynamicRegistries.registerSynced().
Using register()
DynamicRegistries.register() creates a non-synced registry. It is loaded on the server only, and is not available on the client. Use this when the client never needs to read the registry.
It's not relevant in our example, but here's how to do it:
java
DynamicRegistries.register(MAGIC_SKILLS_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC);1
Using registerSynced()
INFO
The keys used in the following methods are made the same way we made the MAGIC_SKILLS_REGISTRY_KEY, but with a different name.
DynamicRegistries.registerSynced() creates a synced registry. When a client joins a world, the server automatically synchronizes that registry's data with the client. Use this when the client needs the data for rendering, UI, tooltips, or other client-side logic.
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_SYNCED_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC);1
DynamicRegistries.registerSynced() has an overload that accepts a second codec for client-side decoding. This is useful if the client does not need every field from the full server entry.
In our case, we only need the name and manaCost fields on the client side, so let's create a Codec that doesn't include onUseMcFunction, and pass that codec to registerSynced:
java
public record MagicSkillsRegistryEntry(String name, int manaCost, Optional<CacheableFunction> onUseMcFunction) {
// Other Variables and Methods
public static final Codec<MagicSkillsRegistryEntry> CLIENT_CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.STRING.fieldOf("name").forGetter(MagicSkillsRegistryEntry::name),
Codec.INT.fieldOf("mana_cost").forGetter(MagicSkillsRegistryEntry::manaCost)
).apply(instance, (name, manaCost) -> new MagicSkillsRegistryEntry(name, manaCost, Optional.empty()))
);
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_DOUBLE_CODEC_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC, MagicSkillsRegistryEntry.CLIENT_CODEC);1
SyncOption
Both overloads of DynamicRegistries.registerSynced() accept SyncOption arguments at the end to configure synchronization behavior. The only available option to use is:
SKIP_WHEN_EMPTY: Synchronizes the registry only when it contains entries. This can help with compatibility for clients that may not need the registry.
Example:
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_WITH_OPTION_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC, DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY);1
Populating the Registry
JSON files are used for creating registry entries. The JSON structure must match the MagicSkillsRegistryEntry. In this example, our entry class has three fields, so the JSON file for healing_skill entry might look like this:
json
{
"mana_cost": 4785,
"name": "Healing Skill",
"on_use_mc_function": "example-mod:healing_skill_function"
}1
2
3
4
5
2
3
4
5
Entry JSON files are stored under src/main/resources/data/example-mod/example-mod/magic_skills_registry/.
INFO
The repeated example-mod/example-mod is not a mistake.
The first example-mod is the namespace of the entry being added. The second example-mod comes from the registry ID itself. Using your mod ID for both is normal, and it allows other mods or data packs to add entries to your registry under their own namespace.
For example, another-mod might want to add elements to our magic_skills_registry, and it would do that with files under src/main/resources/data/another-mod/example-mod/magic_skills_registry/.
Entry ID
The entry ID is a unique key for each entry, and it can be useful for accessing a specific entry from a registry. It is composed of the filename and the registry key. For example, since our entry JSON file is named healing_skill.json, the entry ID is:
java
ResourceKey<MagicSkillsRegistryEntry> HEALING_SKILL_ENTRY_ID = ResourceKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("healing_skill"));1
Accessing the Registry Data
Dynamic registries are loaded with the world, and can be accessed through the RegistryAccess class by using your registry key. Instances of RegistryAccess can be acquired from many classes, but the most common ones are MinecraftServer, ServerLevel, ClientLevel, Entity, and more.
IMPORTANT
When accessing the RegistryAccess instance from a client-only class, such as ClientLevel, only synced registries are available.
Get the Entire Registry
Registries can be accessed using the lookup method of RegistryAccess, which returns an Optional<Registry<T>> where T is the type of the registry.
java
Optional<Registry<MagicSkillsRegistryEntry>> registry = registryAccess.lookup(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY);1
Get a Specific Entry
Specific entries can be accessed using the get method of RegistryAccess which returns a Optional<Holder.Reference<T>> where T is the type of the registry.
java
ResourceKey<MagicSkillsRegistryEntry> HEALING_SKILL_ENTRY_ID = ResourceKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("healing_skill"));
Optional<Holder.Reference<MagicSkillsRegistryEntry>> entry = registryAccess.get(HEALING_SKILL_ENTRY_ID);
entry.ifPresent(magicSkillRef -> {
MagicSkillsRegistryEntry magicSkill = magicSkillRef.value();
// Other logic to reduce player's mana and running the function
serverPlayer.sendOverlayMessage(Component.literal("Used %s Magical Skill, Mana Reduced By %d".formatted(magicSkill.name(), magicSkill.manaCost())));
});1
2
3
4
5
6
7
2
3
4
5
6
7
Read Entry ID to know how to get the HEALING_SKILL_ENTRY_ID.
In our case, we can use this method to get the entry for magic skill used by user on server, then extract the onUseMcFunction field to execute the mcfunction.
Iterate Over All Entries
Registry entries can be iterated over for various purposes like UI population. In our case we can use this method to populate a screen with custom widgets like this:
java
registry.ifPresent(reg -> {
int y = 50;
for (MagicSkillsRegistryEntry skill : reg) {
MagicSkillWidget widget = new MagicSkillWidget(skill, font, 40, y, 80, 20);
this.addRenderableWidget(widget);
y += 30;
}
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
A custom screen populated from the registry

Learn more about creating Custom Screens and Custom Widgets.
Tags For Custom Registry Entries
Tags are a way to group multiple entries together. For example, we can create tags like attack and defense to group similar magic skills together.
For example, the attacking tag would be defined under data/example-mod/tags/example-mod/magic_skills_registry/attacking_skills.json:
json
{
"values": [
"example-mod:blast_skill",
"example-mod:magic_missile_skill"
]
}1
2
3
4
5
6
2
3
4
5
6
Using Tags In Code
Create a tag key for the tag to check if entries are present in the tag or not.
java
public static final TagKey<MagicSkillsRegistryEntry> ATTACKING_SKILLS_TAG_KEY = TagKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("attacking_skills"));
public static <T> boolean isPresentInMyTag(RegistryAccess registryAccess, ResourceKey<T> entryId, TagKey<T> tagKey) {
return registryAccess.get(entryId).map(reference -> reference.is(tagKey)).orElse(false);
}1
2
3
4
5
2
3
4
5
We can use this method to check if a skill is an attacking skill or not.
java
boolean isAttackingSkill = isPresentInMyTag(registryAccess, entryId, ATTACKING_SKILLS_TAG_KEY);1

