This wiki is still very much work in progress! Feel free to suggest changes in the #off-topic Discord channel.



What does this mod do?

This mod lets you create scripts in JavaScript language to manage your server, add new blocks and items, change recipes, add custom handlers for quest mods and more!

How to use it?

Run the game with mod installed once. It should generate kubejs folder in your minecraft directory with example scripts and README.txt. Read that!

I don't know JavaScript

There's examples and pre-made scripts here. And you can always ask in discord support channel for help with scripts, but be specific.

Can I reload scripts?

Yes, use /reload to reload server_scripts/F3 + T to reload client_scripts/ and /kubejs reload startup_scripts to reload startup_scripts/. If you don't care about reloading recipes but are testing some world interaction event, you can run /kubejs reload server_scripts. Note: Not everything is reloadable. Some things require you to restart game, some only world, some work on fly. Reloading startup scripts is not recommended, but if you only have event listeners, it shouldn't be a problem.

What mod recipes does it support / is mod X supported?

If the mod uses datapack recipes, then it's supported by default. Some more complicated mods require addon mods, but in theory, still would work with datapack recipes. See Recipes section for more info.

What features does this mod have?

The feature list would go here if I actually wrote it. But basically, editing and creating recipes, tags, items, blocks, fluids, worldgen. Listening to chat, block placement, etc. events. Just look at the event list on Wiki.

How does this mod work?

It uses a fork of Rhino, a JavaScript engine by Mozilla to convert JS code to Java classes at runtime. KubeJS wraps minecraft classes and adds utilities to simplify that a lot and remove need for mappings. Architectury lets nearly the same source code be compiled for both Forge and Fabric making porting extremely easy.

Ok, but what if it.. doesn't work?

You can report issues here.

I have more questions/suggestions!

If wiki didn't have the answer for what you were looking for, you can join the Discord server and ask for help on #support channel!



Website: https://kubejs.com/

Source and issue tracker: https://github.com/KubeJS-Mods/KubeJS

Download: https://www.curseforge.com/minecraft/mc-mods/kubejs

Anything below 1.16 is no longer supported!



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




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

Parent class


Can be cancelled


Variables and Functions

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


This event needs cleanup! Using it is not recommended.


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

Parent class


Can be cancelled


Variables and Functions

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



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.

Parent class


Can be cancelled


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.


// 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
  // 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

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 is for adding tags to block types, it works the same way. You can find existing block 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.




The most basic script to add a single recipe:

onEvent('recipes', event => {
  event.shaped('3x minecraft:stone', [
    'S S',
  ], {
    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', [
    'S S',
  ], {
    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!
    type: 'create:crushing',
    ingredients: [
    results: [
    processingTime: 100
  // Example of using items with NBT in a recipe
  event.shaped('minecraft:book', [
  ], {
    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


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
	]).damageItem(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'

    // Craft example block from 2 diamond swords and 2 stones. After crafting, diamond sword is replaced with stone sword
	event.shapeless('kubejs:example_block', [
	]).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', [
		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")


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.



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 = [
  event.removeSpawnsByCategory(spawns => {
    spawns.biomes.values = [
    spawns.categories.values = [

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
onEvent('worldgen.remove', event => {
  event.removeFeatureById('underground_ores', 'mekanism:ore_copper')


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
  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'])
  tooltip.addAdvanced('thermal:latex_bucket', (item, advanced, text) => {
    text.add(0, Text.of('Hello')) // Adds text in first line, replacing title. If you want 2nd line, the index must be 1


Available fields and methods and examples on how to use them



Parent class of all Java objects. 


None (and itself at the same time, don't question it)

Variables and Functions

Name Type Info
toString() String Tag collection type.
equals(Object other) boolean Checks equality with another object.
hashCode() int Hash code of this object. It is used to optimize maps and other things, should never be used for object equality.
class Class Object's type/class.


Class of string objects, such as "abc" (and in JS 'abc' works as well) 



Variables and Functions

Name Type Info
empty boolean Returns if string is empty a.k.a string === ''
toLowerCase() String Returns a copy of this string, but with all characters in upper case
toUpperCase() String Returns a copy of this string, but with all characters in lower case
equalsIgnoseCase(String other) boolean Hash code of this object. It is used to optimize maps and other things, should never be used for object equality.
length() int Number of characters
charAt(int index) char Single character at index

Primitive Types


Primitive types are objects that don't have a real class and don't inherit methods from Object.

All primitive types

Type Java class Info
void Void No type
byte Byte 8 bit decimal number.
short Short 16 bit decimal number.
int Integer 32 bit decimal number, most common decimal type.
long Long 64 bit decimal number.
float Float 32 bit floating point number.
double Double 64 bit floating point number.
char Character Single character in String such as 'a' or '-'.
boolean Boolean Only true and false values. Can be checked in if function without comparing to true, as if (x) or if (!x) instead of if (x == true) or if (x == false).


Constants, classes and functions


Example scripts for various things you can do with KubeJS



You can also always look at existing modpack using KubeJS UI to see how they do it

onEvent('ui.main_menu', event => {
  event.replace(ui => {
    ui.tilingBackground('kubejsui:textures/example_background.png', 256)
    ui.button(b => {
      b.name = 'Test'
      b.x = 10
      b.y = 10
      b.action = 'minecraft:singleplayer'
    ui.button(b => {
      b.name = 'Test but in bottom right corner'
      b.x = ui.width - b.width - 10
      b.y = ui.height - b.height - 10
      b.action = 'https://feed-the-beast.com/'
    ui.label(l => {
      l.name = Text.yellow('FTB Stranded')
      l.x = 2
      l.y = ui.height - 12
      l.action = 'https://feed-the-beast.com/'

    ui.image(i => {
      i.x = (ui.width - 40) / 2
      i.y = (ui.height - 30) / 2
      i.width = 40
      i.height = 30
      i.action = 'https://feed-the-beast.com/'
    ui.label(l => {
      l.name = Text.aqua('Large label')
      l.x = 100
      l.y = ui.height - 20
      l.height = 15
      l.shadow = true

Custom Items

This is a startup_scripts/ event

// Listen to item registry event
onEvent('item.registry', event => {
  // The texture for this item has to be placed in kubejs/assets/kubejs/textures/item/test_item.png
  // If you want a custom item model, you can create one in Blockbench and put it in kubejs/assets/kubejs/models/item/test_item.json
  // You can chain builder methods as much as you like
  // You can specify item type as 2nd argument in create(), some types have different available methods
  event.create('custom_sword', 'sword').tier('diamond').attackDamageBaseline(10.0)

Valid item types:

Other methods item builder supports: [you can chain these methods after create()]

Methods available if you use 'sword', 'pickaxe', 'axe', 'shovel' or 'hoe' type:

Valid tool tiers:

Methods available if you use 'helmet', 'chestplate', 'leggings' or 'boots' type:

Valid armor tiers:

Valid group/creative tab IDs:

Creating custom tool and armor tiers

All values are optional and by default are based on iron tier

onEvent('item.registry.tool_tiers', event => {
  event.add('tier_id', tier => {
    tier.uses = 250
    tier.speed = 6.0
    tier.attackDamageBonus = 2.0
    tier.level = 2
    tier.enchantmentValue = 14
    tier.repairIngredient = '#forge:ingots/iron'
onEvent('item.registry.armor_tiers', event => {
  // Slot indicies are [FEET, LEGS, BODY, HEAD]
  event.add('tier_id', tier => {
    tier.durabilityMultiplier = 15 // Each slot will be multiplied with [13, 15, 16, 11]
    tier.slotProtections = [2, 5, 6, 2]
    tier.enchantmentValue = 9
    tier.equipSound = 'minecraft:item.armor.equip_iron'
    tier.repairIngredient = '#forge:ingots/iron'
    tier.toughness = 0.0 // diamond has 2.0, netherite 3.0
    tier.knockbackResistance = 0.0

Custom Blocks

This is a startup script.

onEvent('block.registry', event => {
       .displayName('Test Block') // No longer required in 1.18.2+
       .tagBlock('minecraft:mineable/shovel') // Make it mine faster using a shovel in 1.18.2+
  // Block with custom type (see below for list of types)
  event.create('test_block_slab', 'slab').material('glass').hardness(0.5)

The texture for this block has to be placed in kubejs/assets/kubejs/textures/block/test_block.png.
If you want a custom block model, you can create one in Blockbench and put it in kubejs/assets/kubejs/models/block/test_block.json.


List of available materials - to change break/walk sounds and to change some properties.

Materials (1.18.2)




Other methods block builder supports: 

Event callbacks:



Loot Table Modification

onEvent('block.loot_tables', event => {
  event.addSimpleBlock('minecraft:dirt', 'minecraft:red_sand')
onEvent('block.loot_tables', event => {
  event.addSimpleBlock('minecraft:dirt') // To drop itself (fix broken blocks)
  event.addSimpleBlock(/minecraft:.*_ore/, 'minecraft:red_sand') // To drop a different item
onEvent('block.loot_tables', event => {
  event.addBlock('minecraft:dirt', table => { // Build loot table manually
    table.addPool(pool => {
      pool.rolls = 1 // fixed
      // pool.rolls = [4, 6] // or {min: 4, max: 6} // uniform
      // pool.rolls = {n: 4, p: 0.3} // binominal
      pool.addItem('minecraft:dirt', 40) // 40 = weight
      pool.addItem('minecraft:dirt', 40, [4, 8]) // [4-8] = count modifier, uses same syntax as rolls
      // pool.addCondition({json condition, see vanilla wiki})
      // pool.addEntry({json entry, see vanilla wiki for non-items})

Example from Factorial: (adds 1-3 leaves dropped from all Leaves blocks, 4-8 logs from all log and wood blocks and 4-8 stone from Stone, Cobblestone, Andesite, Diorite and Granite)

onEvent('block.loot_tables', event => {
	event.addBlock(/minecraft:.*_leaves/, table => {
		table.addPool(pool => {
			pool.addItem('factorial:leaf', 1, [1, 3])

	event.addBlock(/minecraft:.*_(log|wood)/, table => {
		table.addPool(pool => {
			pool.addItem('factorial:wood', 1, [4, 8])

	], table => {
		table.addPool(pool => {
			pool.rolls = [4, 8] // Roll the pool instead of individual items
			pool.addItem('factorial:stone', 1)

You can also modify existing loot tables to add items to them:

onEvent('block.loot_tables', event => {
  // all dirt blocks have a 50% chance to drop an enchanted diamond sword named "test"
  event.modifyBlock(/^minecraft:.*dirt/, table => {
    table.addPool(pool => {
      pool.addItem('minecraft:diamond_sword').randomChance(0.5).enchantWithLevels(1, true).name(Text.of('Test').blue())

Other loot table types work too:

onEvent('entity.loot_tables', event => {
  // Override zombie loot table that will drop 5 of either carrot (25% chance) or apple (75% chance)
  event.addEntity('minecraft:zombie', table => {
    table.addPool(pool => {
      pool.rolls = 5
      pool.addItem('minecraft:carrot', 1)
      pool.addItem('minecraft:apple', 3)
  event.modifyEntity('minecraft:pig', table => {
    table.addPool(pool => {
      // Modify pig loot table to *also* drop dirt on top of its regular drops

Supported table types:

Event ID Override method name Modify method name
generic.loot_tables addGeneric modify
block.loot_tables addBlock modifyBlock
entity.loot_tables addEntity modifyEntity
gift.loot_tables addGift modify
fishing.loot_tables addFishing modify
chest.loot_tables addChest modify



Item Modification

item.modification event is a startup script event that allows you to change properties of existing items

onEvent('item.modification', event => {
  event.modify('minecraft:ender_pearl', item => {
    item.maxStackSize = 64
    item.fireResistant = true

All available properties:


Block Modification

block.modification event is a startup script event that allows you to change properties of existing blocks

onEvent('block.modification', event => {
  event.modify('minecraft:stone', block => {
    block.destroySpeed = 0.1
    block.hasCollision = false

All available properties:


JEI Integration


onEvent('jei.subtypes', event => {
  event.userNBTKey('example:item', 'type')

Hide Items & Fluids

onEvent('jei.hide.items', event => {

onEvent('jei.hide.fluids', event => {

Add Items & Fluids

onEvent('jei.add.items', event => {
  event.add(Item.of('example:item', {test: 123}))

onEvent('jei.add.fluids', event => {

Add Information

onEvent('jei.information', event => {
  event.add('example:ingredient', ['Line 1', 'Line 2'])

Hide categories

onEvent('jei.remove.categories', event => {
  console.log(event.getCategoryIds()) //log a list of all category ids to logs/kubejs/client.txt

REI Integration

Note: REI integration only works on Fabric in 1.16. In 1.18+, it works on both Forge and Fabric!

Hide Items

onEvent('rei.hide.items', event => {

Add Items

onEvent('rei.add.items', event => {
  event.add(item.of('example:item').nbt({test: 123}))

Add Information

onEvent('rei.information', event => {
  event.add('example:ingredient', 'Title', ['Line 1', 'Line 2'])

Yeet categories

onEvent('rei.remove.categories', event => {
  console.log(event.getCategoryIds()) //log a list of all category ids to logs/kubejs/client.txt
  //event.remove works too, but yeeting is so much more fun 😉

FTB Quests Integration

onEvent('ftbquests.custom_task.75381f79', event => {
  log.info('Custom task!')
  event.checkTimer = 20
  event.check = (task, player) => {
    if (player.world.daytime && player.world.raining) {

onEvent('ftbquests.custom_reward.e4f76908', event => {
  log.info('Custom reward!')

// specific object completion
onEvent('ftbquests.completed.d4f36905', event => {
  if (event.player) {
    event.notifiedPlayers.tell(Text.of(`${event.player.name} completed... something!`).green())

// generic 'quest' object completion. Note: There isnt actually a way to get reliable title on server side, so dont use event.object.title
onEvent('ftbquests.completed', event => {
  if (event.player && event.object.objectType.id === 'quest') {
    event.notifiedPlayers.tell(Text.of(`${event.player.name} completed a quest!`).blue())

// object with tag 'ding' completion
onEvent('ftbquests.completed.ding', event => {

onEvent('entity.death', event => {
  && event.source.actual
  && event.source.actual.player
  && event.source.actual.mainHandItem.id === 'minecraft:wooden_sword'
  && event.entity.type === 'minecraft:zombie') {
    event.source.actual.data.ftbquests.addProgress('12345678', 1)

Reflection / Java access

Very limited reflection is possible, but is not recommended. Use it in cases when KubeJS doesnt support something.

In 1.18.2+ internal Minecraft classes are remapped to MojMaps at runtime, so you don't have to use obfuscated names if accessing internal Minecraft fields and methods.


An example of adding a block with a custom material, built using reflection to get the MaterialJS class, then make a new instance of that with amethyst sounds and material properties from internal Minecraft classes.

// Startup script, 1.18.2
const MaterialJS = java("dev.latvian.mods.kubejs.block.MaterialJS")
const Material = java('net.minecraft.world.level.material.Material')
const SoundType = java('net.minecraft.world.level.block.SoundType')

amethystMaterial = new MaterialJS('amethyst', Material.AMETHYST, SoundType.AMETHYST) // f_164531_ and f_154654_ are the respective obfuscated names of these fields, for older versions

//This builder uses 1.18.2 syntax, it will not work in 1.16 or 1.18.1
onEvent('block.registry', event => {
	event.create('amethyst_slab', 'slab')
		.material(amethystMaterial)// Use the new MaterialJS instance we created as the material
		.texture('texture', 'minecraft:block/amethyst_block')

This does come at a cost, it takes 1-2 seconds to load this script, instead of the normal milliseconds. You should always import your classes at the top of the script, instead of in an event.


Painter API


Painter API allows you to draw things on the screen, both from server and directly from client. This can allow you to create widgets from server side or effects on screen or in world from client side.

Currently it doesn't support any input, but in future, in-game menus or even GUIs similar to Source engine ones will be supported.

Paintable objects are created from NBT/Json objects and all have an id. If id isn't provided, a random one will be generated. Objects x and z are absolute positions based on screen, but you can align elements in one of the corners of screen. You can bulk add multiple objects in one json object. All properties are optional, but obviously some you should almost always override like size and position for rectangles.

paint({...}) is based on upsert principle - if object doesn't exist it will create it (if the object also contains valid type), otherwise, update existing:

You can bulk update/create multiple things in same object:

You can remove object with remove: true, bulk remove multiple objects or remove all objects:

These methods have command alternatives:

If the object is re-occuring, it's recommended to create objects at login with all of its static properties and visible: false, then update it later to unhide it. Painter objects will be cleared when players leave world/server, if its persistent, then it must be re-added at login every time.

Currently available objects

Underlined objects are not something you can create


(available for all objects)



Available Unit variables
Available Unit constants


onEvent('player.logged_in', event => {
		example_rectangle: {
			type: 'rectangle',
			x: 10,
			y: 10,
			w: 50,
			h: 20,
			color: '#00FF00',
			draw: 'always'
		last_message: {
			type: 'text',
			text: 'No last message',
			scale: 1.5,
			x: -4,
			y: -4,
			alignX: 'right',
			alignY: 'bottom',
			draw: 'always'

onEvent('player.chat', event => {
	// Updates example_rectangle x value and last_message text value to last message + contents from event
	event.player.paint({example_rectangle: {x: '(sin((time() * 1.1)) * (($screenW - 32) / 2))', w: 32, h: 32, alignX: 'center', texture: 'kubejs:textures/item/diamond_ore.png'}})
	event.player.paint({last_message: {text: `Last message: ${event.message}`}})
	// Bulk update, this is the same code as 2 lines above, you can use whichever you like better
	// event.player.paint({example_rectangle: {x: 120}, last_message: {text: `Last message: ${event.message}`}})
	event.player.paint({lava: {type: 'atlas_texture', texture: 'minecraft:block/lava_flow'}})