Events

Events that get fired during game to control recipes, world, etc.

List of all events

This is a list of all events. It's possible that not all events are listed here, but this list will be updated regularly.

Click on event ID to open it's class and see information, fields and methods.

Type descriptions:

ID Cancellable Type
init No Startup
postinit No Startup
loaded No Startup
command.registry No Server
command.run Yes Server
client.init No Client
client.debug_info.left No Client
client.debug_info.right No Client
client.logged_in No Client
client.logged_out No Client
client.tick No Client
server.load No Server
server.unload No Server
server.tick No Server
server.datapack.first No Server
server.datapack.last No Server
recipes No Server
world.load No Server
world.unload No Server
world.tick No Server
world.explosion.pre Yes Server
world.explosion.post No Server
player.logged_in No Server
player.logged_out No Server
player.tick No Server
player.data_from_server. Yes Client
player.data_from_client. Yes Server
player.chat Yes Server
player.advancement No Server
player.inventory.opened No Server
player.inventory.closed No Server
player.inventory.changed No Server
player.chest.opened No Server
player.chest.closed No Server
entity.death Yes Server
entity.attack Yes Server
entity.drops Yes Server
entity.check_spawn Yes Server
entity.spawned Yes Server
block.registry No Startup
block.missing_mappings No Server
block.tags No Server
block.right_click Yes Server
block.left_click Yes Server
block.place Yes Server
block.break Yes Server
block.drops No Server
item.registry No Startup
item.missing_mappings No Server
item.tags No Server
item.right_click Yes Server
item.right_click_empty No Server
item.left_click No Server
item.entity_interact Yes Server
item.modification No Startup
item.pickup Yes Server
item.tooltip No Client
item.toss Yes Server
item.crafted No Server
item.smelted No Server
fluid.registry No Startup
fluid.tags No Server
entity_type.tags No Server
worldgen.add No Startup
worldgen.remove No Startup

 

EventJS

This event is the most basic event class, parent of all other events.

Parent class

Object

Can be cancelled

No

Variables and Functions

Name Return Type Info
cancel() void Cancels event. If the event can't be cancelled, it won't do anything.

CommandEventJS

This event needs cleanup! Using it is not recommended.

Information

This event is fired when a command is executed on server side.

Parent class

EventJS

Can be cancelled

Yes

Variables and Functions

Name Type Info
parseResults ParseResults<CommandSource> Command params 
exception Exception Error, set if something went wrong



TagEventJS

This event is fired when a tag collection is loaded, to modify it with script. You can add and remove tags for items, blocks, fluids and entity types.

Tags are per item/block/fluid/entity type and as such cannot be added based on things like NBT data!

Parent class

EventJS

Can be cancelled

No

Variables and Functions

Name Type Info
type String Tag collection type.
get(String tag) TagWrapper Returns specific tag container which you can use to add or remove objects to. tag parameter can be something like 'forge:ingots/copper'. If tag doesn't exist, it will create a new one.
add(String tag, String[]/Regex ids) TagWrapper Shortcut method for event.get(tag).add(ids).
remove(String tag, String[]/Regex ids) TagWrapper Shortcut method for event.get(tag).remove(ids).
removeAll(String tag) TagWrapper Shortcut method for event.get(tag).removeAll().
removeAllTagsFrom(String[] ids) void Removes all tags from object

TagWrapper class

Variables and Functions

Name Type Info
add(String[]/Regex ids) TagWrapper (itself) Adds an object to this tag. If string starts with # then it will add all objects from the second tag. It can be either single string, regex (/regex/flags) or array of either.
remove(String[]/Regex ids) TagWrapper (itself) Removes an object from tag, works the same as add().
removeAll() TagWrapper (itself) Removes all entries from tag.
getObjectIds() Collection<ResourceLocation> Returns a list of all entries in a tag. Will resolve any sub-tags.

Examples

// Listen to item tag event
onEvent('tags.items', event => {
  // Get the #forge:cobblestone tag collection and add Diamond Ore to it
  event.add('forge:cobblestone', 'minecraft:diamond_ore')
  
  // Get the #forge:cobblestone tag collection and remove Mossy Cobblestone from it
  event.remove('forge:cobblestone', 'minecraft:mossy_cobblestone')
  
  // Get #forge:ingots/copper tag and remove all entries from it
  event.removeAll('forge:ingots/copper')
  
  // Required for FTB Quests to check item NBT
  event.add('itemfilters:check_nbt', 'some_item:that_has_nbt_types')
  
  // You can create new tags the same way you add to existing, just give it a name
  event.add('forge:completely_new_tag', 'minecraft:clay_ball')
  
  // Removes all tags from this entry
  event.removeAllTagsFrom('minecraft:stick')
  
  // Add all items from the forge:stone tag to the c:stone tag, unless the id contains diorite
  const stones = event.get('forge:stone').getObjectIds()
  const blacklist = Ingredient.of(/.*diorite.*/)
  stones.forEach(stone => {
    if (!blacklist.test(stone)) {
      event.add('c:stone', stone)
    }
  })
})

Recipes use item tags, not block or fluid tags, even if items representing those are blocks. Like minecraft:cobblestone even if it's a block, it will still be an item tag for recipes.

tags.blocks and tags.fluids are for adding tags to block and fluid types, they work the same way. You can find existing block and fluid tags if you look at a block with F3 mode enabled, on side. These are mostly only used for technical reasons, and like mentioned above, if its for recipes/inventory, you will want to use tags.items even for blocks.

RecipeEventJS

Examples

The most basic script to add a single recipe:

onEvent('recipes', event => {
  event.shaped('3x minecraft:stone', [
    'SAS',
    'S S',
    'SAS'
  ], {
    S: 'minecraft:sponge',
    A: 'minecraft:apple'
  })
})

The most basic script to remove a recipe:

onEvent('recipes', event => {
  event.remove({output: 'minecraft:stick'})
})

Example recipe script:

// kubejs/server_scripts/example.js
// This is just an example script to show off multiple types of recipes and removal methods
// Supports /reload

// Listen to server recipe event
onEvent('recipes', event => {
  // Remove broken recipes from vanilla and other mods
  // This is on by default, so you don't need this line
  //event.removeBrokenRecipes = true

  event.remove({}) // Removes all recipes (nuke option, usually not recommended)
  event.remove({output: 'minecraft:stone_pickaxe'}) // Removes all recipes where output is stone pickaxe
  event.remove({output: '#minecraft:wool'}) // Removes all recipes where output is Wool tag
  event.remove({input: '#forge:dusts/redstone'}) // Removes all recipes where input is Redstone Dust tag
  event.remove({mod: 'quartzchests'}) // Remove all recipes from Quartz Chests mod
  event.remove({type: 'minecraft:campfire_cooking'}) // Remove all campfire cooking recipes
  event.remove({id: 'minecraft:glowstone'}) // Removes recipe by ID. in this case, data/minecraft/recipes/glowstone.json
  event.remove({output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'}) // You can combine filters, to create ANDk logic
  
  // You can use 'mod:id' syntax for 1 sized items. For 2+ you need to use '2x mod:id' or Item.of('mod:id', count) syntax. If you want NBT or chance, 2nd is required

  // Add shaped recipe for 3 Stone from 8 Sponge in chest shape
  // (Shortcut for event.recipes.minecraft.crafting_shaped)
  // If you want to use Extended Crafting, replace event.shapeless with event.recipes.extendedcrafting.shapeless_table
  event.shaped('3x minecraft:stone', [
    'SAS',
    'S S',
    'SAS'
  ], {
    S: 'minecraft:sponge',
    A: 'minecraft:apple'
  })

  // Add shapeless recipe for 4 Cobblestone from 1 Stone and 1 Glowstone
  // (Shortcut for event.recipes.minecraft.crafting_shapeless)
  // If you want to use Extended Crafting, replace event.shapeless with event.recipes.extendedcrafting.shaped_table
  event.shapeless('4x minecraft:cobblestone', ['minecraft:stone', '#forge:dusts/glowstone'])

  // Add Stonecutter recipe for Golden Apple to 4 Apples
  event.stonecutting('4x minecraft:apple', 'minecraft:golden_apple')
  // Add Stonecutter recipe for Golden Apple to 2 Carrots
  event.stonecutting('2x minecraft:carrot', 'minecraft:golden_apple')

  // Add Furnace recipe for Golden Apple to 3 Carrots
  // (Shortcut for event.recipes.minecraft.smelting)
  event.smelting('2x minecraft:carrot', 'minecraft:golden_apple')
  // Similar recipe to above but this time it has a custom, static ID - normally IDs are auto-generated and will change. Useful for Patchouli
  event.smelting('minecraft:golden_apple', 'minecraft:carrot').id('mymodpack:my_recipe_id')

  // Add similar recipes for Blast Furnace, Smoker and Campfire
  event.blasting('3x minecraft:apple', 'minecraft:golden_apple')
  event.smoking('5x minecraft:apple', 'minecraft:golden_apple')
  event.campfireCooking('8x minecraft:apple', 'minecraft:golden_apple')
  // You can also add .xp(1.0) at end of any smelting recipe to change given XP
  
  // Add a smithing recipe that combines 2 items into one (in this case apple and gold ingot into golden apple)
  event.smithing('minecraft:golden_apple', 'minecraft:apple', 'minecraft:gold_ingot')

  // Create a function and use that to make things shorter. You can combine multiple actions
  let multiSmelt = (output, input, includeBlasting) => {
    event.smelting(output, input)
    
    if (includeBlasting) {
      event.blasting(output, input)
    }
  }
  
  multiSmelt('minecraft:blue_dye', '#forge:gems/lapis', true)
  multiSmelt('minecraft:black_dye', 'minecraft:ink_sac', true)
  multiSmelt('minecraft:white_dye', 'minecraft:bone_meal', false)

  // If you use custom({json}) it will be using vanilla Json/datapack syntax. Must include "type": "mod:recipe_id"!
  // You can add recipe to any recipe handler that uses vanilla recipe system or isn't supported by KubeJS
  // You can copy-paste the json directly, but you can also make more javascript-y by removing quotation marks from keys
  // You can replace {item: 'x', count: 4} in result fields with Item.of('x', 4).toResultJson()
  // You can replace {item: 'x'} / {tag: 'x'} with Ingredient.of('x').toJson() or Ingredient.of('#x').toJson()
  // In this case, add Create's crushing recipe, Oak Sapling to Apple + 50% Carrot
  
  // Important! Create has integration already, so you don't need to use this. This is just an example for datapack recipes!
  event.custom({
    type: 'create:crushing',
    ingredients: [
      Ingredient.of('minecraft:oak_sapling').toJson()
    ],
    results: [
      Item.of('minecraft:apple').toResultJson(),
      Item.of('minecraft:carrot').withChance(0.5).toResultJson()
    ],
    processingTime: 100
  })
  
  // Example of using items with NBT in a recipe
  event.shaped('minecraft:book', [
    'CCC',
    'WGL',
    'CCC'
  ], {
    C: '#forge:cobblestone',
    // Item.of('id', {key: value}), it's recommended to use /kubejs hand
    L: Item.of('minecraft:enchanted_book', {StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}),
    // Same principle, but if its an enchantment, there's a helper method
    W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2),
    G: '#forge:glass'
  })
  
  // In all shapeless crafting recipes, replace any planks with Gold Nugget in input items
  event.replaceInput({type: 'minecraft:crafting_shapeless'}, '#minecraft:planks', 'minecraft:gold_nugget')
  
  // In all recipes, replace Stick with Oak Sapling in output items 
  event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling')
})

Possible settings you can change for recipes. It's recommended that you put this in it's own server scripts file, like settings.js

// priority: 5

// Enable recipe logging, off by default
settings.logAddedRecipes = true
settings.logRemovedRecipes = true
// Enable skipped recipe logging, off by default
settings.logSkippedRecipes = true
// Enable erroring recipe logging, on by default, recommended to be kept to true
settings.logErroringRecipes = false

As mentioned before, you can add any recipe from any mod with JSON syntax (see event.custom({})) but these mods are supported as addons with special syntax:

Poorly documented things below!

You can transform ingredients in shaped and shapeless recipes by adding these functions at end of it:

IngredientFilter can be either

Examples:

onEvent('recipes', event => {
  	event.shapeless('9x minecraft:melon_slice', [ // Craft 9 watermelon slices
		Item.of('minecraft:diamond_sword').ignoreNBT(), // Diamond sword that ignores damage
		'minecraft:minecraft:melon' // Watermelon block
	]).damageIngredient(Item.of('minecraft:diamond_sword').ignoreNBT()) // Damage the sword (also has to ignore damage or only 0 damage will work)
  
    // Craft example block from 2 diamond swords and 2 dirt. After crafting first diamond sword is damaged (index 0) and 2nd sword is kept without changes.
	event.shaped('kubejs:example_block', [
		'SD ',
		'D S'
	], {
		S: Item.of('minecraft:diamond_sword').ignoreNBT(),
		D: 'minecraft:dirt'
	}).damageIngredient(0).keepIngredient('minecraft:diamond_sword')

    // Craft example block from 2 diamond swords and 2 stones. After crafting, diamond sword is replaced with stone sword
	event.shapeless('kubejs:example_block', [
		Item.of('minecraft:diamond_sword').ignoreNBT(),
		'minecraft:stone',
		Item.of('minecraft:diamond_sword').ignoreNBT(),
		'minecraft:stone'
	]).replaceIngredient('minecraft:diamond_sword', 'minecraft:stone_sword')

    // Craft clay from sand, bone meal, dirt and water bottle. After crafting, glass bottle is left in place of water bottle
	event.shapeless('minecraft:clay', [
		'minecraft:sand',
		'minecraft:bone_meal',
		'minecraft:dirt',
		Item.of('minecraft:potion', {Potion: "minecraft:water"})
	]).replaceIngredient({item: Item.of('minecraft:potion', {Potion: "minecraft:water"})}, 'minecraft:glass_bottle')
  
  	// Register a customIngredientAction, and recipe that uses it
  	// This one takes the nbt from an enchanted book and applies it to a tool in the crafting table, for no cost.
  	// Thanks to Prunoideae for providing it!
  	Ingredient.registerCustomIngredientAction("apply_enchantment", (itemstack, index, inventory) => {
        let enchantment = inventory.get(inventory.find(Item.of("minecraft:enchanted_book").ignoreNBT())).nbt;
        if (itemstack.nbt == null)
            itemstack.nbt = {}
        itemstack.nbt = itemstack.nbt.merge({ Enchantments: enchantment.get("StoredEnchantments") })
        return itemstack;
    })
  
 	event.shapeless("minecraft:book", ["#forge:tools", Item.of("minecraft:enchanted_book").ignoreNBT()])
        .customIngredientAction("#forge:tools", "apply_enchantment")
})

WorldgenAddEventJS (1.16)

This event isn't complete yet and can only do basic things. Adding dimension-specific features also isn't possible yet, but is planned.

Example script: (kubejs/startup_scripts/worldgen.js)

onEvent('worldgen.add', event => {
  event.addLake(lake => { // Create new lake feature
    lake.block = 'minecraft:diamond_block' // Block ID (Use [] syntax for properties)
    lake.chance = 3 // Spawns every ~3 chunks
  })

  event.addOre(ore => {
    ore.block = 'minecraft:glowstone' // Block ID (Use [] syntax for properties)
    ore.spawnsIn.blacklist = false // Inverts spawn whitelist
    ore.spawnsIn.values = [ // List of valid block IDs or tags that the ore can spawn in
      '#minecraft:base_stone_overworld' // Default behavior - ores spawn in all stone types
    ]
    
    ore.biomes.blacklist = true // Inverts biome whitelist
    ore.biomes.values = [ // Biomes this ore can spawn in
      'minecraft:plains', // Biome ID
      '#nether' // OR #category, see list of categories below
    ]
    
    ore.clusterMinSize = 5 // Min blocks per cluster (currently ignored, will be implemented later, it's always 1)
    ore.clusterMaxSize = 9 // Max blocks per cluster
    ore.clusterCount = 30 // Clusters per chunk
    ore.minHeight = 0 // Min Y ore spawns in
    ore.maxHeight = 64 // Max Y ore spawns in
    ore.squared = true // Adds random value to X and Z between 0 and 16. Recommended to be true
    // ore.chance = 4 // Spawns the ore every ~4 chunks. You usually combine this with clusterCount = 1 for rare ores
  })
  
  event.addSpawn(spawn => { // Create new entity spawn
    spawn.category = 'creature' // Category, can be one of 'creature', 'monster', 'ambient', 'water_creature' or 'water_ambient'
    spawn.entity = 'minecraft:pig' // Entity ID
    spawn.weight = 10 // Weight
    spawn.minCount = 4 // Min entities per group
    spawn.maxCount = 4 // Max entities per group
  })
})

All values are optional. All feature types have biomes field like addOre example

Valid biome categories ('#category'):

You can also use ('$type' (case doesn't matter)) on Forge's BiomeDictionary:

This is the order vanilla worldgen happens:

  1. raw_generation
  2. lakes
  3. local_modifications
  4. underground_structures
  5. surface_structures
  6. strongholds
  7. underground_ores
  8. underground_decoration
  9. vegetal_decoration
  10. top_layer_modification

It's possible you may not be able to generate some things in their layer, like ores in dirt, because dirt hasn't spawned yet. So you may have to change the layer by calling ore.worldgenLayer = 'top_layer_modification'. But this is not recommended.

If you want to remove things, see this event.

WorldgenRemoveEventJS (1.16)

For more information on biomes field, see worldgen.add event page.

onEvent('worldgen.remove', event => {
  event.removeOres(ores => {
    ores.blocks = [ 'minecraft:coal_ore', 'minecraft:iron_ore' ] // Removes coal and iron ore
    ores.biomes.values = [ 'minecraft:plains' ] // Removes it only from plains biomes
  })
  
  event.removeSpawnsByID(spawns => {
    spawns.entities.values = [
      'minecraft:cow',
      'minecraft:chicken',
      'minecraft:pig',
      'minecraft:zombie'
    ]
  })
  
  event.removeSpawnsByCategory(spawns => {
    spawns.biomes.values = [
      'minecraft:plains'
    ]
    spawns.categories.values = [
      'monster'
    ]
  })
})

If something isn't removing, you may try to remove it "manually" by first printing all features (this will spam your console a lot, I suggest reading logs/kubejs/startup.txt) and then removing them by ID where possible.

onEvent('worldgen.remove', event => {
  // May be one of the decoration types/levels described in worldgen.add docs
  // But ores are *most likely* to be generated in this one
  event.printFeatures('underground_ores')
})
onEvent('worldgen.remove', event => {
  event.removeFeatureById('underground_ores', 'mekanism:ore_copper')
})

ItemTooltipEventJS

A client event that allows adding tooltips to any item!

onEvent('item.tooltip', tooltip => {
  // Add tooltip to all of these items
  tooltip.add(['quark:backpack', 'quark:magnet', 'quark:crate'], 'Added by Quark Oddities')
  // You can also use any ingredient except #tag (due to tags loading much later than client scripts)
  tooltip.add(/refinedstorage:red_/, 'Can be any color')
  // Multiple lines with an array []. You can also escape ' by using other type of quotation marks
  tooltip.add('thermal:latex_bucket', ["Not equivalent to Industrial Foregoing's Latex", 'Line 2 text would go here'])
  // Use some data from the client to decorate this tooltip. name returns a component so we just append that.
  tooltip.add('minecraft:skeleton_skull', Text.of('This used to be ').append(Client.player.name).append("'s head"))
  
  tooltip.addAdvanced('thermal:latex_bucket', (item, advanced, text) => {
    text.add(0, Text.of('Hello')) // Adds text in first line, pushing the items name down a line. If you want the line below the item name, the index must be 1
  })
  
  tooltip.addAdvanced('minecraft:beacon', (item, advanced, text) => {
    // shift, alt and ctrl are all keys you can check!
    if (!tooltip.shift) {
      text.add(1, [Text.of('Hold ').gold(), Text.of('Shift ').yellow(), Text.of('to see more info.').gold()])
    } else {
      text.add(1, Text.green('Gives positive effects to players in a range').bold(true))
      text.add(2, Text.red('Requires a base built out of precious metals or gems to function!'))
      text.add(3, [Text.white('Iron, '), Text.aqua('Diamonds, '), Text.gold('Gold '), Text.white('or even '), Text.green('Emeralds '), Text.white('are valid base blocks!')])
    }
  })
  
  // Neat utility to display NBT in the tooltip
  tooltip.addAdvanced(Ingredient.all, (item, advanced, text) => {
    if (tooltip.alt && item.nbt) {
      text.add(Text.of('NBT: ').append(Text.prettyPrintNbt(item.nbt)))
    }
  })
  
  // Show the name of the player who owns the skull in a skulls tooltip
  tooltip.addAdvanced('minecraft:player_head', (item, advanced, text) => {
    let playername = item.nbt?.SkullOwner?.Name
    if (playername) {
      text.add(Text.red(`The head of ${playername}`))
    }
  })
})

Worldgen Events

These following examples will only work on 1.18+! If you need examples for 1.16, you can look here if you want to add new features to world generation and here if you want to remove features from it.

General Notes

Biome Filters:

Biome filters work similarly to recipe filters, and can be used to create complex and exact filters to fine tune exactly where your features may and may not spawn in the world. They are used for the biomes field of a feature and may look something like this:

onEvent('worldgen.add', event => {
	event.addOre((ore) => {
      // let's look at all of the "simple" filters first
      ore.biomes = "minecraft:plains" 		// only spawn in exactly this biome
      ore.biomes = /^minecraft:.*/			// spawn in all biomes that match the given pattern (here: anything that starts with minecraft:)
      ore.biomes = "#minecraft:is_forest" 	// [1.19+] spawn in all biomes tagged as "minecraft:is_forest"
      ore.biomes = "^nether" 				// [1.18 only!] spawn in all biomes in the "nether" category (see Biome Categories)
      ore.biomes = "$hot"					// [Forge 1.18 only!] spawn in all biomes that have the "hot" biome type (see Biome Dictionary)
      // filters can be arbitrarily combined using AND, OR and NOT logic
      ore.biomes = {}						// empty AND filter, always true
      ore.biomes = []						// empty OR filter, also always true
      ore.biomes = {
        not: "minecraft:ocean"				// spawn in all biomes that are NOT "minecaraft:ocean"
      }
      // since AND filters are expressed as maps and expect string keys,
      // all of the "primitive" filters can also be expressed as such
      ore.biomes = {					// see above for an explanation of these filters
        id: "minecraft:plains",
        id: /^minecraft:.*/,			// regex (also technically an id filter)
        tag: "#minecraft:is_forest",
        category: "^nether",
        biome_type: "$hot",
      }
      // note all of the above syntax may be mixed and matched individually
      // for example, this will spawn the feature in any biome that is
      // either plains, or a hot biome that is not in the nether or savanna categories
      ore.biomes = [
        "minecraft:plains",
        {
          biome_type: "$hot",
          not: [
            '#nether',
            {
            	category: "savanna"
            }
          ]
        },
      ]
	})
})

 

Rule Tests and Targets:

In 1.18, Minecraft worldgen has changed to a "target"-based replacement system, meaning you can specify specific blocks to be replaced with specific other blocks within the same feature configuration. (This is used to replace stone with the normal ore and deepslate with the deepslate ore variant, for example).

Each target gets a "rule test" as input (something that checks if a given block state should be replaced or not) and produces a specific output block state. On the KubeJS script side, both of these concepts are expressed as the same class: BlockStatePredicate.

Syntax-wise, BlockStatePredicate is pretty similar to biome filters as they too can be combined using AND or OR filters (which is why we will not be repeating that step here), and can be used to match one of three different things fundamentally:

  1. Blocks (these are simply parsed as strings, so for example "minecraft:stone" to match Stone)
  2. Block States (these are parsed as the block id followed by an array of properties, so you would use something like "minecraft:furnace[lit=true]" to match only furnace blocks that are active (lit). You can use F3 to figure out a block's properties, as well as possible values through using the debug stick.
  3. Block Tags (as you might expect, these are parsed in the "familiar" tag syntax, so you could for example use "#minecraft:base_stone_overworld" to match all types of stone that can be found generating in the ground in the overworld. Note that these are block tags, not item tags, so they may (and probably will) be different! (F3 is your friend!)

You can also use regular expressions with block filters, so /^mekanism:.*_ore$/ would match any block from Mekanism whose id ends with "_ore". Keep in mind this will not match block state properties!

When a RuleTest is required instead of a BlockStatePredicate, you can also supply that rule test directly in the form of a JavaScript object (it will then be parsed the same as vanilla would parse JSON or NBT objects). This can be useful if you want rule tests that have a random chance to match, for example!

More examples on how targets work can be found in the example script down below.

Height Providers:

Another system that may appear a bit confusing at first is the system of "height providers", which are used to determine at what Y level a given ore should spawn and with what frequency. Used in tandem with this feature are the so-called "vertical anchors", which may be used to get the height of something relative to a specific anchor point (for example the top or bottom of the world)

In KubeJS, this system has been simplified a bit to make it easier to use for script developers: To use the two most common types of ore placement, uniform (the feature has the same chance to spawn anywhere in between the two anchors) and triangle (the feature is more likely to spawn in the center of the two anchors than it is to spawn further outwards), you can use the methods uniformHeight and traingleHeight in AddOreProperties, respectively. Vertical anchors have also been simplified, as you can use the aboveBottom / belowTop helper methods in AddOreProperties, or, in newer KubeJS versions, the builtin class wrapper for VerticalAnchor (Note that the former has been deprecated in favour of the latter), as well as if you want to specify absolute heights as simple numbers, instead.

Once again, see the example script for more information!

(1.18 only!) Biome Categories:

Biome categories are a vanilla system that can be used to roughly sort biomes into predefined categories, which are noted below. Note that other mods may add more categories through extending the enum, however since there is no way for us to know this we will only provide you with the vanilla IDs here:

(1.18 and Forge only!) Biome Dictionary:

Much like Vanilla biome categories, Forge uses a "Biome Dictionary" to sort biomes based on their properties. Note that this system is designed to be extended by mods, so there is no way for us to give a complete list of all categories to you, however some of the ones you might commonly find yourself using are listed here:

In 1.19, both of these systems have been removed with no replacement in favour of biome tags!

Example script: (kubejs/startup_scripts/worldgen.js)

onEvent('worldgen.add', event => {
	// use the anchors helper from the event (NOTE: this requires newer versions of KubeJS)
	// if you are using an older version of KubeJS, you can use the methods in the ore properties class
	const {anchors} = event

	event.addOre((ore) => {
		ore.id = "kubejs:glowstone_test_lmao" // (optional, but recommended) custom id for the feature
		ore.biomes = {
			not: "^savanna" // biome filter, see above (technically also optional)
		}

		// examples on how to use targets
		ore.addTarget('#minecraft:stone_ore_replaceables', 'minecraft:glowstone') // replace anything in the vanilla stone_ore_replaceables tag with Glowstone
		ore.addTarget('minecraft:deepslate', 'minecraft:nether_wart_block')       // replace Deepslate with Nether Wart Blocks
		ore.addTarget([
			'minecraft:gravel',   // replace gravel...
			/minecraft:(.*)_dirt/ // or any kind of dirt (including coarse, rooted, etc.)...
		], 'minecraft:tnt')       // with TNT (trust me, it'll be funny)

		ore.count([15, 50])                      // generate between 15 and 50 veins (chosen at random), you could use a single number here for a fixed amount of blocks
				.squared()                       // randomly spreads the ores out across the chunk, instead of generating them in a column
				.triangleHeight(				 // generate the ore with a triangular distribution, this means it will be more likely to be placed closer to the center of the anchors
						anchors.aboveBottom(32), // the lower bound should be 32 blocks above the bottom of the world, so in this case, Y = -32 since minY = -64
						anchors.absolute(96)	 // the upper bound, meanwhile is set to be just exactly at Y = 96
				)								 // in total, the ore can be found between Y levels -32 and 96, and will be most likely at Y = (96 + -32) / 2 = 32
	    
        // more, optional parameters (default values are shown here)
        ore.size = 9                            // max. vein size
        ore.noSurface = 0.5                     // chance to discard if the ore would be exposed to air
        ore.worldgenLayer = "underground_ores"  // what generation step the ores should be generated in (see below)
      	ore.chance = 0							// if != 0 and count is unset, the ore has a 1/n chance to generate per chunk
    })

	// oh yeah, and did I mention lakes exist, too?
	// (for now at least, Vanilla is likely to remove them in the future)
	event.addLake((lake) => {
		lake.id = "kubejs:funny_lake" // BlockStatePredicate
		lake.chance = 4
		lake.fluid = "minecraft:lava"
		lake.barrier = "minecraft:diamond_block"
	})
})

onEvent('worldgen.remove', event => {
	console.info("HELP")
	//console.debugEnabled = true;

	// print all features for a given biome filter
	event.printFeatures('', 'minecraft:plains')

	event.removeOres((props) => {
		// much like ADDING ores, this supports filtering by worldgen layer...
		props.worldgenLayer = 'underground_ores'
		// ...and by biome
		props.biomes = [{
			category: "icy",
		}, {
			category: "savanna",
		}, {
			category: "mesa",
		}];

		// BlockStatePredicate to remove iron and copper ores from the given biomes
		// Note tags may NOT be used here, unfortunately...
		props.blocks = ["minecraft:iron_ore", "minecraft:copper_ore"]
	})

	// remove features by their id (first argument is a generation step)
	event.removeFeatureById('underground_ores', ['minecraft:ore_coal_upper', 'minecraft:ore_coal_lower'])
})

Generation Steps

  1. raw_generation
  2. lakes
  3. local_modifications
  4. underground_structures
  5. surface_structures
  6. strongholds
  7. underground_ores
  8. underground_decoration
  9. vegetal_decoration
  10. top_layer_modification

It's possible you may not be able to generate some things in their layer, like ores in dirt, because dirt hasn't spawned yet. So you may have to change the layer to one of the above generation steps by calling ore.worldgenLayer = 'top_layer_modification'. This is, however, not recommended.