name: taboolib-dev description: > Help developers build Minecraft plugins using TabooLib 6.x framework (Kotlin/Java). Use when user mentions "taboolib", "TabooLib plugin", "minecraft plugin with taboolib", "bukkit plugin framework", "create taboolib project", "taboolib command", "taboolib config", "taboolib UI", "taboolib database", "taboolib NMS", "taboolib kether", or asks about TabooLib module setup, @Awake lifecycle, cross-version compatibility. Covers project setup, module selection, API usage, and troubleshooting. license: MIT compatibility: Claude Code, Claude.ai metadata: author: community version: 1.0.0 category: minecraft-development tags: [minecraft, kotlin, taboolib, bukkit, plugin-development] source-repo: https://github.com/TabooLib/taboolib source-commit: 4eaffc793a25962453849ae77597198d1643d765 source-date: "2026-02-23" taboolib-version: 6.1.2-beta12 gradle-plugin-version: "2.0.31"
TabooLib Plugin Development
Overview
TabooLib is a Kotlin-based cross-platform Minecraft plugin framework (supports Bukkit/Spigot/Paper, BungeeCord, Velocity). Current stable version: 6.1.2-beta12, Gradle plugin: 2.0.31.
Key traits:
- ~30KB runtime footprint (modules downloaded on demand)
- Kotlin DSL APIs for commands, UI, config, database
- Cross-version NMS abstraction (1.8 ~ 1.21+)
- Annotation-driven lifecycle (
@Awake,@SubscribeEvent,@Schedule,@Config)
Instructions
Step 1: Project Setup
When user wants to create a new TabooLib plugin:
- Generate
build.gradle.ktswith the TabooLib Gradle plugin:
import io.izzel.taboolib.gradle.*
plugins {
java
id("io.izzel.taboolib") version "2.0.31"
kotlin("jvm") version "2.0.0"
}
taboolib {
env {
install(UNIVERSAL, BUKKIT)
// Add modules as needed: CONFIGURATION, LANG, CHAT, NMS, NMS_UTIL, UI, DATABASE, KETHER
}
version { taboolib = "6.1.2-beta12" }
}
repositories {
mavenCentral()
}
dependencies {
compileOnly("ink.ptms.core:v12004:12004:mapped")
compileOnly("ink.ptms.core:v12004:12004:universal")
compileOnly(kotlin("stdlib"))
taboo("ink.ptms:um:1.2.0") // Universal Minecraft API
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
- Create the plugin main class (use
object, NOTclass):
import taboolib.common.platform.Plugin
object MyPlugin : Plugin() {
override fun onLoad() { }
override fun onEnable() { info("Plugin enabled!") }
override fun onActive() { } // Called after server fully started
override fun onDisable() { }
}
- No
plugin.ymlneeded — TabooLib auto-generates it.
Step 2: Module Selection Guide
Help user pick the right modules based on their needs:
| Need | Module Constant | Notes |
|---|---|---|
| Config files (YAML/TOML/HOCON) | CONFIGURATION |
Enables @Config, @ConfigNode |
| Multi-language / i18n | LANG |
Requires CONFIGURATION |
| Rich text / clickable messages | CHAT |
Components API |
| NMS / packet operations | NMS, NMS_UTIL |
Cross-version abstraction |
| Chest GUI / Anvil GUI | UI |
Requires CHAT + NMS |
| Database (MySQL/SQLite) | DATABASE |
HikariCP connection pool |
| Kether scripting | KETHER |
Requires CONFIGURATION |
| Bukkit utilities (ItemBuilder etc) | BUKKIT_ALL |
XMaterial, ItemBuilder |
| PlaceholderAPI / Vault hooks | BUKKIT_HOOK |
Third-party integration |
| BungeeCord messaging | PORTICUS |
Cross-server communication |
Module dependencies (auto-resolved by Gradle plugin, but useful to know):
UIdepends onCHAT+NMSLANGdepends onCONFIGURATIONNMS_UTILdepends onNMS
Step 3: Core API Patterns
Always follow these patterns when writing TabooLib code:
Pattern: Singleton Object + @Awake
@Awake
object PlayerManager {
private val cache = ConcurrentHashMap<UUID, PlayerData>()
@Awake(LifeCycle.ENABLE)
fun init() { /* startup logic */ }
@SubscribeEvent
fun onQuit(event: PlayerQuitEvent) {
cache.remove(event.player.uniqueId)
}
@Schedule(async = true, period = 6000L)
fun autoSave() {
cache.values.forEach { it.save() }
}
}
Pattern: Config Injection
@Awake
object Settings {
@Config("config.yml", autoReload = true)
lateinit var config: Configuration
@ConfigNode("debug")
var debug = false
@ConfigNode("settings.max-players")
var maxPlayers = 100
}
Pattern: Command DSL
command("myplugin", aliases = listOf("mp"), permission = "myplugin.use") {
literal("reload") {
execute<ProxyCommandSender> { sender, _, _ ->
Settings.config.reload()
sender.sendMessage("Reloaded!")
}
}
dynamic("player") {
suggestion<ProxyCommandSender> { _, _ ->
Bukkit.getOnlinePlayers().map { it.name }
}
execute<ProxyCommandSender> { sender, _, argument ->
sender.sendMessage("Target: $argument")
}
}
}
Pattern: Event Listening
// Annotation-based (recommended)
@Awake
object Listeners {
@SubscribeEvent
fun onJoin(event: PlayerJoinEvent) {
event.player.sendMessage("Welcome!")
}
@SubscribeEvent(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun onDamage(event: EntityDamageEvent) { /* ... */ }
}
// Functional style
listenEvent<PlayerJoinEvent> {
it.player.sendMessage("Welcome!")
}
Pattern: Chest UI
player.openMenu<Chest> {
title("My Menu")
rows(3)
map(
"# # # # # # # # #",
"# A # B # C # D #",
"# # # # # # # # #"
)
set('A', buildItem(XMaterial.DIAMOND) { name = "&bDiamond"; colored() })
onClick('A') { event -> event.clicker.sendMessage("Clicked!") }
handLocked(false)
}
Pattern: Database Table
val table = Table<HostSQL, Connection>("player_data", host) {
add("uuid") { type(ColumnTypeSQL.VARCHAR, 36) }
add("data") { type(ColumnTypeSQL.TEXT) }
}
// Always use async for DB operations
submitAsync {
table.createTable(dataSource)
table.select(dataSource) {
where { "uuid" eq player.uniqueId.toString() }
}.firstOrNull()
}
Pattern: Task Scheduling
// Delayed sync task (20 ticks = 1 second)
submit(delay = 20L) { /* runs on main thread */ }
// Async repeating task
submit(async = true, period = 20L) { /* every 1 second, off main thread */ }
// Annotation-based
@Schedule(async = true, period = 100L, delay = 20L)
fun periodicTask() { /* called automatically */ }
Pattern: NMS Version Guard
if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_17)) {
// 1.17+ specific code
}
// majorLegacy: 12004 = 1.20.4
val ver = MinecraftVersion.majorLegacy
Step 4: Validation Checklist
Before the plugin is considered complete, verify:
-
build.gradle.ktshas correcttaboolib {}block with needed modules - Main class is an
objectextendingPlugin(), not aclass - All stateful components use
@Awake object, not global variables - Database operations are wrapped in
submitAsync {} - NMS-dependent code has version guards
- Commands have proper permission nodes
- Config files use
@Config+@ConfigNodefor auto-injection - No blocking operations on the main thread
Examples
Example 1: Basic Plugin with Config and Command
User says: "Create a TabooLib plugin with a config and a reload command"
Action:
- Set up
build.gradle.ktswithUNIVERSAL,BUKKIT,CONFIGURATION - Create
Pluginobject main class - Create
Settingsobject with@Config - Create command with
reloadsubcommand - Create default
config.yml
Example 2: Plugin with Chest GUI
User says: "I need a shop GUI plugin with TabooLib"
Action:
- Set up with
UNIVERSAL,BUKKIT,CONFIGURATION,CHAT,NMS,NMS_UTIL,UI,BUKKIT_ALL - Create config for shop items
- Build Chest menu with
map()layout - Handle click events for purchase logic
- Add NMS version check if using version-specific features
Example 3: Plugin with Database
User says: "Player data storage plugin with MySQL"
Action:
- Set up with
UNIVERSAL,BUKKIT,CONFIGURATION,DATABASE - Define table schema using
TableAPI - Create async data load/save in
@SubscribeEventhandlers - Use
submitAsync {}for all DB operations - Configure
config.ymlwith database connection settings
Troubleshooting
Error: Module not found at runtime
- Cause: Module not installed in
taboolib { env { install(...) } } - Solution: Add the missing module constant. Check
references/module-map.mdfor dependencies.
Error: @Awake object not being discovered
- Cause: Missing
@Awakeannotation on the object, or object is in a package not scanned - Solution: Ensure
@Awakeis on the object declaration. TabooLib scans all classes in the plugin JAR.
Error: NMS operation fails on specific MC version
- Cause: NMS API changed between versions
- Solution: Use
MinecraftVersion.isHigherOrEqual()guards. For packet operations, check field names per version.
Error: Config not reloading
- Cause:
autoReload = truenot set, orFileWatchernot active - Solution: Set
autoReload = truein@Config. For manual reload callconfig.reload().
Error: UI clicks not registering
- Cause: Missing
handLocked(false)or wrong slot mapping - Solution: Verify
map()character assignments matchset()andonClick()characters.
Error: Gradle build fails with TabooLib plugin
- Cause: Version mismatch or repository not configured
- Solution: Ensure
io.izzel.taboolibplugin version matches, andmavenCentral()is in repositories.
Error: Chinese filename causes server crash on restart
- Known bug: Avoid Chinese characters in config file names. Use ASCII-only filenames.