KubeJS Secure Gates is a NeoForge mod for Minecraft 1.21.1 that lets KubeJS scripts safely block player actions before they complete on the server.
The mod is intentionally generic. It does not know about your progression system, quests, ranks, permissions, or any server-specific rule. It only exposes secure KubeJS events, and your scripts decide whether an action should be allowed or denied.
SecureGateEvents KubeJS event group.1.21.121.1.x2101.7.2-build.368 or compatibleThe mod registers the following server-side events:
SecureGateEvents.craft(event => {})
SecureGateEvents.useItem(event => {})
SecureGateEvents.placeBlock(event => {})
SecureGateEvents.useBlock(event => {})
SecureGateEvents.breakBlock(event => {})
SecureGateEvents.autoCraft(event => {})
Every event exposes:
event.deny(message)
event.cancel()
Use deny(message) when you want to block the action and show a custom message.
event.deny('You have not unlocked this action yet.')
Use cancel() when you want to block the action without a custom message.
event.cancel()
Depending on the action, events may expose:
event.player
event.item
event.itemId
event.result
event.resultId
event.block
event.blockId
event.recipeId
event.pos
event.hand
event.level
event.action
event.denied
event.cancelled
event.message
IDs are exposed as strings, for example:
minecraft:diamond_pickaxe
minecraft:flint_and_steel
minecraft:beacon
Triggered when a player tries to pick up a crafting result.
SecureGateEvents.craft(event => {
if (event.resultId == 'minecraft:diamond_pickaxe') {
event.deny('You cannot craft this item yet.')
}
})
This is enforced through the result slot pickup path, so normal click and shift-click pickup are blocked server-side.
The craft event is also fired while the server calculates the visible crafting result.
When a script denies the preview, the server synchronizes the output slot as empty to the client. This makes blocked recipes visually disappear from the crafting output slot while keeping the authoritative pickup check in place.
You can distinguish preview from pickup with:
event.action == 'CRAFT_PREVIEW'
event.action == 'CRAFT_PICKUP'
Most scripts do not need to check event.action. A normal rule based on event.resultId applies to both preview and pickup.
Triggered when a player uses an item, including right-clicking a block with that item.
SecureGateEvents.useItem(event => {
if (event.itemId == 'minecraft:flint_and_steel') {
event.deny('You cannot use Flint and Steel yet.')
}
})
Triggered when a player attempts to place a block.
SecureGateEvents.placeBlock(event => {
if (event.blockId == 'minecraft:beacon') {
event.deny('You cannot place Beacons yet.')
}
})
Triggered when a player interacts with a placed block.
SecureGateEvents.useBlock(event => {
if (event.blockId == 'minecraft:enchanting_table') {
event.deny('You cannot use Enchanting Tables yet.')
}
})
Triggered when a player attempts to break a block.
SecureGateEvents.breakBlock(event => {
if (event.itemId == 'minecraft:diamond_pickaxe') {
event.deny('You cannot break blocks with this item yet.')
}
})
Triggered when a vanilla Crafter attempts to craft and dispense an item.
Automatic crafters do not have a player context, so event.player is null.
SecureGateEvents.autoCraft(event => {
if (event.resultId == 'minecraft:diamond_pickaxe') {
event.deny('This item cannot be crafted automatically.')
}
})
Available properties include:
event.result
event.resultId
event.recipeId
event.pos
event.level
event.player // null
This example blocks diamond pickaxe and Flint and Steel until the player reaches XP level 10. It also blocks Dark Oak placement and prevents vanilla Crafters from producing protected outputs.
const REQUIRED_LEVEL = 10
const BLOCKED_CRAFTS = [
'minecraft:diamond_pickaxe',
'minecraft:flint_and_steel'
]
const BLOCKED_USE_ITEMS = [
'minecraft:flint_and_steel'
]
const BLOCKED_BREAK_ITEMS = [
'minecraft:diamond_pickaxe'
]
const BLOCKED_PLACE_BLOCKS = [
'minecraft:dark_oak_log',
'minecraft:dark_oak_wood',
'minecraft:stripped_dark_oak_log',
'minecraft:stripped_dark_oak_wood',
'minecraft:dark_oak_planks'
]
function hasRequiredLevel(player) {
return player && player.experienceLevel >= REQUIRED_LEVEL
}
function denyLevel(event, actionName) {
const level = event.player ? event.player.experienceLevel : 0
event.deny(`You need level ${REQUIRED_LEVEL} to ${actionName}. Current level: ${level}/${REQUIRED_LEVEL}.`)
}
SecureGateEvents.craft(event => {
if (!BLOCKED_CRAFTS.includes(event.resultId)) return
if (hasRequiredLevel(event.player)) return
denyLevel(event, `craft ${event.resultId}`)
})
SecureGateEvents.useItem(event => {
if (!BLOCKED_USE_ITEMS.includes(event.itemId)) return
if (hasRequiredLevel(event.player)) return
denyLevel(event, `use ${event.itemId}`)
})
SecureGateEvents.breakBlock(event => {
if (!BLOCKED_BREAK_ITEMS.includes(event.itemId)) return
if (hasRequiredLevel(event.player)) return
denyLevel(event, `break blocks with ${event.itemId}`)
})
SecureGateEvents.placeBlock(event => {
if (!BLOCKED_PLACE_BLOCKS.includes(event.blockId)) return
if (hasRequiredLevel(event.player)) return
denyLevel(event, `place ${event.blockId}`)
})
SecureGateEvents.autoCraft(event => {
if (!BLOCKED_CRAFTS.includes(event.resultId)) return
event.deny(`Automatic crafting is blocked for ${event.resultId}.`)
})
This example uses a generic server-defined progress function. Replace global.getPlayerProgress(player) with whatever your modpack already provides.
const CRAFT_REQUIREMENTS = {
'minecraft:diamond_pickaxe': 25,
'minecraft:netherite_sword': 75,
'minecraft:beacon': 150
}
const USE_ITEM_REQUIREMENTS = {
'minecraft:ender_pearl': 20,
'minecraft:elytra': 100
}
const PLACE_BLOCK_REQUIREMENTS = {
'minecraft:beacon': 150
}
const USE_BLOCK_REQUIREMENTS = {
'minecraft:enchanting_table': 30,
'minecraft:anvil': 10
}
function getProgress(player) {
return global.getPlayerProgress(player)
}
function denyIfMissingProgress(event, required, actionName) {
const progress = getProgress(event.player)
if (progress >= required) {
return
}
event.deny(`You need progress ${required} to ${actionName}. Current: ${progress}/${required}.`)
}
SecureGateEvents.craft(event => {
const required = CRAFT_REQUIREMENTS[event.resultId]
if (!required) return
denyIfMissingProgress(event, required, `craft ${event.resultId}`)
})
SecureGateEvents.useItem(event => {
const required = USE_ITEM_REQUIREMENTS[event.itemId]
if (!required) return
denyIfMissingProgress(event, required, `use ${event.itemId}`)
})
SecureGateEvents.placeBlock(event => {
const required = PLACE_BLOCK_REQUIREMENTS[event.blockId]
if (!required) return
denyIfMissingProgress(event, required, `place ${event.blockId}`)
})
SecureGateEvents.useBlock(event => {
const required = USE_BLOCK_REQUIREMENTS[event.blockId]
if (!required) return
denyIfMissingProgress(event, required, `use ${event.blockId}`)
})
This mod blocks actions before they are completed by the server. It does not rely on removing items after the fact, inventory tick checks, or global recipe removal.
Craft preview hiding is a server-synchronized UX feature. The secure server-side pickup gate still runs independently.
The mod does not implement progression logic. All progression checks should live in KubeJS scripts.
event.resultId. event.recipeId is available for craft preview and automatic crafter, but full recipe tracking for every manual craft pickup path is still limited.autoCraft.