This wiki is for KubeJS 6+, for Minecraft versions 1.19.2+. This wiki still is incomplete, so you may fallback to 1.18.2 one, see KubeJS Legacy page.




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 and Addons sections for more info.

What features does this mod have?

See list of all Features.

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.18 is no longer supported!



Here's a list of all documented (and sometimes undocumented) features that I can remember:



Primitive prototype additions




Wrapped Classes

Name Class
JavaMath java.lang.Math
Blocks net.minecraft.world.level.block.Blocks
Items net.minecraft.world.item.Items
Stats net.minecraft.stats.Stats
DecorationGenerationStep net.minecraft.world.level.levelgen.GenerationStep.Decoration
CarvingGenerationStep net.minecraft.world.level.levelgen.GenerationStep.Carving
BlockPos net.minecraft.core.BlockPos
DamageSource net.minecraft.world.damagesource.DamageSource
BlockProperties net.minecraft.world.level.block.state.properties.BlockStateProperties
Vec3, Vec3d net.minecraft.world.phys.Vec3
Vec3i net.minecraft.core.Vec3i



List of 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:

Folder Method Cancellable
/startup_scripts/ StartupEvents.init (link) No
/startup_scripts/ StartupEvents.postInit (link) No
  • StartupEvents.registry (fluid)

  • StartupEvents.registry (block)

  • StartupEvents.registry (item)

  • StartupEvents.registry (enchantments)

  • StartupEvents.registry (mob effects)

  • StartupEvents.registry (sound event)

  • StartupEvents.registry (block entity type)

  • StartupEvents.registry (potion)

  • StartupEvents.registry (particle type)

  • StartupEvents.registry (painting variant)

  • StartupEvents.registry (custom stat)

  • StartupEvents.registry (point of interest type)

  • StartupEvents.registry (villager type)

  • StartupEvents.registry (villager profession)


ClientEvents.highPriorityAssets (link)


ClientEvents.init (link)


ClientEvents.loggedIn (link)


ClientEvents.loggedOut (link)


ClientEvents.tick (link)


ClientEvents.painterUpdated (link)


ClientEvents.leftDebugInfo (link)


ClientEvents.rightDebugInfo (link)

/client_scripts/ ClientEvents.paintScreen (link) No

ServerEvents.lowPriorityData (link)


ServerEvents.highPriorityData (link)


ServerEvents.loaded (link)


ServerEvents.unloaded (link)


ServerEvents.tick (link)


ServerEvents.tags (link)


ServerEvents.commandRegistry (link)


ServerEvents.command (link)

/server_scripts/ ServerEvents.customCommand (link) Yes
/server_scripts/ ServerEvents.recipes (link) No
/server_scripts/ ServerEvents.afterRecipes (link) No
/server_scripts/ ServerEvents.specialRecipeSerializers (link) No
/server_scripts/ ServerEvents.compostableRecipes (link) No
/server_scripts/ ServerEvents.recipeTypeRegistry (link) No
  • ServerEvents.genericLootTables (link)

  • ServerEvents.blockLootTables (link)

  • ServerEvents.entityLootTables (link)

  • ServerEvents.giftLootTables (link)

  • ServerEvents.fishingLootTables (link)

  • ServerEvents.chestLootTables (link)

/server_scripts/ LevelEvents.loaded (link) No
/server_scripts/ LevelEvents.unloaded (link) No
/server_scripts/ LevelEvents.tick (link) No
/server_scripts/ LevelEvents.beforeExplosion (link) Yes
/server_scripts/ LevelEvents.afterExplosion (link) No
/startup_scripts/ WorldgenEvents.add (link) No
/startup_scripts/ WorldgenEvents.remove (link) No
/client_scripts/ NetworkEvents.fromServer (link) Yes
/server_scripts/ NetworkEvents.fromClient (link) Yes
/startup_scripts/ ItemEvents.modification (link) No
/startup_scripts/ ItemEvents.toolTierRegistry (link) No
/startup_scripts/ ItemEvents.armorTierRegistry (link) No
/server_scripts/ ItemEvents.rightClicked (link) No
/server_scripts/ ItemEvents.pickedUp (link) No
/server_scripts/ ItemEvents.dropped (link) No
/server_scripts/ ItemEvents.entityInteracted (link) No
/server_scripts/ ItemEvents.crafted (link) No
/server_scripts/ ItemEvents.smelted (link) No
/server_scripts/ ItemEvents.foodEaten (link) No
/server_scripts/ ItemEvents.rightClickedEmpty (link) No
/client_scripts/ ItemEvents.clientLeftClicked (link) No
/client_scripts/ ItemEvents.tooltip (link) No
/startup_scripts/ ItemEvents.modelProperties (link) No
/client_scripts/ ItemEvents.clientRightClicked (link) No
/startup_scripts/ BlockEvents.modification (link) No
/server_scripts/ BlockEvents.rightClicked (link) No
/server_scripts/ BlockEvents.leftClicked (link) No
/server_scripts/ BlockEvents.placed (link) No
/server_scripts/ BlockEvents.broken (link) No
/server_scripts/ BlockEvents.detectorChanged (link) No
/server_scripts/ BlockEvents.detectorPowered (link) No
/server_scripts/ BlockEvents.detectorUnpowered (link) No
/server_scripts/ EntityEvents.death (link) Yes
/server_scripts/ EntityEvents.hurt (link) Yes
/server_scripts/ EntityEvents.checkSpawn (link) Yes
/server_scripts/ EntityEvents.spawned (link) Yes
/server_scripts/ PlayerEvents.loggedIn (link) No
/server_scripts/ PlayerEvents.loggedOut (link) No
/server_scripts/ PlayerEvents.respawned (link) No
/server_scripts/ PlayerEvents.tick (link) No
/server_scripts/ PlayerEvents.chat (link) No
/server_scripts/ PlayerEvents.decorateChat (link) No
/server_scripts/ PlayerEvents.advancement (link) Yes
/server_scripts/ PlayerEvents.inventoryOpened (link) No
/server_scripts/ PlayerEvents.inventoryClosed (link) No
/server_scripts/ PlayerEvents.inventoryChanged (link) No
/server_scripts/ PlayerEvents.chestOpened (link) No
/server_scripts/ PlayerEvents.chestClosed (link) No

Mod Compatibility

These events are enabled when certain other mods are present.

Folder Method Cancellable
/client_scripts/ JEIEvents.subtypes (link)
/client_scripts/ JEIEvents.hideItems (link)

JEIEvents.hideFluids (link)

/client_scripts/ JEIEvents.hideCustom (link)
/client_scripts/ JEIEvents.removeCategories (link)
/client_scripts/ JEIEvents.removeRecipes (link)
/client_scripts/ JEIEvents.addItems (link)
/client_scripts/ JEIEvents.addFluids (link)
/client_scripts/ JEIEvents.information (link)
Folder Method Cancellable
/client_scripts/ REIEvents.hide (link)
/client_scripts/ REIEvents.add (link)
/client_scripts/ REIEvents.information (link)
/client_scripts/ REIEvents.removeCategories (link)
/client_scripts/ REIEvents.groupEntries (link)
Folder Method Cancellable
/server_scripts/ GameStageEvents.stageAdded (link)
/server_scripts/ GameStageEvents.stageRemoved (link) No



The tag event is a server event.

The tags event takes an extra parameter that determines which registry its adding tags to. You will generally only need item, block, and fluid tags. However, it does support adding tags to any registry, including other mods ones. For mod ones make sure to include the namespace!

// Listen to item tag event
ServerEvents.tags('item', 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')
  // It supports adding tags to tags as well. Just prefix the second tag with #
  event.add('c:stones', '#forge:stone')
  // Removes all tags from this entry
  // 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, it still uses an item tag for recipes.

// Listen to the block tag event
ServerEvents.tags('block', event => {
  // Add tall grass to the climbable tag. This does make it climable!
  event.add('minecraft:climable', 'minecraft:tall_grass')

Custom Tiers

The custom tier event is a startup event.

You can make custom tiers for armour and tools in a startup script. They are no reloadable without restarting the game. The events are not cancellable

Tool tiers
ItemEvents.toolTierRegistry (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'
Armour tiers
ItemEvents.armorTierRegistry (event => {
  event.add('tier_id', tier => {
    tier.durabilityMultiplier = 15 // Each slot will be multiplied with [13, 15, 16, 11]
    tier.slotProtections = [2, 5, 6, 2] // Slot indicies are [FEET, LEGS, BODY, HEAD] 
    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 Items

The custom item event is a startup event.

Custom items are created in a startup script. They cannot be reloaded without restarting the game. The event is not cancellable.

// Listen to item registry event
StartupEvents.registry('item', 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
  // If you want to specify a different texture location you can do that too, like this:
  event.create('test_item_1').texture('mobbo:item/lava') // This texture would be located at kubejs/assets/mobbo/textures/item/lava.png
  // 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 a tool type ('sword', 'pickaxe', 'axe', 'shovel' or 'hoe'):

Default available tool tiers:

Methods available if you use an armour type ('helmet', 'chestplate', 'leggings' or 'boots'):


Default available armor tiers:


Vanilla group/creative tab IDs:


Custom Foods

StartupEvents.registry('item', event => {
	event.create('magic_steak').food(food => {
    		.saturation(6)//This value does not directly translate to saturation points gained
      		//The real value can be assumed to be:
      		//min(hunger * saturation * 2 + saturation, foodAmountAfterEating)
      		.effect('speed', 600, 0, 1)
      		.alwaysEdible()//Like golden apples
      		.fastToEat()//Like dried kelp
      		.meat()//Dogs are willing to eat it
      		.eaten(ctx => {//runs code upon consumption
        		ctx.player.tell(Text.gold('Yummy Yummy!'))
          		//If you want to modify this code then you need to restart the game.
          		//However, if you make this code call a global startup function
          		//and place the function *outside* of an event handler
          		//then you may use the command:
          		//  /kubejs reload startup_scripts
          		//to reload the function instantly.
          		//See example below
  event.create('magicer_steak').unstackable().food(food => {
      // This references the function below instead of having code directly, so it is reloadable! 
      .eaten(ctx => global.myAwesomeReloadableFunction(ctx))

global.myAwesomeReloadableFunction = ctx => {
  ctx.player.tell('Hello there!')
  ctx.player.tell(Text.of('Change me then reload with ').append(Text.red('/kubejs reload startup_scripts')).append(' to see your changes!'))



The most basic script to add a single recipe:

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

The most basic script to remove a recipe:

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

Example recipe script:

// Listen to server recipe event
ServerEvents.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:smoking'}) // You can combine filters, to create AND 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
  // See the Item and Ingredient for more information: https://mods.latvian.dev/books/kubejs/page/item-and-ingredient

  // Add shapeless recipe for 4 Cobblestone from 1 Stone and 1 Glowstone
  // (Shortcut for event.recipes.minecraft.crafting_shapeless)
  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 Carrot to 2 Carrots
  event.stonecutting('2x minecraft:carrot', 'minecraft:golden_carrot')

  // 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 replacing existing recipes
  event.smelting('minecraft:golden_carrot', '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, blasting or smoking 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)

  // You can use event.custom({json}) to use 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).toJson()
  // 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!
  // Note that not all mods format their jsons the same, often the key names ('ingredients', 'results', ect) are different. 
  // You should check inside the mod jar (mod.jar/data/modid/recipes/) for examples.
    type: 'create:crushing',
    ingredients: [
    results: [
    processingTime: 100
  // Example of using items with NBT in a recipe
  // Might be broken
  event.shaped('minecraft:book', [
  ], {
    C: '#forge:cobblestone',
    // Item.of('id', '{key: value}'), it's recommended to use /kubejs hand, which will give you this syntax.
    // If you want to add a count its Item.of('id', count, '{key: value}'). This won't work here though as crafting table recipes to do accept stacked items
    L: Item.of('minecraft:enchanted_book', '{StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}').weakNBT(),
    // Same principle, but if its an enchantment, there's a helper method
    W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2).weakNBT(),
    G: '#forge:glass'
  // In all shapeless crafting recipes except ones from create, replace any planks with Gold Nugget in input items
  event.replaceInput({type: 'minecraft:crafting_shapeless', not: {mod: 'create'}}, '#minecraft:planks', 'minecraft:gold_nugget')
  // In all recipes, replace Stick with Oak Sapling in output items 
  event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling')
  // By default KubeJS will mirror and shrink recipes, which makes things like UU-Matter type crafting harder to do as you have less shapes.
  // You can use noMirror() and noShrink() to stop this behaviour.
  event.shaped('9x minecraft:emerald', [
    ' D ',
    'D  ',
    '   '
  ], {
    D: 'minecraft:diamond'

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:

Ingredient Actions

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


ServerEvents.recipes(event => {
  event.shapeless('9x minecraft:melon_slice', [ // Craft 9 watermelon slices
    Item.of('minecraft:diamond_sword'), // Diamond sword that ignores damage
    'minecraft:melon' // Watermelon block
  ]).damageIngredient(Item.of('minecraft:diamond_sword')) // Damage the sword (also has to ignore damage or only 0 damage will work)

  // Craft an oak log 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('minecraft:oak_log', [
    'SD ',
    'D S'
  ], {
    S: Item.of('minecraft:diamond_sword'),
    D: 'minecraft:dirt'

  // Craft a birch log from 2 diamond swords and 2 stones. After crafting, diamond sword is replaced with stone sword
  event.shapeless('minecraft:birch_log', [
  ]).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
  // Note that (most) buckets do this automatically, you dont need replaceIngredient for those!
  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"))).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")])
      .customIngredientAction("#forge:tools", "apply_enchantment")

Recipes Event (ServerEvents.recipes)

This page is still under development. It is not complete, and subject to change at any time.

The recipe event is a server event.


Recipes, Callbacks, and You

The recipe event can be used to add, remove, or replace recipes.

Any script that modifies recipes should be placed in the kubejs/server_scripts folder, and can be reloaded at any time with /reload.

Any modifications to the recipes should be done within the context of a recipes event. This means that we need to register an "event listener" for the ServerEvents.recipes event, and give it some code to execute whenever the game is ready to modify recipes. Here's how we tell KubeJS to execute some code whenever it's recipe time:

 * ServerEvents.recipes() is a function that accepts another function,
 * called the "callback", as a parameter. The callback gets run when the 
 * server is working on recipes, and then we can make our own changes.
 * When the callback runs, it is also known as the event "firing". 

ServerEvents.recipes(event => { //listen for the "recipes" server event.
  // You can replace `event` with any name you like, as 
  // long as you change it inside the callback too!
  // This part, inside the curly braces, is the callback.
  // You can modify as many recipes as you like in here,
  // without needing to use ServerEvents.recipes() again.
  console.log('Hello! The recipe event has fired!')

In the next sections you can see what to put inside your callback.

Adding Recipes

The following is all code that should be placed inside your recipe callback.


Shaped recipes are added with the event.shaped() method. Shaped recipes must have their ingredients in a specific order and shape in order to match the player input. The arguments to event.shaped() are:

  1. The output Item, which can have a count of 1-64
  2. An Array (max length 3) of crafting table rows, represented as Strings (max length 3). Spaces ( ) represent slots with no item, and letters represent items. The letters don't have to mean anything; you explain what they mean in the next argument.
  3. An object mapping the letters to Items, like {letter: item}. Input items must have a count of 1.

If you want to force strict positions on the crafting grid, or disable mirroring, see Methods of Custom Recipes.

//event.shaped is a shortcut for event.recipes.minecraft.crafting_shaped
  Item.of('minecraft:stone', 3), // arg 1: output
    'A B', 
    ' C ', // arg 2: the shape (array of strings)
    'B A'  
    A: 'minecraft:andesite', 
    B: 'minecraft:diorite',  //arg 3: the mapping object
    C: 'minecraft:granite'   

Shapeless recipes are added with the event.shapeless() method. Players can put ingredients of shapeless recipes anywhere on the grid and it will still craft. The arguments to event.shapeless() are:

  1. The output item
  2. An Array of input items. Input items must have a count of 1.
//event.shapeless is a shortcut for event.recipes.minecraft.crafting_shapeless
  Item.of('minecraft:dandelion', 3), // arg 1: output
    'minecraft:yellow_dye', //arg 2: the array of inputs

Smithing recipes have 2 inputs and one output, and are added with the event.smithing() method. Smithing recipes are crafted in the smithing table.

//event.smithing is a shortcut for event.recipes.minecraft.smithing
  'minecraft:netherite',  // arg 1: output
  'minecraft:iron_ingot', // arg 2: the item to be upgraded
  'minecraft:black_dye'   // arg 3: the upgrade item
Smelting & Cooking

Cooking recipes are all very similar, accepting one input (a single item) and giving one output (which can be up to 64 of the same item). The fuel cannot be changed in this recipe event, and should be done with tags instead.

// Cook 1 stone into 3 gravel in a Furnace:
event.smelting('3x minecraft:gravel', 'minecraft:stone')
// Blast 1 iron ingot into 10 nuggets in a Blast Furnace: 
event.blasting('10x minecraft:iron_nugget', 'minecraft:iron_ingot')
// Smoke glass into tinted glass in the Smoker:
event.smoking('minecraft:tinted_glass', 'minecraft:glass')
// Burn sticks into torches on the Campfire:
event.campfireCooking('minecraft:torch', 'minecraft:stick')

Like the cooking recipes, stonecutting recipes are very simple, with one input (a single item) and one output (which can be up to 64 of the same item). They are added with the event.stonecutting() method.

//allow cutting 3 sticks from any plank on the stonecutter
event.stonecutting('3x minecraft:stick', '#minecraft:planks')
Custom/Modded JSON recipes

If a mod supports datapack recipes, you can add recipes for it without any addon mod support! Unfortunately we can't give specific advice because every mod's layout is different, but if a mod has a GitHub (most do!) or other source code, you can find the relevant JSON files in /src/generated/resources/data/<modname>/recipes/. Otherwise, you may be able to find it by unzipping the mod's .jar file.

Here's an example of adding a Farmer's Delight cutting board recipe, which defines an input, output, and tool, taken straight from their GitHub. Obviously, you can substitute any of the items in here to make your own recipe!

// Slice cake on a cutting board!
  type: 'farmersdelight:cutting',
  ingredients: [
      item: 'minecraft:cake',
  tool: {
    tag: 'forge:tools/knives',
  result: [
      item: 'farmersdelight:cake_slice',
      count: 7,

Here's another example of event.custom, for making a Tinkers' Construct alloying recipe, which defines, inputs, an output, and a temperature, straight from their GitHub (conditions removed):

// Adding the Molten Electrum alloying recipe from Tinkers' Construct
	type: 'tconstruct:alloy',
	inputs: [
			tag: 'forge:molten_gold',
			amount: 90,
			tag: 'forge:molten_silver',
			amount: 90,
	result: {
		fluid: 'tconstruct:molten_electrum',
		amount: 180,
	temperature: 760,

Removing Recipes

Removing recipes can be done with the event.remove() method. event.remove() accepts 1 argument: a Recipe Filter. A Recipe Filter is a set of properties that determine which recipe(s) to select. There are many conditions with which you can select a recipe:

All of the following can go in your recipe callback, as normal:

// A blank condition removes all recipes (obviously not recommended):
// Remove all recipes where output is stone pickaxe:
event.remove({ output: 'minecraft:stone_pickaxe' })
// Remove all recipes where output has the Wool tag:
event.remove({ output: '#minecraft:wool' })
// Remove all recipes where any input has the Redstone Dust tag:
event.remove({ input: '#forge:dusts/redstone' })
// Remove all recipes from Farmer's Delight:
event.remove({ mod: 'farmersdelight' })
// Remove all campfire cooking recipes:
event.remove({ type: 'minecraft:campfire_cooking' })
// Remove all recipes that grant stone EXCEPT smelting recipes:
event.remove({ not: { type: 'minecraft:smelting' }, output: 'stone' })
// Remove recipes that output cooked chicken AND are cooked on a campfire:
event.remove({ output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking' })
// Remove any blasting OR smelting recipes that output minecraft:iron_ingot:
event.remove([{ type: 'minecraft:smelting', output: 'minecraft:iron_ingot' }, { type: 'minecraft:blasting', output: 'minecraft:iron_ingot' }])
// Remove a recipe by ID. in this case, data/minecraft/recipes/glowstone.json:
// Note: Recipe ID and output are different!
event.remove({ id: 'minecraft:glowstone' })

To find a recipe's unique ID, turn on advanced tooltips using the F3+H keys (you will see an announcement in chat), then hover over the [+] symbol (if using REI) or the output (if using JEI).

Modifying & Replacing Recipes

You can bulk-modify recipes using event.replaceInput() and event.replaceOutput(). They each take 3 arguments:

  1. A filter, similar to the ones used when removing recipes
  2. The ingredient to replace
  3. The ingredient to replace it with (can be a tag)

For example, let's say you were removing all sticks, and wanted to make people craft things with saplings instead (for some reason). Inside your callback, you would put:

	{ input: 'minecraft:stick' }, // Arg 1: the filter
	'minecraft:stick', //arg 2: the item to replace
	Ingredient.of('#minecraft:saplings') //arg 3: the item to replace it with
	// Note: tagged fluid ingredients do not work on Fabric, but tagged items do.

Advanced Techniques

Helper functions

Are you making a lot of similar recipes? Feel like you're typing the same text over and over? Try helper functions! Helper functions will perform repeated actions in much less space by taking in only the parts that are relevant, then doing the repetitive stuff for you!

A simple example, to stop you from having to type minecraft: in front of everything:

let MC = (id) => `minecraft:${id}`
let KJ = (id) => `kubejs:${id}`
let TC = (id) => `tinkers_construct:${id}`

//Now we can refer to items with less typing 🥵
ServerEvents.recipes(event => {
	//instead of writing...
	event.smithing('tinkers_construct:smeltery_controller', 'kubejs:reinforced_iron', 'minecraft:coal')
	//we can write...
	event.smithing(TC('smeltery_controller'), KJ('reinforced_iron'), MC('coal'))

Here's a more advanced helper function, which allows you to make items by crafting a flower pot around the item you specify.

let MC = (id) => 'minecraft:'+id
let KJ = (id) => 'kubejs:'+id

ServerEvents.recipes(event => { 
  let potting = function(output, potted_input){
        ' B '
  //Now we can make many "potting" recipes without typing that huge block over and over!
  potting(KJ('potted_snowball'), MC('snowball'))
  potting(KJ('potted_lava'), MC('lava_bucket'))
  potting(MC('blast_furnace', MC('furnace')))  

In addition to helper functions, you can also loop through an array to perform an action on every item in the array.


Block Modification

The block modification event is a startup event.

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

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

All available properties:




Default Options

You can ship default options from options.txt with KubeJS. This includes keybindings, video settings, enabled resource packs, controls like autojump and toggle sprint and wierd things like advanced tooltips.

Why use this instead of just shipping options.txt? If you ship options.txt then the users options will get overridden every time they update your modpack, where-as KubeJS only sets the options once, on the first time the modpack boots.


To use it simply make a file called defaultoptions.txt in the kubejs/config folder. Then copy any lines you want to set by default over from the normal options.txt file. You can also just copy the entire file if you want to include everything.

A full list of what options the options.txt file can contain is available on the Minecraft Wiki: https://minecraft.fandom.com/wiki/Options.txt


Changing Window Title and Icon

Yes, you can do that with KubeJS too.

To change title, all you have to do is change title in kubejs/config/client.properties.

To change icon, you create a kubejs/config/packicon.png image in standard Minecraft texture size preferably (64x64, 128x128, 256x256, that kind of size).

The image has to be saved as 32-bit PNG, not Auto-detect/24-bit, otherwise you will get a JVM crash!

Here's how to do that in PaintNET:

Example result:

Currently incompatible with Fancy Menu!


Loading Assets and Data

You can also use KubeJS to load assets from resource packs and data from datapacks! While this isn't the only method, its one of the easiest. Other options are <TODO: make and link server datapack load page and client generate assets event page>

The data folder is loaded identically to the data folder in a datapack. If you already have a datapack just copy the folder(s) from inside the datapacks data folder to KubeJS' data folder.

The assets folder is loaded identically to the assets folder in a resourcepack. If you already have a resourcepack just copy the folder(s) from inside the resourcepacks assets folder to KubeJS' assets folder.




Download: CurseForge

No info yet!


KubeJS Create

Download: CurseForge

No info yet!


KubeJS Thermal

Download: CurseForge

This info is currently incomplete!

Supported recipe types:

- furnace
- sawmill
- pulverizer
- smelter
- centrifuge
- press
- crucible
- chiller
- refinery
- brewer
- bottler

event.recipes.thermal.press('minecraft:bone', '#forge:dyes/black')
event.recipes.thermal.crucible(Fluid.of('minecraft:water', 300), '#minecraft:saplings')

- insolator

event.recipes.thermal.insolator('minecraft:bone', '#forge:dyes/black').water(400)

- pulverizer_catalyst
- smelter_catalyst
- insolator_catalyst


- stirling_fuel
- compression_fuel
- magmatic_fuel
- numismatic_fuel
- lapidary_fuel



KubeJS Mekanism

Download: CurseForge

No info yet!


KubeJS Immersive Engineering

Download: CurseForge

No info yet!


KubeJS Blood Magic

Download: CurseForge

No info yet!


KubeJS Tinkers Construct

Download: CurseForge

No info yet!



Download: CurseForge

No info yet!



Download: CurseForge

No info yet!



Download: CurseForge

No info yet!


KubeJS Additions

Download: CurseForge

No info yet!



Download: CurseForge

No info yet!



Download: CurseForge

No info yet!



Download: CurseForge

The custom BlockEntity event is a startup event.

Block Entities

Custom BlockEntities are created in a startup script. They cannot be reloaded without restarting the game. The event is not cancellable.

StartupEvents.registry('block', event => {
	event.create('example_block', 'entity' /*has to be here for the BE builder to work*/).displayName('Example Block')
	.entity(builder => { // adds a BlockEntity onto this block
	    builder.ticker((level, pos, state, be) => { // a tick method, called on block entity tick
            if(!level.clientSide) { // ALWAYS check side, the tick method is called on both CLIENT and SERVER
                if(level.levelData.gameTime % 20 == 0) { // only .levelData.gameTime works for some reason??
                    if(level.getBlockState(pos.above()) === Blocks.AIR.defaultBlockState()) {
                        level.setBlock(pos.above(), Blocks.GLASS.defaultBlockState(), 3)
                      	be.persistentData.putBoolean("placed", true)
                    } else {
                        level.setBlock(pos.above(), Blocks.AIR.defaultBlockState(), 3)
                        be.persistentData.putBoolean("placed", false)
                  	console.info("placed: " + be.persistentData.getBoolean("placed"))
    	}).defaultValues(tag => tag = { progress: 0, example_value_for_extra_saved_data: '0mG this iz Crazyyy'}) // adds a 'default' saved value, added on block entity creation (place etc)
                                                                                                                  // [1st param: CompoundTag consumer]
        .addValidBlock('example_block') // adds a valid block this can attach to, useless in normal circumstances (except if you want to attach to multiple blocks or are building the BE separately)
        .itemHandler(27) // adds a basic item handler to this block entity, use something like PowerfulJS for more advanced functionality
                         // [1st param: slot count]
        .energyHandler(10000, 1000, 1000) // adds a basic FE handler, same as above
                                          // [1st param: max energy, 2nd param: max input, 3rd param: max output]
        .fluidHandler(1000, stack => true) // adds a basic fluid handler
              	                           // [1st param: max amount, 2nd param: fluid filter]

alternatively, you can create the BlockEntity separately and attach it with EntityBlockJS.Builder#entity('kubejs:be_id')

StartupEvents.registry('block_entity_type', event => {
	.ticker((level, pos, state, be) => { // a tick method, called on block entity tick
        if(!level.clientSide) { // ALWAYS check side, the tick method is called on both CLIENT and SERVER
            if(level.levelData.gameTime % 20 == 0) { // only .levelData.gameTime works for some reason??
                if(level.getBlockState(pos.above()) === Blocks.AIR.defaultBlockState()) {
                    level.setBlock(pos.above(), Blocks.GLASS.defaultBlockState(), 3)
                } else {
                    level.setBlock(pos.above(), Blocks.AIR.defaultBlockState(), 3)
    }).saveCallback((level, pos, be, tag) => { // called on BlockEntity save, don't see why you would ever need these tbf, but they're here
        tag.putInt("tagValueAa", be.getPersistentData().getInt('progress'))
    }).loadCallback((level, pos, be, tag) => { // called on BlockEntity load, same as above
          be.getPersistentData().putInt("progress", tag.getInt("tagValueAa"))
    }).defaultValues(tag => tag = { progress: 0, example_value_for_extra_saved_data: '0mG this iz Crazyyy'}) // adds a 'default' saved value, added on block entity creation (place etc)
                                                                                                              // [1st param: CompoundTag consumer]
    .addValidBlock('example_block') // adds a valid block this can attach to, useless in normal circumstances (except if you want to attach to multible blocks)
    .hasGui() // if ScreenJS is installed, marks this blockentity as having a GUI, doesn't do anything otherwise
    .itemHandler(27) // adds a basic item handler to this block entity, use something like PowerfulJS for more advanced functionality
                     // [1st param: slot count]
    .energyHandler(10000, 1000, 1000) // adds a basic FE handler, same as above
                                      // [1st param: max energy, 2nd param: max input, 3rd param: max output]
    .fluidHandler(1000, stack => true) // adds a basic fluid handler
                          	            // [1st param: max amount, 2nd param: fluid filter]

all valid methods available on all builders:


multiblock builder example:

StartupEvents.registry('block', event => {
    let CAP_PREDICATE = be => { // has *any* forge capability (item, energy, fluid)
        return be != null && (be.getCapability(ForgeCapabilities.ITEM_HANDLER).present || be.getCapability(ForgeCapabilities.FLUID_HANDLER).present || be.getCapability(ForgeCapabilities.ENERGY).present)

	event.create('multi_block', 'multiblock').material('metal').hardness(5.0).displayName('Multiblock')
	    .entity(builder => {
	        builder.ticker((level, pos, state, be) => { // tick me here, but ONLY WHEN MULTIBLOCK IS FORMED!!
        	.pattern(() => { // ordering is: [aisle: z, aisle contents[]: y, single string: x]
        	    return BlockPatternBuilder.start()
        	        .aisle( 'BBB',
                    .aisle( 'BBB',
                    .aisle( 'BBB',
                    .where('A', BlockInWorld.or(BlockInWorld.hasState(BlockPredicate.forBlock('minecraft:iron_block')), BlockInWorld.hasBlockEntity(CAP_PREDICATE)))
              					// ^ iron block OR any capability on a BE
                    .where('C', BlockInWorld.hasState(BlockPredicate.forBlock('kubejs:multi_block')))
              					// ^ controller block
                    .where('B', BlockInWorld.hasState(BlockPredicate.forBlock('minecraft:copper_block')))
              					// ^ self explanatory
        .property(BlockProperties.HORIZONTAL_FACING) // block builder stuff, facing direction
        .defaultState(state => {
            state.setValue(BlockProperties.HORIZONTAL_FACING, Direction.NORTH)
        .placementState(state => {
            state.setValue(BlockProperties.HORIZONTAL_FACING, state.horizontalDirection.opposite)

currently only 1 input & 1 output per type are set as the multiblock's IO, and it's the last one found in the scan.

extra valid methods on multiblock builder:

available static methods in BlockInWorld:



more advanced example: link

multiblock (and recipe type) example: link

Recipe Types

beJS can create custom recipe types for your block entities to use!

StartupEvents.registry('recipe_type', event => {
        .assembler((recipe, container) => { // optional, but very much suggested
            let results = recipe.results
            for (let i = 0; i < results.size() && i < container.containerSize; ++i) {
                container.setItem(i, results.get(i))
        .maxInputs(2) // required
        .maxOutputs(4) // required
        .toastSymbol('kubejs:block_id_here') // optional

valid methods on all RecipeType builders:

Item/Fluid Handlers

beJS has multiple custom handlers that have extra functionality:


IMultipleItemHandler is an item handler with multiple slots. valid methods listed below:


IMultipleItemHandler is a fluid handler with multiple slots. valid methods listed below:



Download: CurseForge

The custom ContainerMenu event is a startup event.

Custom Container menus are created in a startup script. They cannot be reloaded without restarting the game. The event is not cancellable.

for block entities:

StartupEvents.registry('menu', event => {
    event.create('example_block' /*name can be anything*/, 'block_entity')
        .addSlot(-10, -10) // adds a slot into this x,y position on the texture
        .addSlot(10, 200)
        .loop(builder /*this builder*/=> {
            for(let x = 0; x < 9; x++) {
                for (let y = 0; y < 4; y++) {
                    builder.addSlot(x * 18 /*<- the width of a slot, remember to add this*/, y * 18, x + y * 4, 0)
        .addOutputSlot(118, 118, 0, 0, 1, 'minecraft:smelting') // adds a slot you can't put an item into, but can pull an item from
  		.inputSlotIndices(0) // sets a list of ITEM HANDLER indexes to handle as slotChanged callback input
        .playerInventoryY(100) // marks the start of the player's inventory on the texture
        .tintColor(0xFF00FF00) // a color to tint the whole inventory texture, in hexadecimal [a, r, g, b]
        .progressDrawable(50, 50, new Rectangle(0, 0, 10, 30), 'forge:textures/white.png', 'up', 'energy') // displays an energy bar from the blockentity's FE capability
  		.slotChanged((menu, level, player, itemHandlers) => {
      		console.info('' + player)
        .setBlockEntity('kubejs:example_block') // the block entity type that should open this GUI on right-click

for any block: 

StartupEvents.registry('menu', event => {
    event.create('grass_block' /*name can be anything*/, 'block')
        /*default parameter set*/
  		.addItemHandler(9) // adds an item handler.
        .setBlock('minecraft:grass_block') // the block that should open this GUI on right-click

for entities: 

StartupEvents.registry('menu', event => {
    event.create('snow_golem' /*name can be anything*/, 'entity')
        /*default parameter set*/
        .setEntity('minecraft:snow_golem') // the enity type that should open this GUI on right-click

and lastly, for completely separate 'basic' GUIs:

StartupEvents.registry('menu', event => {
    event.create('name_here' /*name can be anything*/)
        /*default parameter set*/

valid menu types:

methods the menu builder supports:

default available types:

default available move directions:

available types: 

Custom Key Binds

ScreenJS can do custom key bindings! examples & available methods below:

The custom KeyBind event is a Client event.

// client_scripts

KeybindEvents.register(event => {
    event.register(new KeyBind("open_menu" /* name */, InputConstants.KEY_G /* key index, opengl spec */, "screenjs" /* category name */), (action, modifiers /* modifiers as per OpenGL spec */) => {
        if (action == 1) { // action == 1 is PRESS
            Minecraft.instance.gui.setOverlayMessage(Text.string('AAA').yellow(), false) // vanilla method
            MenuScreens.create('kubejs:separate', Minecraft.instance, 1000, Text.string('AAA').yellow()) // opens a GUI container, preferably of type 'basic'
        } else if (action == 0) { // action == 0 is RELEASE
            Minecraft.instance.gui.setOverlayMessage(Text.string('BBB').yellow(), true)
        } else { // action == 2 is REPEAT (after a second of PRESS)
            Minecraft.instance.gui.setOverlayMessage(Text.string('REPEAT').red(), false)

available methods: 

available types:


KubeJS REI Runtime

Download: Curseforge Modrinth

KubeJS REI Runtime lets you show/hide items in REI dynamically, it provides these methods by default:

// in client_scripts

REIRuntime.showItem(item); // shows an item in REI
REIRuntime.showItems([item, item, ...]); // shows items in REI
REIRuntime.hideItem(item); // hides an item in REI
REIRuntime.hideItems([item, item, ...]); // hides items in REI


KubeJS Botany Pots

Download: Curseforge Modrinth

This mod allows you to create crops, soils, and fertilizers for the Botany Pots mod. 

ServerEvents.recipes(event => {
        "minecraft:candle", // seed item
        ["oak_leaves"], // categories that this crop can be planted on
        10, // growthTicks
        1, // growthModifier - this can be set to 1 in most cases
        { block: "minecraft:candle" }, // display block
                output: "minecraft:candle", // item
                chance: 100, // weight of this entry compared to the others
                minRolls: 1, // the minimum times this loot will be chosen
                maxRolls: 2, // the maximum times this loot will be chosen
                // for example, when chosen this will give 1 to 2 candles

        "minecraft:oak_leaves", // the item that this soil is attached to
        { block: "minecraft:oak_leaves" }, // display block
        ["oak_leaves"], // categories that this soil provides
        100, // growth ticks that this soil will provide, set to -1 for no modifier
        0.5 // optional, growth modifier, example: 0.5 means all crops will take half the time

        "minecraft:iron_ingot", // fertilizer item
        10, // min growth ticks applied
        20 // max growth ticks applied
        // ex: 10 to 20 ticks will be randomly given to the crop

KubeJS Ars Nouveau

Download: Curseforge Modrinth

This addon allows you to create recipes for the mod Ars Nouveau

ServerEvents.recipes(event => {
        ], // input items
	    "minecraft:gunpowder", // reagent
	    "minecraft:tnt", // output
	    1000, // source cost
	    // true // keep nbt of reagent, think like a smithing recipe

        ], // input items
        "minecraft:vanishing_curse", // applied enchantment
        1, // enchantment level
        1000, // source cost

        "minecraft:tnt", // input block
            item: "minecraft:sand",
            chance: 1
        }] // loot table
        // true // drop the item in world?

    // this *does* work, but the recipe must be a valid glyph
    // in the tome, so this really can only be used to
    // replace a glyph's recipe
        "minecraft:tnt", // output item (glyph)
        ], // input items
        3 // exp cost

    // accessible via `/ars-tome id` in this case `/ars-tome kubejs:not_glow`
  	// this is a direct copy of the Glow Trap tome in base Ars Nouveau
        "Not-Glow Trap", // name,
        ], //spell
        "Doesn't snare the target and grant other targets Glowing.", // description
        16718260, // color
            "family": "ars_nouveau:default",
            "pitch": 1.0,
            "volume": 1.0

        "minecraft:sand", // input item
        "minecraft:tnt", // output
        1000, // source cost
        ] // pedestal items // optional

KubeJS ProjectE

Download: Curseforge Modrinth

Lets you set the EMC values of items and the Philosopher's Stone transformations blocks with the ProjectE mod. Examples are shown below.

Server side events ( server_scripts ):

ProjectEEvents.setEMC(event => {
    // sets the absolute emc value of an item
    event.setEMC("minecraft:cobblestone", 10000) // alias. setEMCAfter

    // sets the emc of an item before anything else happens
    // this can sometimes result in this emc value not being
    // set, but also it allows for emc values to be generated
    // from this one; i.e crafting recipes
    event.setEMCBefore("minecraft:stick", 10000);

Startup events ( server_scripts ):

ProjectEEvents.registerWorldTransmutations(event => {
    event.transform("minecraft:tnt", "minecraft:oak_planks");

KubeJS Powah

Download: Curseforge Modrinth

Allows you to create Energizing Orb recipes from the Powah mod. 


ServerEvents.recipes(event => {
    // .energizing([inputs, ...], output, energy)
	event.recipes.powah.energizing(["minecraft:cobblestone"], "minecraft:tnt", 1000)