How registration works

One shape for every kind of content, the two ways to declare it, and what RetroAPI does with your ids once you hand them over.

Everything in this section, blocks, items, mobs, dimensions, sounds, particles, world features, follows the same three-part shape. Learn it once here and the individual pages are mostly a list of what each builder can do.

The shape

  1. Describe it. A builder (RetroBlockAccess.create(...), RetroItemAccess.create(), RetroFeatures.ore(...)) or a plain constructor for your own subclass.
  2. Name it. A namespaced identifier: example_mod:ruby_ore. This name is the permanent one, everything else about your content, including its number, is allowed to change.
  3. Register it from your retroapi entrypoint (Entrypoints & sides), so it lands at a point where the platform is ready and before any world assigns ids.
the shape, three times
// a block
RUBY_ORE = RetroBlockAccess.create(Material.STONE)
    .strength(3.0F).texture(id("ruby_ore")).register(id("ruby_ore"));

// an item
RUBY = RetroItemAccess.create()
    .maxStackSize(64).texture(id("ruby")).register(id("ruby"));

// something that generates in the world
RetroFeatures.ore(RUBY_ORE).size(6).count(8).heightRange(4, 32).register();

Ids move. Names do not.

Beta identifies a block by a number, and a modded block has to be given one. RetroAPI hands out a provisional number at registration and then, when a world is opened, replaces it with whatever that world's saved map says the block should be, so that a world you built last week still has your ore where you left it, even if you have since added or removed content.

The consequence is the single most important rule in this wiki: never keep a raw numeric id, and never keep an ItemStack, in a field that outlives registration, unless you are prepared to fix it up. An ItemStack stores the number, not the name. If you must cache one, subscribe to the remap:

fixing cached numbers
IdRemapCallback.EVENT.register(remap -> {
    remap.fix(ExampleMod.STARTER_KIT);                      // an ItemStack field
    ExampleMod.magicId = remap.map(ExampleMod.magicId);      // a raw id
});

RetroAPI already does this for everything it owns: crafting and smelting recipes, fuels, achievement icons. This callback is for the ones only you know about.

Code-driven and data-driven

Most content can be declared as a builder call or as a JSON file under assets/ or data/. Wherever the two are genuine alternatives, a switch sits directly above them and flips the whole wiki between them. They are not alternatives everywhere, though, so here is the honest map:

TopicCodeData filesWhich wins
Block properties (hardness, light, material)✔ builder - code only
Block textures.texture, .sided, .column, .textures✔ model JSONa model JSON overrides .texture
Block state definitions.states(...)blockstates/*.json "properties"code wins on conflict, but see below
What each state looks likestate-aware textures, tint, overlaysblockstates/*.json "variants"only data can pick a model per variant
Item sprites.texture, .layers, .overlaymodels/item/*.jsona model JSON overrides code
Tags (mineable, tiers, your own).mineable, .needsTool, .tag, RetroTagsdata/<ns>/tags/**.jsonunion, both apply
Display names✔ auto-generated fallbacklang/*.langlang file wins
Texture animationaddAnimated*Texture.png.mcmetaeither; mcmeta is detected automatically
SoundsRetroSounds✔ drop .ogg files in placefiles are auto-loaded
Recipes, entities, dimensions, features, particles, components - code only
initRetro()
RUBY_ORE = RetroBlockAccess.create(Material.STONE)
    .register(id("ruby_ore"));   // displays as "Ruby Ore"

No file at all: with no translation present RetroAPI generates a readable name from the identifier. Fine while prototyping.

assets/example_mod/lang/en_US.lang
tile.example_mod.ruby_ore.name=Ruby Ore

One line per name, per language, and a translation pack can add languages you never wrote. What you ship.

Where the two combine, not compete

The table above has one row that is easy to misread, so it gets its own section. Block states are not an either/or. The two sides do different jobs and a block with visual variants normally needs both.

You want…Comes from
the list of properties to exist at alleither side, .states(LIT) or "properties" in the JSON
a typed handle to read and write with (state.get(LIT))code only
a different model per variantthe blockstate JSON only
a different texture/tint/overlay per state, with no modelcode (.sided, .tint, .overlay)

Declare the property in JSON only and it does exist, but in Java you have to find it by name and cast, which throws away the type safety that made the property object worth having:

the cost of a JSON-only property
@SuppressWarnings("unchecked")
RetroProperty<Boolean> lit =
    (RetroProperty<Boolean>) RetroStates.property(LAMP, "lit");
RetroStates.set(world, x, y, z, state.with(lit, true));

So the usual shape of a lamp that both does something and looks different is: the property in code, the variants in the blockstate file. They agree because they are the same property name; if they disagree, the code declaration wins and the mismatch is logged.

both halves, which is normal
// code: the property you will actually use in game logic
static final RetroBoolProperty LIT = RetroBoolProperty.of("lit");

LAMP = RetroBlockAccess.create(Material.STONE)
    .states(LIT)
    .register(id("lamp"));
assets/example_mod/blockstates/lamp.json
{
  // no "properties" needed: the code already declared it
  "variants": {
    "lit=false": { "model": "example_mod:block/lamp_off" },
    "lit=true":  { "model": "example_mod:block/lamp_on" }
  }
}

A block whose states are pure gameplay, a machine that tracks progress but never changes its face, needs no JSON at all. A block that only changes color with its state can stay in code too, using .tint(...) or .overlay(...). It is specifically different models per variant that requires the file.

What each page in this section covers

PageCovers
Blocksthe builder, per-face textures, tint and overlays, tool gating, custom block classes
Block statesproperties, flattened indices, reading/writing state, storage past 16 states
Itemsstacks, custom item classes, layered sprites, handheld poses
Tools, food & armortool kinds and tiers, tools with no ToolMaterial, edible items, armor sets
Tagsmineable/*, needs_*_tool, item tags, and your own groupings
Recipes & fuelshaped, shapeless, smelting, fuel, the wildcard rule
Block entities & GUIstickers, inventories, containers, screens, synced fields
Entitiesmobs, renderers, and how spawns reach the client
Achievementspages, icons, granting
Sounds & musiceffects, streaming music, records, the multiplayer bridge
Particlesthe particle registry, sprite particles, the multiplayer bridge
Item componentstyped per-stack data that survives saving and networking
Dimensions & portalsregistering a world, walking into it, serial ids
Worldgenchunk generators, biomes, and features that generate in existing worlds

Under StationAPI, registration is handed to StationAPI and the calls above still work unchanged, that is the point of routing everything through one entrypoint. What differs is listed on StationAPI interop.