Skip to content
TheonlyTazz edited this page Jul 23, 2026 · 1 revision

KubeJS integration

Dynamic Brewing adds event.brewing to KubeJS's ServerEvents.recipes event.

ServerEvents.recipes(event => {
    event.brewing.add(output, input, ingredient)
    event.brewing.remove(filter)
})

The argument order is deliberately output-first, matching normal KubeJS recipe functions:

  1. output: an ItemStack, including count and components
  2. input: an Ingredient for the bottle/container slot
  3. ingredient: an Ingredient for the brewing catalyst slot

Call .id(...) on a newly added recipe to give it a stable ID.

Basic recipe

ServerEvents.recipes(event => {
    event.brewing
        .add('minecraft:diamond', 'minecraft:apple', 'minecraft:stick')
        .id('kubejs:brewing/apple_to_diamond')
})

Potion components

Item component filters use the same bracket syntax as Minecraft's /give command. The input below matches a Water Potion, not every item whose ID is minecraft:potion.

ServerEvents.recipes(event => {
    event.brewing.add(
        'minecraft:potion[potion_contents={potion:"minecraft:awkward"}]',
        'minecraft:potion[potion_contents={potion:"minecraft:water"}]',
        'minecraft:nether_wart'
    ).id('kubejs:brewing/water_to_awkward')
})

The output is parsed as an exact stack. The input and ingredient are parsed as ingredient filters.

Custom output components

KubeJS Item helpers are often easier to read than a long component string:

ServerEvents.recipes(event => {
    const result = Item.of('minecraft:diamond')
        .withCustomName(Text.aqua('Arcane Diamond'))
        .enchant('minecraft:fortune', 3)

    event.brewing
        .add(result, 'minecraft:apple', '#c:rods/wooden')
        .steps(1) // optional JEI display override
        .id('kubejs:brewing/arcane_diamond')
})

When .steps(...) is omitted, Dynamic Brewing infers the shortest chain. Any input not produced by another brewing recipe is treated as step 0, so a standalone Apple → Diamond recipe displays as step 1 automatically.

An explicit step is a JEI display override. It does not change brewing time, and it becomes the starting distance used to infer recipes later in that displayed chain.

Tags, lists, intersections, and subtractions work because both input positions are real KubeJS ingredients:

ServerEvents.recipes(event => {
    const catalyst = Ingredient.of('#c:dusts')
        .except('minecraft:redstone')

    event.brewing.add(
        Item.of('minecraft:emerald').withCustomName('Distilled Emerald'),
        ['minecraft:apple', 'minecraft:golden_apple'],
        catalyst
    ).id('kubejs:brewing/distilled_emerald')
})

Exact component-bearing input

Use /kubejs hand while holding an item to get a copyable representation. This is especially useful for custom components from other mods.

ServerEvents.recipes(event => {
    const chargedInput = Ingredient.of(
        'example:crystal[example:charge=100]'
    )

    event.brewing.add(
        'example:refined_crystal[example:charge=0]',
        chargedInput,
        'minecraft:glowstone_dust'
    ).id('kubejs:brewing/refine_charged_crystal')
})

Whether partial component matching is available depends on the ingredient syntax or custom ingredient type being used. A plain item ID ignores components; a component-bearing ingredient filters them.

Custom effect and automatic potion containers

KubeJS can register a custom effect and the potion that contains it from a startup script:

StartupEvents.registry('mob_effect', event => {
    event.create('kubejs:effervescence')
        .displayName('Effervescence')
        .beneficial()
        .color(0x45D6FF)
})

StartupEvents.registry('potion', event => {
    event.create('kubejs:effervescence')
        .effect('kubejs:effervescence', 20 * 60, 0)
})

Only the normal-potion recipe needs to be added in server_scripts:

ServerEvents.recipes(event => {
    event.brewing.add(
        'minecraft:potion[potion_contents={potion:"kubejs:effervescence"}]',
        'minecraft:potion[potion_contents={potion:"minecraft:awkward"}]',
        'minecraft:amethyst_shard'
    ).id('kubejs:brewing/effervescence')
})

The built-in generic conversions then preserve kubejs:effervescence automatically:

  1. Awkward Potion + Amethyst Shard → Potion of Effervescence
  2. Potion of Effervescence + Gunpowder → Splash Potion of Effervescence
  3. Splash Potion of Effervescence + Dragon's Breath → Lingering Potion of Effervescence

This requires the generic minecraft:brewing/splash/all and minecraft:brewing/lingering/all recipes to remain loaded. A pack that removes every Minecraft-owned brewing recipe must retain or recreate those two conversions.

Removing recipes

Removal is always scoped to the dynamic_brewing:brewing recipe type, even if the filter would match recipes of another type.

ServerEvents.recipes(event => {
    // One exact recipe ID
    event.brewing.remove({ id: 'minecraft:brewing/base/awkward' })

    // Every brewing recipe owned by Minecraft
    event.brewing.remove({ mod: 'minecraft' })

    // Recipes producing diamonds
    event.brewing.remove({ output: 'minecraft:diamond' })

    // Recipes using redstone in either input role
    event.brewing.remove({ input: 'minecraft:redstone' })
})

String shorthand is treated as a recipe ID:

event.brewing.remove('minecraft:brewing/base/awkward')

Reloading and debugging

  • Put scripts in kubejs/server_scripts/.
  • Run /reload after editing them.
  • Check logs/kubejs/server.log for added, removed, or failed recipe counts.
  • Use /kubejs hand to inspect the held stack and its components.
  • Avoid defining multiple recipes with the same effective input and ingredient; matching order is not a priority system.

Clone this wiki locally