Registry
Adding custom content to the game is the core of modding. Katton lets you register nine types of native Minecraft objects — all from Kotlin scripts, all hot-reloadable.
WARNING
The registry system is actively developed. APIs are stable for RELOADABLE mode but may evolve. When in doubt, check the API docs.
Register Modes
Every register function accepts a registerMode parameter:
| Mode | Behavior |
|---|---|
RegisterMode.GLOBAL | Registered once at mod init. Not tracked for reload. Survives all reloads. |
RegisterMode.RELOADABLE | Tracked by Katton. Ownership is cleared on reload and the script can re-register. The Minecraft registry entry is preserved (soft-retained) to prevent holder crashes. |
RegisterMode.AUTO | Acts as GLOBAL during mod init, then RELOADABLE after server starts. Use this for "just works" behavior. |
TIP
Use RELOADABLE for content you iterate on. Use GLOBAL for content that must persist unchanged across reloads.
Reload Lifecycle
Running /katton reload triggers this sequence:
beginReload()called on each registry — clears ownership tracking- Scripts re-execute —
ensureRegisteredreturns the same instance if already registered markManaged()re-tracks the entry for the current script- Stale entries (no longer registered by any script) remain in Minecraft's registry until restart
Registry Diagnostics
Use /katton registry to see a summary per registry: how many entries Katton tracks, how many are managed by scripts, and how many are stale (still in Minecraft's registry but no longer owned by any script).
Use /katton registry stale to show only registries with stale entries.
All Native Types
1. Items
The most common registry call. You can register simple items, custom-behavior items, and food items.
registerNativeItem(
id = "qwq:hello", // The identifier of the item, must be unique across all mods
registerMode = RegisterMode.RELOADABLE, // TODO
configure = {
// Configure the item properties
// For datapack developers, you can think of it as data components
setName(Component.literal("Hello"))
stacksTo(1)
setModel(Identifier.fromNamespaceAndPath("minecraft", "diamond"))
food(FoodProperties.Builder().nutrition(1).saturationModifier(0.1f).build())
}
) {
// Here to define the item class, just do what you will do in a normal mod.
object : Item(it) {
// Executed when the player uses the item
override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResult {
// Note that the method defined here won't be hot-reloadable
// Wrap the logic you want to hot-reload in a separate function
return useHelloItem(player)
}
}
}
fun useHelloItem(player: Player): InteractionResult{
(player as? ServerPlayer)?.let { p -> tell(p , "Used the hello item!") }
return InteractionResult.SUCCESS
}CAUTION
When the client connects to a server, only item data components sync — not the Kotlin logic in your item class. Client-side interaction logic may need extra handling.
2. Blocks
Register a custom block with strength, tool requirements, and custom behavior.
registerNativeBlock(
id = "qwq:test_block",
registerMode = RegisterMode.RELOADABLE
) { props ->
Block(
props
.strength(3.0f, 6.0f)
.requiresCorrectToolForDrops()
)
}NOTE
You need a blockstate JSON and a model JSON in your resource pack for the block to render properly.
3. Mob Effects
Create custom potion effects — beneficial or harmful.
registerNativeEffect(
id = "qwq:test_qwq",
registerMode = RegisterMode.RELOADABLE
) {
object : MobEffect(MobEffectCategory.BENEFICIAL, 0x55FF55) {
override fun applyEffectTick(serverLevel: ServerLevel, mob: LivingEntity, amplification: Int): Boolean {
mob.hurtServer(serverLevel, mob.damageSources().wither(), 1.0F);
return super.applyEffectTick(serverLevel, mob, amplification)
}
override fun shouldApplyEffectTickThisTick(tickCount: Int, amplification: Int): Boolean {
return tickCount % 20 == 0
}
}
}4. Commands
Script-registered commands use ScriptCommandRegistry — they're auto-cleaned on reload.
import com.mojang.brigadier.arguments.IntegerArgumentType.getInteger
import com.mojang.brigadier.arguments.IntegerArgumentType.integer
import com.mojang.brigadier.arguments.StringArgumentType.getString
import com.mojang.brigadier.arguments.StringArgumentType.word
import net.minecraft.commands.SharedSuggestionProvider.suggest
import net.minecraft.network.chat.Component
import top.katton.registry.registerCommand
@ServerScriptEntrypoint
fun commandTest() {
registerCommand("demo") {
literal("ping") {
executes { ctx ->
ctx.source.sendSuccess(
{ Component.literal("[demo] pong") },
false
)
1
}
}
literal("echo") {
argument("text", word()) {
suggests { _, builder ->
suggest(listOf("hello", "world", "katton"), builder)
}
executes { ctx ->
val text = getString(ctx, "text")
ctx.source.sendSuccess(
{ Component.literal("[demo] $text") },
false
)
1
}
}
}
literal("add") {
argument("a", integer()) {
argument("b", integer()) {
executes { ctx ->
val a = getInteger(ctx, "a")
val b = getInteger(ctx, "b")
ctx.source.sendSuccess(
{ Component.literal("[demo] $a + $b = ${a + b}") },
false
)
1
}
}
}
}
}
}5. Sound Events
Register custom sounds. Pair with a sounds.json in your resource pack.
import top.katton.api.registry.registerNativeSoundEvent
import top.katton.api.registry.createVariableRangeSoundEvent
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
registerNativeSoundEvent(
id = "mymod:my_sound",
registerMode = RegisterMode.RELOADABLE
) {
createVariableRangeSoundEvent("mymod:my_sound")
}
}You also need a
sounds.jsonin your resource pack to map the sound event to an actual audio file.
6. Particle Types
Add custom visual particles.
import net.minecraft.core.particles.SimpleParticleType
import top.katton.api.registry.registerNativeParticleType
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
registerNativeParticleType(
id = "mymod:my_particle",
registerMode = RegisterMode.RELOADABLE
) {
object : SimpleParticleType(false) {}
}
}7. Block Entity Types
For blocks that store data (chests, furnaces, custom machines).
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntityType
import top.katton.api.registry.registerNativeBlockEntityType
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
// Register a block entity type — you'll typically pair it with a custom BlockEntity class
registerNativeBlockEntityType(
id = "mymod:my_block_entity",
registerMode = RegisterMode.RELOADABLE
) {
BlockEntityType.Builder.of(
::MyBlockEntity, // your BlockEntity class
MyBlocks.MY_BLOCK // the block(s) that host this entity
).build(null)
}
}8. Creative Mode Tabs
Organize your items in the creative inventory.
import net.minecraft.network.chat.Component
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import top.katton.api.registry.registerNativeCreativeTab
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
registerNativeCreativeTab(
id = "mymod:my_tab",
registerMode = RegisterMode.RELOADABLE
) {
CreativeModeTab.builder(CreativeModeTab.Row.TOP, 0)
.title(Component.literal("My Custom Tab"))
.icon { ItemStack(Items.DIAMOND) }
.displayItems { _, items ->
items.accept(Items.DIAMOND)
items.accept(Items.EMERALD)
}
.build()
}
}9. Data Component Types
Type-safe custom data on item stacks — think of it as structured NBT.
import com.mojang.serialization.Codec
import net.minecraft.core.component.DataComponentType
import top.katton.api.registry.registerNativeDataComponentType
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
registerNativeDataComponentType<String>(
id = "mymod:custom_data",
registerMode = RegisterMode.RELOADABLE
) {
DataComponentType.builder<String>()
.persistent(Codec.STRING)
.build()
}
}Data components let you attach custom data to item stacks — like a built-in NBT but type-safe!
10. Entity Types (Basic)
Register a bare entity type — no attributes, no renderer, no spawn egg.
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.MobCategory
import top.katton.api.registry.registerNativeEntityType
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
// Simple entity type registration (no custom attributes/behavior)
registerNativeEntityType(
id = "mymod:my_entity",
registerMode = RegisterMode.RELOADABLE
) {
EntityType.Builder.of(::MyEntity, MobCategory.CREATURE)
.sized(0.6f, 1.8f)
.build("mymod:my_entity")
}
}11. Entity Types (Full — with attributes + spawn egg)
Complete entity registration including attributes and spawn configuration.
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.MobCategory
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.monster.zombie.Zombie
import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.entity.SpawnPlacementType
import top.katton.api.registry.registerNativeEntityTypeWithProperties
import top.katton.registry.KattonEntityProperties
import top.katton.registry.RegisterMode
import top.katton.api.ServerScriptEntrypoint
@ServerScriptEntrypoint
fun main() {
val props = KattonEntityProperties(id("mymod:my_mob_full")).apply {
// Set entity dimensions
dimensions(0.6f, 1.95f)
// Set mob category
mobCategory = MobCategory.MONSTER
// Configure attributes (health, speed, damage, etc.)
attributes {
it.add(Attributes.MAX_HEALTH, 20.0)
it.add(Attributes.MOVEMENT_SPEED, 0.23)
it.add(Attributes.ATTACK_DAMAGE, 3.0)
it.add(Attributes.FOLLOW_RANGE, 35.0)
}
// Enable spawn egg in creative inventory
spawnEgg = true
// Configure natural spawn placement
spawnPlacementType = SpawnPlacementType.ON_GROUND
spawnHeightmap = Heightmap.Types.MOTION_BLOCKING_NO_LEAVES
}
registerNativeEntityTypeWithProperties(
id = "mymod:my_mob_full",
registerMode = RegisterMode.RELOADABLE,
properties = props
) { p ->
EntityType.Builder.of(::Zombie, MobCategory.MONSTER)
.sized(p.width, p.height)
.build("mymod:my_mob_full")
}
}TIP
For a complete walkthrough of creating a custom animated entity with BlockBench models and animations, see the Entity Tutorial.
