β¨ Beautiful DSL β’ Coroutine-First β’ Dupe-Safe β’ Production-Ready β¨
Quick Start β’ Features β’ Installation β’ API Reference β’ Examples
Write beautiful GUIs in 6 lines, not 60.
DaisyMenu is a modern, coroutine-first GUI library built specifically for Paper and its forks. It provides the cleanest DSL syntax, complete dupe protection, and seamless async support.
player.openMenu {
title = "&6&lβ¦ Shop β¦"
rows = 3
slot(13) {
item(Material.DIAMOND) {
name("&bπ Diamond")
lore("&7Price: &a$100", "&8Click to buy")
glow()
}
onClick { player, _ ->
player.sendMessage("&aβ Purchased!".mm())
}
}
}| Feature | Description |
|---|---|
| π¨ Beautiful DSL | Clean, readable Kotlin syntax that feels natural |
| β‘ Coroutine-First | Full suspend function support in click handlers |
| π Dupe Protection | Complete protection against all known item duplication exploits |
| π MiniMessage | Built-in text formatting with gradients, hex colors, and legacy codes |
| π Pagination | Easy multi-page menus with automatic navigation |
| βοΈ Anvil Input | Get text input with a simple suspend function call |
| π― Pattern System | Create complex layouts with string-based patterns |
| π Live Updates | Update menu items in real-time without rebuilding |
| π Player Heads | Easy skull textures with UUID or Player |
| πΎ Persistent Data | Store custom data on items using PDC |
| π Paper Optimized | Built specifically for Paper 1.21+ and its forks |
| β Java Compatible | Works with Java plugins too |
repositories {
mavenCentral()
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.fu3i0n:DaisyMenu:1.0.0")
}repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.fu3i0n:DaisyMenu:1.0.0'
}<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.fu3i0n</groupId>
<artifactId>DaisyMenu</artifactId>
<version>1.0.0</version>
</dependency>DaisyMenu requires these dependencies (provided by Paper):
- Paper API 1.21+
- Kotlin Stdlib (for Kotlin plugins)
- Kotlinx Coroutines
import cat.daisy.menu.DaisyMenu
class MyPlugin : JavaPlugin() {
override fun onEnable() {
// Initialize DaisyMenu - REQUIRED
DaisyMenu.initialize(this)
}
override fun onDisable() {
// Clean shutdown
DaisyMenu.shutdown()
}
}import cat.daisy.menu.openMenu
import cat.daisy.menu.text.DaisyText.mm
suspend fun openShop(player: Player) {
player.openMenu {
title = "&b&lShop"
rows = 3
// Fill background
fill(Material.GRAY_STAINED_GLASS_PANE) { name(" ") }
// Add items
slot(11) {
item(Material.DIAMOND) {
name("&bDiamond")
lore("&7Price: &a$100", "&8Click to purchase")
}
onClick { p, _ ->
p.sendMessage("&aβ Purchased!".mm())
}
}
slot(15) {
item(Material.BARRIER) { name("&cClose") }
onClick { p, _ -> p.closeInventory() }
}
}
}getCommand("shop")?.setExecutor { sender, _, _, _ ->
if (sender is Player) {
// Use your coroutine scope
scope.launch {
openShop(sender)
}
}
true
}// Open a menu (suspend function)
suspend fun Player.openMenu(block: MenuBuilder.() -> Unit)
// Open anvil for text input (suspend function)
suspend fun Player.openAnvil(title: String, placeholder: String = ""): String?
// Open anvil with callback (non-suspend)
fun Player.openAnvilAsync(title: String, block: (String?) -> Unit)| Property/Method | Description |
|---|---|
title: String |
Menu title with MiniMessage support |
rows: Int |
Number of rows (1-6, default: 3) |
slot(index) { } |
Define a button at slot index (0-53) |
slot(x, y) { } |
Define a button at x,y coordinates (1-indexed) |
fill { } |
Fill empty slots with default glass pane |
fill(Material) { } |
Fill empty slots with specific material |
fillBorder { } |
Fill only the border slots |
fillRow(row) { } |
Fill a specific row (1-indexed) |
fillColumn(col) { } |
Fill a specific column (1-indexed) |
fillSlots(1, 2, 3) { } |
Fill specific slot indices |
pattern(...) { } |
Apply a string-based pattern |
pagination(perPage) { } |
Enable pagination |
onOpen { menu -> } |
Callback when menu opens |
onClose { menu -> } |
Callback when menu closes |
| Method | Description |
|---|---|
item(Material) { } |
Set the item with builder |
item(ItemStack) |
Set the item directly |
onClick { player, clickType -> } |
Suspend click handler |
onClick { player -> } |
Suspend click handler (ignore click type) |
onClickSync { player, clickType -> } |
Non-suspend click handler |
onClickSync { player -> } |
Non-suspend click handler (ignore click type) |
| Method | Description |
|---|---|
name("text") |
Set display name (MiniMessage) |
name(Component) |
Set display name (Component) |
lore("line1", "line2") |
Set lore lines (MiniMessage) |
lore(listOf("...")) |
Set lore from list |
addLore("line") |
Add single lore line |
amount(count) |
Set stack size (1-64) |
glow() |
Add enchantment glow |
enchant(Enchantment, level) |
Add enchantment |
unbreakable() |
Make item unbreakable |
customModelData(id) |
Set custom model data |
flags(ItemFlag...) |
Add item flags |
hideAttributes() |
Hide all attributes |
skullOwner(UUID) |
Set skull owner |
skullOwner(Player) |
Set skull owner from player |
persistentData(key, value) |
Store PDC data |
| Method | Description |
|---|---|
updateSlot(index) { } |
Update a slot's button |
updateSlot(index, Button) |
Update a slot with button |
fill(Button) |
Fill empty slots at runtime |
repeatUpdate(ticks) { } |
Repeat task while menu is open |
close() |
Close the menu |
import cat.daisy.menu.text.DaisyText.mm
// MiniMessage parsing (italic disabled by default)
val component = "&cRed &lBold Text".mm()
// Legacy color codes
"&a&lGreen Bold" // Works!
"&7Gray &8Dark Gray" // Works!
// MiniMessage tags
"<red>Red</red>" // Works!
"<#FF69B4>Pink Hex" // Works!
"<gradient:#FF0000:#0000FF>Gradient</gradient>" // Works!
// Extension functions
"Rainbow Text".rainbow() // Rainbow gradient
"Gradient".gradient("#FF0000", "#0000FF") // Custom gradientCreate complex layouts easily with string patterns:
player.openMenu {
title = "&b&lPattern Demo"
rows = 5
pattern(
"XXXXXXXXX",
"X X",
"X D X",
"X X",
"XXXXXXXXX"
) {
'X' to {
item(Material.BLACK_STAINED_GLASS_PANE) { name(" ") }
}
'D' to {
item(Material.DIAMOND) {
name("&bDiamond")
glow()
}
onClick { p, _ -> p.sendMessage("Clicked!".mm()) }
}
}
}Multi-page menus made simple:
suspend fun openPlayerList(player: Player) {
val allPlayers = Bukkit.getOnlinePlayers().toList()
player.openMenu {
title = "&b&lπ₯ Online Players"
rows = 6
fill { name(" ") }
pagination(itemsPerPage = 45) {
totalPages((allPlayers.size + 44) / 45)
for (i in pageItems()) {
val target = allPlayers.getOrNull(i) ?: continue
slot(i % 45) {
item(Material.PLAYER_HEAD) {
name("&6${target.name}")
lore("&7Click to teleport")
skullOwner(target)
}
onClick { viewer, _ ->
viewer.teleport(target.location)
}
}
}
// Previous page button
if (hasPrevious()) {
slot(45) {
item(Material.ARROW) { name("&cβ Previous") }
onClick { _, _ -> prevPage() }
}
}
// Page indicator
slot(49) {
item(Material.PAPER) {
name("&fPage ${currentPage + 1}/$totalPages")
}
}
// Next page button
if (hasNext()) {
slot(53) {
item(Material.ARROW) { name("&aβΆ Next") }
onClick { _, _ -> nextPage() }
}
}
}
}
}Get text input with a simple suspend call:
suspend fun renameItem(player: Player) {
val newName = player.openAnvil(
title = "&e&lRename Item",
placeholder = "&7Type new name..."
)
if (newName != null) {
player.sendMessage("&aβ Renamed to: &b$newName".mm())
} else {
player.sendMessage("&cCancelled".mm())
}
}Update menu content in real-time:
player.openMenu {
title = "&b&lServer Status"
rows = 3
slot(13) {
item(Material.CLOCK) { name("&7Loading...") }
}
onOpen { menu ->
// Update every second (20 ticks)
menu.repeatUpdate(20L) {
val tps = Bukkit.getTPS()[0]
val color = if (tps >= 18.0) "&a" else if (tps >= 15.0) "&e" else "&c"
menu.updateSlot(13) {
item(Material.CLOCK) {
name("$colorβ‘ TPS: %.1f".format(tps))
lore(
"&7Players: &b${Bukkit.getOnlinePlayers().size}",
"&7Memory: &b${Runtime.getRuntime().freeMemory() / 1024 / 1024}MB free"
)
}
}
}
}
}DaisyMenu includes complete dupe protection out of the box:
- β Blocks all dangerous inventory actions (COLLECT_TO_CURSOR, HOTBAR_SWAP, etc.)
- β Cancels shift-click exploits
- β Prevents drag events on menu slots
- β Clears cursor on close to prevent ghost items
- β Validates clicker is the menu owner
- β High-priority event handlers to override other plugins
No configuration needed β protection is automatic.
DaisyMenu works with Java too:
import cat.daisy.menu.DaisyMenu;
import cat.daisy.menu.MenuBuilder;
import cat.daisy.menu.ExtensionsKt;
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
DaisyMenu.INSTANCE.initialize(this, null);
}
public void openMenu(Player player) {
// Use the Java-friendly API
MenuBuilder builder = new MenuBuilder();
builder.setTitle("&bShop");
builder.setRows(3);
builder.slot(13, slot -> {
slot.item(Material.DIAMOND, item -> {
item.name("&bDiamond");
item.lore("&7Click to buy");
return null;
});
slot.onClickSync(p -> {
p.sendMessage("Purchased!");
return null;
});
return null;
});
// Build and open
builder.build().open(player);
}
}| Feature | DaisyMenu | mc-chestui-plus | InventoryFramework |
|---|---|---|---|
| DSL Quality | βββββ | βββ | ββ |
| Coroutine Support | β Native | β | β |
| Dupe Protection | β Complete | ||
| Pattern System | β | β | β |
| Anvil Input | β Suspend | β | β Callback |
| Live Updates | β | β | β |
| Text Formatting | β Built-in | β | β |
| Memory Efficiency | βββββ | βββ | βββ |
| Lines of Code | ~6 | ~15 | ~20 |
| Paper Optimized | β |
cat.daisy.menu/
βββ DaisyMenu.kt # Main singleton, initialization
βββ Menu.kt # Menu class, runtime operations
βββ MenuBuilder.kt # DSL builder for menus
βββ Button.kt # Button class, ItemBuilder DSL
βββ Pagination.kt # Pagination handler
βββ AnvilMenu.kt # Anvil text input
βββ Extensions.kt # Player extension functions
βββ text/
βββ DaisyText.kt # MiniMessage text utilities
- Paper 1.21+ (or forks like Purpur, Pufferfish)
- Java 21+
- Kotlin 2.0+ (for Kotlin plugins)
- Kotlinx Coroutines 1.8+
MIT License - see LICENSE for details.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with π by fu3i0n
If DaisyMenu helps your project, consider giving it a β!