Skip to content

1.21.1 Custom Addons

stefanomanca edited this page Oct 3, 2026 · 2 revisions

Custom Addons with KubeJS

Version Parity: Supported on both Minecraft 1.20.1 (Forge with KubeJS 6) and Minecraft 1.21.1 (NeoForge with KubeJS 7).

Besides the built-in addons, a pack can register new addon items that reuse one of the built-in detectors. With KubeJS, register them in a startup script. Addons are items, so a game restart is needed after every edit.

1. Register the addon

kubejs/startup_scripts/radar_addons.js:

SignalRadarEvents.registerAddons(event => {
  event.create('mypack:addon_oil', 'block_tag')   // id, detector
    .tag('#c:ores/oil')            // what to look for (required for the *_tag detectors)
    .minTier(3)                    // radar tier needed to install it (0-4)
    .radius(16, 48)                // radius at minTier .. radius at tier 4 (blocks)
    .refresh(5)                    // seconds between detector runs
    .color('#222222')              // blip frame colour (also tints the generic item icon)
    .energy(10)                    // extra FE per scan period while installed
    .category('Oil')               // category name
    .icon('minecraft:lava_bucket') // optional blip icon
    .texture('mypack:item/oil')    // optional item texture (step 2)
    .requiredMod('somemod')        // optional: only registered when that mod is loaded
})
Detector Finds Needs
block_tag blocks in a block tag, loaded chunks only, one blip per vein .tag('#ns:tag')
entity_tag entities in an entity-type tag .tag('#ns:tag')
container block entities with an inventory nothing
structure_tag nearest structure of each entry of a structure tag (cached); never falls back to "all structures" .tag('#ns:tag')

Builder options (all optional except .tag for the tag detectors): .minTier(0..4), .radius(min, max), .refresh(seconds), .color('#RRGGBB' or 0xRRGGBB), .energy(fe), .category(name), .requiredMod(modid), .icon(spec), .texture(id).

Ids without a namespace get kubejs:. A bad value is reported in the startup log and that addon is skipped; the others still register. Custom addons have no config entries: they use the values from the definition. The built-in addons are in the item tag signalradar:addons.

Blip icon (.icon): item:ns:id (or a bare item id), block:ns:id, entity:ns:id, texture:ns:textures/....png. Without it the icon follows the detector: the block face (block_tag), the mob face (entity_tag), the container's block item (container), a map (structure_tag).

2. Item texture

KubeJS serves kubejs/assets/ as a resource pack. Put a 16x16 PNG at

kubejs/assets/<ns>/textures/item/<addon path>.png      e.g. kubejs/assets/mypack/textures/item/addon_oil.png

or point .texture('ns:item/xxx') at kubejs/assets/ns/textures/item/xxx.png. The item looks like, in order:

  1. your own model kubejs/assets/<ns>/models/item/<path>.json, if present;
  2. otherwise the PNG above (an item/generated model is made for you);
  3. otherwise a generic radar addon icon tinted with .color() (never the purple/black missing texture).

The client log says which one each addon uses: Custom addon <id>: texture <rl>, generic (texture missing) or own model <file>. Changing a PNG needs only F3+T; a new or changed .texture() needs a restart.

3. Name and recipe

Name, in kubejs/assets/<ns>/lang/en_us.json:

{ "item.mypack.addon_oil": "Oil Scanner Addon" }

Without it the item is called "Radar Addon (Oil)" (the category).

Recipe (custom addons ship none), in kubejs/server_scripts/radar_recipes.js:

ServerEvents.recipes(event => {
  event.shaped('mypack:addon_oil', ['NRN', 'RXR', 'NRN'], {
    N: 'minecraft:iron_nugget', R: 'minecraft:redstone', X: 'minecraft:bucket'
  })
})

4. Use it

Sneak + right-click the radar, drop the addon in a slot (the radar must be at least minTier). Its blips use the addon colour, and every scan costs energy FE more (less on higher radar tiers).

Full example startup script

Three addons using each texture option, plus one that only exists when another mod is loaded. Save as kubejs/startup_scripts/example_addons.js:

SignalRadarEvents.registerAddons(event => {
  // Blocks in a tag (with no such blocks it simply finds nothing).
  event.create('mypack:addon_oil', 'block_tag')
    .tag('#c:ores/oil')
    .minTier(3)
    .radius(16, 48)
    .refresh(5)
    .color('#222222')
    .energy(10)
    .category('Oil')
    .icon('minecraft:lava_bucket')
    // no .texture(): default mypack:item/addon_oil = kubejs/assets/mypack/textures/item/addon_oil.png

  // Entities in a tag.
  event.create('mypack:addon_undead', 'entity_tag')
    .tag('#minecraft:undead')
    .minTier(1)
    .radius(24, 48)
    .refresh(2)
    .color(0x88AA55)
    .energy(15)
    .category('Undead')
    .texture('mypack:item/undead_skull')   // kubejs/assets/mypack/textures/item/undead_skull.png

  // No texture PNG anywhere: falls back to the generic tinted model.
  event.create('mypack:addon_plain', 'container')
    .color('#33AAFF')
    .category('Plain')

  // Only registered when its mod is loaded.
  event.create('mypack:addon_optional', 'container')
    .requiredMod('some_other_mod')
    .category('Optional')
})

Tips

  • Keep radius small for block_tag addons on common blocks: block scans share a per-scan budget (scan.maxBlockChecksPerScan, see Configuration).
  • React to installs with the addonChanged event and query radars with SignalRadar.hasAddon(stack, id), see KubeJS.
  • Other mods can register addons from Java, see Mod API.

Clone this wiki locally