From taboolib-dev
Guides building Minecraft plugins using TabooLib 6.x framework (Kotlin/Java) for Bukkit/Spigot/Paper. Covers project setup, module selection, commands, UI, config, database, NMS, Kether, lifecycle, and troubleshooting.
npx claudepluginhub taboolib/taboolib-skill --plugin taboolib-devThis skill uses the workspace's default tool permissions.
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**.
Develops Minecraft server plugins using Bukkit, Spigot, Paper APIs: events, commands, world/player management, performance optimization. For plugin architecture, mechanics, server features.
Guides Minecraft server plugin development using Bukkit, Spigot, and Paper APIs, covering events, commands, NMS internals, performance optimization, and integrations with databases and Docker.
Assists with Maven for Java: initializes projects, configures pom.xml, manages dependencies and scopes, sets up builds/plugins/profiles, troubleshoots errors.
Share bugs, ideas, or general feedback.
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:
@Awake, @SubscribeEvent, @Schedule, @Config)When user wants to create a new TabooLib plugin:
build.gradle.kts with 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"
}
object, NOT class):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() { }
}
plugin.yml needed — TabooLib auto-generates it.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):
UI depends on CHAT + NMSLANG depends on CONFIGURATIONNMS_UTIL depends on NMSAlways 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
Before the plugin is considered complete, verify:
build.gradle.kts has correct taboolib {} block with needed modulesobject extending Plugin(), not a class@Awake object, not global variablessubmitAsync {}@Config + @ConfigNode for auto-injectionUser says: "Create a TabooLib plugin with a config and a reload command"
Action:
build.gradle.kts with UNIVERSAL, BUKKIT, CONFIGURATIONPlugin object main classSettings object with @Configreload subcommandconfig.ymlUser says: "I need a shop GUI plugin with TabooLib"
Action:
UNIVERSAL, BUKKIT, CONFIGURATION, CHAT, NMS, NMS_UTIL, UI, BUKKIT_ALLmap() layoutUser says: "Player data storage plugin with MySQL"
Action:
UNIVERSAL, BUKKIT, CONFIGURATION, DATABASETable API@SubscribeEvent handlerssubmitAsync {} for all DB operationsconfig.yml with database connection settingstaboolib { env { install(...) } }references/module-map.md for dependencies.@Awake annotation on the object, or object is in a package not scanned@Awake is on the object declaration. TabooLib scans all classes in the plugin JAR.MinecraftVersion.isHigherOrEqual() guards. For packet operations, check field names per version.autoReload = true not set, or FileWatcher not activeautoReload = true in @Config. For manual reload call config.reload().handLocked(false) or wrong slot mappingmap() character assignments match set() and onClick() characters.io.izzel.taboolib plugin version matches, and mavenCentral() is in repositories.