MiniMessage: easy formatted text
Write colorful messages with simple tags like <green> and <bold>, and fill in placeholders safely.
MiniMessage lets you write colorful, clickable Minecraft text as simple tags, like <green>Hello <bold>world. On this page you will learn every tag you need, how to put player names and numbers into messages safely, how to keep messages in a messages.yml file, and how to convert old &a color codes.
What MiniMessage is
On the previous page you built formatted text in Java: Component.text("Hi", NamedTextColor.GREEN).append(...). That is precise, but a long message turns into a long chain of method calls. MiniMessage is a second way to make the exact same component. You write the text with tags in angle brackets, a bit like HTML on a web page, and the library turns it into a component for you.
1<gold>Welcome, <aqua><bold>Steve</bold></aqua>! <gray>Type <yellow>/help</yellow> to start.- An opening tag such as
<gold>switches a style on. Everything after it uses that style. - A closing tag with a slash, such as
</bold>, switches it off again. You may leave closing tags out at the very end of a message. - Some tags take values after a colon, such as
<gradient:red:blue>or<click:run_command:/spawn>.
Sending MiniMessage from your plugin
There are two ways to turn MiniMessage text into something a player sees. Both are in this listener, which greets every player who joins:
Where this file livesminimessage-firstsrcmainjavacomexampleminimessagefirstJoinGreeter.java
The package com.example.minimessagefirst is the folder path com/example/minimessagefirst inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-first/
- src/main/
- java/com/example/minimessagefirst/Package com.example.minimessagefirst
- JoinGreeter.javayou are hereListener: reacts to events
- MiniFirstPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagefirst/Package com.example.minimessagefirst
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.minimessagefirst;2 3import net.kyori.adventure.text.Component;4import net.kyori.adventure.text.minimessage.MiniMessage;5import net.kyori.adventure.text.minimessage.tag.resolver.Formatter;6import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;7import org.bukkit.Bukkit;8import org.bukkit.entity.Player;9import org.bukkit.event.EventHandler;10import org.bukkit.event.Listener;11import org.bukkit.event.player.PlayerJoinEvent;12 13public final class JoinGreeter implements Listener {14 15 private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();16 17 @EventHandler18 public void onJoin(PlayerJoinEvent event) {19 Player player = event.getPlayer();20 21 player.sendRichMessage("<green>Welcome to the server!");22 23 Component greeting = MINI_MESSAGE.deserialize(24 "<gradient:gold:yellow><bold>Hello</bold></gradient> <aqua><name></aqua>, there are <white><online></white> players online.",25 Placeholder.unparsed("name", player.getName()),26 Formatter.number("online", Bukkit.getOnlinePlayers().size()));27 player.sendMessage(greeting);28 29 player.sendRichMessage(30 "<gray>Your name is <name>.",31 Placeholder.unparsed("name", player.getName()));32 }33}- One shared MiniMessage object.
MiniMessage.miniMessage()gives you the standard one, with every normal tag turned on. Keep it in a constant so you do not create it again for every message. - The shortcut.
sendRichMessagereads the MiniMessage text, makes a component and sends it, all in one call. It exists on everyCommandSender, so players and the console both have it. - The longer way.
deserializeturns MiniMessage text into aComponentand gives it back to you, so you can store it, change it or send it later. "Deserialize" means "turn text into an object". - A gradient: the letters fade smoothly from gold to yellow. You will meet all tags in the table below.
- A placeholder: a gap in the message that your code fills in. This one is not a real tag, so MiniMessage asks the resolvers you pass in (small helpers that know what to put in a gap) for one named
name. - Fills the
<name>gap with the player's name. "Unparsed" means the value is inserted as plain text and never read for tags. This is the safe choice for anything a player controls. - Fills
<online>with a number, written the way a person would write it (1,250, not1250). - Sending the finished component works exactly like on the previous page.
The registration lines are the same as in Events and listeners:
Where this file livesminimessage-firstsrcmainjavacomexampleminimessagefirstMiniFirstPlugin.java
The package com.example.minimessagefirst is the folder path com/example/minimessagefirst inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-first/
- src/main/
- java/com/example/minimessagefirst/Package com.example.minimessagefirst
- JoinGreeter.javaListener: reacts to events
- MiniFirstPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagefirst/Package com.example.minimessagefirst
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.minimessagefirst;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class MiniFirstPlugin extends JavaPlugin {6 7 @Override8 public void onEnable() {9 getServer().getPluginManager().registerEvents(new JoinGreeter(), this);10 }11}What Steve sees when he joins and one other player is online:
Hello Steve, there are 2 players online.
Your name is Steve.
How tags work
Opening and closing
Tags nest like boxes. A closing tag closes the matching opening tag, and any tag that was opened after it:
1<red>This is red, <blue>this is blue</blue> and red again, <bold>bold and red</bold>.2<gold><bold>Everything</gold> after the gold closing tag is plain again.Everything after the gold closing tag is plain again.
Two more tools for taking styles away: <reset> clears everything at once, and a negated tag with an exclamation mark, such as <!bold>, switches just that one style off for the rest of the text. The negated form is also how you turn off the italics that item names get by default: <!italic>My sword.
Arguments and quotes
A colon separates a tag's arguments. When an argument contains a colon, a space or another tag, wrap it in single or double quotes:
1<hover:show_text:'<red>Careful, this is <bold>hot</bold>!'>Point at me2<click:run_command:'/msg Steve hello there'>Click to greet SteveWhen MiniMessage does not understand a tag
MiniMessage never crashes on a typo. A tag it does not know is simply printed as text, and a closing tag with nothing to close is printed too. So if you see <gren> in chat, you misspelled a tag name. The old color codes are also not understood: &6Hello shows up as the letters &6Hello.
Showing a real angle bracket
To show a literal < that is not a tag, put a backslash in front of it. In a Java string the backslash itself must be doubled, because Java also uses the backslash as its own escape character:
1player.sendRichMessage("Type \\<name> to fill in a name");For text you do not control, use the tools in the placeholder section below instead of escaping by hand.
The tags
These are all the tags a beginner needs. Every example below is MiniMessage, then what the player sees.
Colors
Use the 16 named colors (black, dark_blue, dark_green, dark_aqua, dark_red, dark_purple, gold, gray, dark_gray, blue, green, aqua, red, light_purple, yellow, white) as tags, or a hex color. Hex is six digits after a hash sign: <#ff8800>. The long form <color:#ff8800> is the same. The exact colors are in the table on Text and colors with Adventure.
1<red>Red <#ff8800>orange <color:aqua>aqua <#7f5af0>violetDecorations
| Tag | Short form | Effect |
|---|---|---|
<bold> | <b> | Bold |
<italic> | <i>, <em> | Italic |
<underlined> | <u> | Underlined |
<strikethrough> | <st> | Crossed out |
<obfuscated> | <obf> | Scrambled, constantly changing letters |
Reset and newline
1<gold><bold>Title<reset> back to plain<newline><gray>A second line (you can also write br)Gradient, rainbow and transition
These three change the color from letter to letter:
<gradient:red:blue>fades between two or more colors. You can list as many as you like, with names or hex codes.<rainbow>runs through all rainbow colors.<rainbow:!>runs it backwards.<transition:red:blue:0.5>gives the whole text one color that sits partway between the two. The last number, from 0 to 1, says how far. It is useful for animations where your code changes the number every tick.
1<gradient:#7f5af0:#2cb67d><bold>Nebula Network</bold></gradient>2<rainbow>Every letter is a different color</rainbow>3<gradient:red:gold:green>A three color gradient</gradient>4<transition:red:blue:0.5>Exactly halfway</transition>Every letter is a different color
A three color gradient
Exactly halfway
Click
The first value is the action, and the second is what it works on. These are the same actions as on the previous page:
| Tag | What happens on click |
|---|---|
<click:run_command:/spawn> | The player runs the command. Include the slash. |
<click:suggest_command:'/msg Steve '> | Puts text in the chat box without sending it. |
<click:open_url:'https://papermc.io'> | Opens a web page after the player confirms. |
<click:copy_to_clipboard:play.example.com> | Copies the text. |
<click:change_page:2> | Turns to page 2 of a written book. |
Hover
<hover:show_text:'...'> shows a tooltip, and the tooltip can contain tags too. <hover:show_item:diamond_sword:1> shows an item's real tooltip. Put a hover and a click on the same text by nesting them:
1<click:run_command:/spawn><hover:show_text:'<gray>Teleports you to <green>spawn'><green>[Go to spawn]</hover></click>Key, lang, font and insertion
<key:key.jump>shows the key the player really uses for jumping, even if they changed it. Use it in tutorials: "Press<key:key.jump>twice to fly".<lang:item.minecraft.diamond_sword>shows a vanilla name in the player's own language, like a translatable component.<font:uniform>switches the font. The names come from the resource pack;uniform,altandillageraltare built into the game.<insert:Steve>pastes "Steve" into the chat box when a player shift-clicks the text.
1Press <yellow><key:key.jump></yellow> twice to fly.2You found a <aqua><lang:item.minecraft.diamond_sword></aqua>!3<insert:Steve>Shift-click my nameYou found a !
Shift-click my name
Placeholders: filling in names, numbers and more
Most messages contain something that changes: a player name, a score, a time. A placeholder is a gap you write as a tag, like <name>, and fill in from Java. You give MiniMessage a TagResolver, an object that says "when you see the tag name, put this here". Paper's sendRichMessage and MiniMessage's deserialize both accept any number of resolvers after the text.
| Resolver | What it inserts | Use it for |
|---|---|---|
Placeholder.unparsed("name", text) | The text exactly as it is. Tags inside it are shown as letters. | Anything a player typed or chose: names, chat, signs, item names. |
Placeholder.parsed("name", text) | The text, after reading its tags like the rest of the message. | Text you wrote, such as a prefix from your own config. It also works inside tag arguments, as in <click:run_command:'/tp <who>'>. |
Placeholder.component("name", component) | A ready-made component, with its own color, hover and click. | Items, display names, buttons you built in Java. |
Placeholder.styling("name", NamedTextColor.RED, TextDecoration.BOLD) | A style, used like a tag: <warn>careful. | Your own named styles, defined once in Java. |
Formatter.number("name", 1250.5) | A number with thousands separators: 1,250.5. | Money, scores, counts. |
Why unparsed matters: players can type tags
MiniMessage tags are just text, so a player can type them too. If you paste player text into your message as parsed, the player gets to use every tag, including the dangerous ones. Here is a chat feature that does this wrong:
1player.sendRichMessage(2 "<gray>Steve says: <msg>",3 Placeholder.parsed("msg", playerTypedText));- Whatever the player typed is read as MiniMessage. A player can type
<click:run_command:/spawn>Free diamonds</click>and everyone who clicks the text runs a command. They can also write fake system messages, hide text, or make the line a thousand letters of gradient.
The fix is a one-word change. With unparsed, the tags stay as visible letters and do nothing:
1player.sendRichMessage(2 "<gray>Steve says: <msg>",3 Placeholder.unparsed("msg", playerTypedText));- The text goes in exactly as typed. A player who types tags just sees their own tags in the message.
The rule is simple: use unparsed for everything you did not write yourself. Use parsed only for text that comes from you or from your server's owner.
Two helpers fit nearby jobs. MiniMessage.miniMessage().escapeTags(text) puts backslashes in front of every tag so it can be pasted into a message safely, and MiniMessage.miniMessage().stripTags(text) removes all tags and keeps only the words:
1MiniMessage mini = MiniMessage.miniMessage();2String escaped = mini.escapeTags("<red>hi");3String stripped = mini.stripTags("<red>hi <bold>there</bold>");After these two lines, escaped holds \<red>hi and stripped holds hi there.
Keeping messages in a messages.yml file
Messages hidden inside Java code are hard to change: every edit needs a rebuild. Server owners like to change wording, colors and languages, so most plugins keep their messages in a file next to the jar. The usual name is messages.yml. This is the file of the demo plugin:
Where this file livesminimessage-demosrcmainresourcesmessages.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- minimessage-demo/
- src/main/
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- AnnounceCommand.javaCommand (BasicCommand)
- ConvertCommand.javaCommand (BasicCommand)
- Messages.javaHelper class
- MiniDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- ReloadCommand.javaCommand (BasicCommand)
- WelcomeListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- messages.ymlyou are hereMessage texts that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1prefix: "<gradient:#7f5af0:#2cb67d><bold>Nebula</bold></gradient> <dark_gray>|</dark_gray> "2welcome: "<green>Welcome, <white><player></white>! <gray>There are <white><online></white> players online."3announce: "<gold><bold>Announcement</bold></gold> <gray>from <sender>:</gray> <yellow><message>"4usage: "<red>Usage: /announce your message here"5reloaded: "<green>Messages reloaded."6convert-usage: "<red>Usage: /convertcolors followed by old text with ampersand codes"7converted: "<gray>Converted, click to copy: <text>"- One message that every other message starts with. Change it in one place and the whole plugin changes.
- A message with two placeholders,
<player>and<online>. The Java code supplies both. - Placeholders
<sender>and<message>. Look at how the code fills them in below.
YAML quoting matters. Wrap every message in double quotes, because MiniMessage text has colons, angle brackets and sometimes single quotes, which YAML would otherwise misread. If you need a double quote inside, use single quotes for the outside. How YAML files work is explained on Configuration files.
A small helper class loads the file once, and then every part of the plugin asks it for a message by key:
Where this file livesminimessage-demosrcmainjavacomexampleminimessagedemoMessages.java
The package com.example.minimessagedemo is the folder path com/example/minimessagedemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-demo/
- src/main/
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- AnnounceCommand.javaCommand (BasicCommand)
- ConvertCommand.javaCommand (BasicCommand)
- Messages.javayou are hereHelper class
- MiniDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- ReloadCommand.javaCommand (BasicCommand)
- WelcomeListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- messages.ymlMessage texts that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.minimessagedemo;2 3import java.io.File;4import java.io.IOException;5import java.io.InputStream;6import java.io.InputStreamReader;7import java.nio.charset.StandardCharsets;8import java.util.logging.Level;9import net.kyori.adventure.audience.Audience;10import net.kyori.adventure.text.Component;11import net.kyori.adventure.text.minimessage.MiniMessage;12import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;13import org.bukkit.configuration.file.YamlConfiguration;14import org.bukkit.plugin.java.JavaPlugin;15 16public final class Messages {17 18 private static final String FILE_NAME = "messages.yml";19 private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();20 21 private final JavaPlugin plugin;22 private final File file;23 private YamlConfiguration yaml = new YamlConfiguration();24 25 public Messages(JavaPlugin plugin) {26 this.plugin = plugin;27 this.file = new File(plugin.getDataFolder(), FILE_NAME);28 reload();29 }30 31 public void reload() {32 if (!file.exists()) {33 plugin.saveResource(FILE_NAME, false);34 }35 YamlConfiguration loaded = YamlConfiguration.loadConfiguration(file);36 try (InputStream bundled = plugin.getResource(FILE_NAME)) {37 if (bundled != null) {38 loaded.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(bundled, StandardCharsets.UTF_8)));39 }40 } catch (IOException exception) {41 plugin.getLogger().log(Level.WARNING, "Could not read the bundled " + FILE_NAME, exception);42 }43 yaml = loaded;44 }45 46 public Component component(String key, TagResolver... resolvers) {47 String template = yaml.getString("prefix", "") + yaml.getString(key, key);48 return MINI_MESSAGE.deserialize(template, resolvers);49 }50 51 public void send(Audience audience, String key, TagResolver... resolvers) {52 audience.sendMessage(component(key, resolvers));53 }54 55 public void broadcast(String key, TagResolver... resolvers) {56 plugin.getServer().broadcast(component(key, resolvers));57 }58}- The name of the file, written once.
- Copies
messages.ymlout of the jar into the plugin's folder, but only when it is not there yet. Thefalsemeans "never overwrite", so the owner's edits survive updates. - Reads the owner's file from disk.
- Also loads the original file from inside the jar as a fallback. If the owner deletes a line, or a new plugin version adds a message, the built-in text is still found.
- Looks up the text for a key, puts the prefix in front and turns it into a component. Everything after the key is a list of placeholders. The
...means "any number of them", including none. - If the key is missing from both files, the key itself is shown, so you can see which message you forgot.
- Sends a message to any audience: a player, the console or a command sender.
- Sends a message to everyone online.
The /announce command
Now everything comes together. /announce Server restart in five minutes broadcasts the text with a gradient prefix, the sender's name and a gold heading. Staff with a special permission may use MiniMessage tags in their announcement. Everyone else's text is inserted safely:
Where this file livesminimessage-demosrcmainjavacomexampleminimessagedemoAnnounceCommand.java
The package com.example.minimessagedemo is the folder path com/example/minimessagedemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-demo/
- src/main/
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- AnnounceCommand.javayou are hereCommand (BasicCommand)
- ConvertCommand.javaCommand (BasicCommand)
- Messages.javaHelper class
- MiniDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- ReloadCommand.javaCommand (BasicCommand)
- WelcomeListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- messages.ymlMessage texts that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.minimessagedemo;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;6import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;7import org.bukkit.command.CommandSender;8 9public final class AnnounceCommand implements BasicCommand {10 11 private static final String FORMAT_PERMISSION = "minimessagedemo.announce.format";12 13 private final Messages messages;14 15 public AnnounceCommand(Messages messages) {16 this.messages = messages;17 }18 19 @Override20 public void execute(CommandSourceStack source, String[] args) {21 CommandSender sender = source.getSender();22 if (args.length == 0) {23 messages.send(sender, "usage");24 return;25 }26 27 String text = String.join(" ", args);28 TagResolver message = sender.hasPermission(FORMAT_PERMISSION)29 ? Placeholder.parsed("message", text)30 : Placeholder.unparsed("message", text);31 32 messages.broadcast("announce", message, Placeholder.unparsed("sender", sender.getName()));33 }34 35 @Override36 public String permission() {37 return "minimessagedemo.announce";38 }39}- The simplest command type in Paper. Commands, part 1 explains it.
- Whoever typed the command: a player or the console.
- The player typed just
/announcewith nothing after it. Tell them how to use the command and stop. - Paper splits the typed text at every space. This glues the words back together.
- Asks whether this sender may use formatting.
- This is the conditional operator:
condition ? a : bmeans "if the condition is true use a, otherwise b". Staff getparsedand can write<rainbow>. Everyone else getsunparsed. - Looks up
announceinmessages.ymland fills in<message>and<sender>. The sender's name isunparsed, because names are not under your control. - Tells Paper which permission is needed to run the command at all. Players without it do not even see the command.
That preview shows an announcement from a staff member who typed the rainbow tag themselves. The plugin class registers three commands and a join listener, and the listener sends the welcome message from the file. They are all in the downloadable project. This is the part of plugin.yml that declares the three permissions (/announce, formatting in announcements, and /mmreload):
Where this file livesminimessage-demosrcmainresourcesplugin.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- minimessage-demo/
- src/main/
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- AnnounceCommand.javaCommand (BasicCommand)
- ConvertCommand.javaCommand (BasicCommand)
- Messages.javaHelper class
- MiniDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- ReloadCommand.javaCommand (BasicCommand)
- WelcomeListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- messages.ymlMessage texts that server owners can change
- plugin.ymlyou are hereTells Paper the plugin's name, version and main class
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1name: MiniMessageDemo2version: '1.0.0'3main: com.example.minimessagedemo.MiniDemoPlugin4api-version: '26.3'5description: A /announce command with a gradient prefix and safe placeholders, messages from messages.yml, and a legacy color converter.6permissions:7 minimessagedemo.announce:8 description: Lets a player use /announce.9 default: op10 minimessagedemo.announce.format:11 description: Lets /announce use MiniMessage tags in the text.12 default: op13 minimessagedemo.reload:14 description: Lets a player use /mmreload.15 default: op- Staff with this permission may use MiniMessage in announcements. The name begins with the plugin name so it can never clash with another plugin.
- Server operators have it automatically. Everyone else needs it granted by a permissions plugin.
- minimessage-demo/
- src/
- main/
- java/
- com/example/minimessagedemo/
- MiniDemoPlugin.javaRegisters the listener and the three commands
- Messages.javaLoads messages.yml and turns its text into components
- AnnounceCommand.java/announce, with safe or formatted text
- ReloadCommand.java/mmreload rereads messages.yml
- ConvertCommand.java/convertcolors turns old color codes into MiniMessage
- WelcomeListener.javaSends the welcome message from the file
- com/example/minimessagedemo/
- resources/
- plugin.ymlPlugin name, version, main class and permissions
- messages.ymlAll message texts the owner may change
- java/
- main/
- src/
Download the demo as a Gradle project, or open the code in the Compile Lab.
Converting old & color codes
Old plugins and old configs use codes such as &6[VIP] &fSteve. MiniMessage does not read them. You can convert a message in two ways:
- By hand or with a tool. Paste the old text into the MiniMessage Studio and rewrite it with tags. For short messages this takes a minute.
- With code. Read the old text with the legacy serializer from the previous page, which makes a component, then write that component back out as MiniMessage with
MiniMessage.miniMessage().serialize(component). The/convertcolorscommand in the demo does exactly this:
Where this file livesminimessage-demosrcmainjavacomexampleminimessagedemoConvertCommand.java
The package com.example.minimessagedemo is the folder path com/example/minimessagedemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-demo/
- src/main/
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- AnnounceCommand.javaCommand (BasicCommand)
- ConvertCommand.javayou are hereCommand (BasicCommand)
- Messages.javaHelper class
- MiniDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- ReloadCommand.javaCommand (BasicCommand)
- WelcomeListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- messages.ymlMessage texts that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessagedemo/Package com.example.minimessagedemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
26@Override27public void execute(CommandSourceStack source, String[] args) {28 if (args.length == 0) {29 messages.send(source.getSender(), "convert-usage");30 return;31 }32 33 Component parsed = LEGACY.deserialize(String.join(" ", args));34 String converted = MiniMessage.miniMessage().serialize(parsed);35 Component copyable = Component.text(converted, NamedTextColor.WHITE)36 .hoverEvent(HoverEvent.showText(Component.text("Copy to clipboard", NamedTextColor.GRAY)))37 .clickEvent(ClickEvent.copyToClipboard(converted));38 messages.send(source.getSender(), "converted", Placeholder.component("text", copyable));39}- Reads the old &-codes and builds a component.
LEGACYis a serializer set up for&codes and hex colors like&#ff8800. - The reverse of
deserialize: turns the component into MiniMessage text. - The converted text, shown as plain letters. A hover says what the click does, and a click copies it, so you can paste it straight into a config file.
- Inserts the finished, clickable component into the message in
messages.yml.
The conversions it makes:
| Old text | MiniMessage |
|---|---|
&6[VIP] &fSteve | <gold>[VIP] </gold><white>Steve |
&6Hello &b&lworld&r! | <gold>Hello </gold><bold><aqua>world</aqua></bold>! |
&#ff8800Orange &7gray | <#FF8800>Orange </#FF8800><gray>gray |
Mistakes to avoid
- Using
Placeholder.parsedfor player text. Players can then add click events and fake messages. Useunparsed. - A placeholder name that does not match.
<player>in the text needsPlaceholder.unparsed("player", ...). - Naming a placeholder like a tag.
<b>,<i>,<red>and<key>are already taken. - Unquoted YAML. A message like
welcome: <green>Hibreaks the file. Always wrap messages in quotes. - Putting a placeholder inside a tag argument with
unparsed. In<click:run_command:'/tp <who>'>, onlyparsedplaceholders are filled in. Use a parsed placeholder only for text you trust, such as a player name you checked yourself, or build the click in Java and insert it withPlaceholder.component. - Forgetting that a closing tag also closes everything opened after it.
<gold>a<bold>b</gold>cmakesbbold and gold, andcplain. - Using old
&codes in MiniMessage text. They are not read. Convert them.
A safe /rainbow command
Write a command /rainbow some text that shows the player's text back in bold rainbow colors. A player typing tags such as <click:run_command:/spawn> must not be able to make the text clickable.
Hint 1
The rainbow can be a tag around a placeholder: <rainbow><text></rainbow>.
Hint 2
The player typed the text, so which of Placeholder.parsed and Placeholder.unparsed is the safe one?
Show the solution
The message is written by you, and only the text inside the rainbow comes from the player. That text goes in with unparsed, so any tags it contains are only letters:
Where this file livesminimessage-exercisesrcmainjavacomexampleminimessageexerciseRainbowCommand.java
The package com.example.minimessageexercise is the folder path com/example/minimessageexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- minimessage-exercise/
- src/main/
- java/com/example/minimessageexercise/Package com.example.minimessageexercise
- MiniExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- RainbowCommand.javayou are hereCommand (BasicCommand)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/minimessageexercise/Package com.example.minimessageexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
9@Override10public void execute(CommandSourceStack source, String[] args) {11 if (args.length == 0) {12 source.getSender().sendRichMessage("<red>Usage: /rainbow some text");13 return;14 }15 16 String text = String.join(" ", args);17 source.getSender().sendRichMessage(18 "<bold><rainbow><text></rainbow></bold>",19 Placeholder.unparsed("text", text));20}- Glues the typed words back together.
- The bold and rainbow tags are yours.
<text>is the gap. - The safe way to insert text the player typed.
Convert it yourself
Rewrite this old message as MiniMessage, with a gold bold [Shop] and gray text after it: &6&l[Shop] &7Prices are in diamonds.
Hint
The old code &6 is gold, &l is bold and &7 is gray. &r would reset.
Show the solution
1<gold><bold>[Shop]</bold></gold> <gray>Prices are in diamonds.Check your answer with /convertcolors from the demo plugin, or in the MiniMessage Studio.
Recap
- MiniMessage writes components as text with tags:
<gold>switches on,</gold>switches off,<!bold>negates one style. - Send it with
sender.sendRichMessage(text, resolvers...), or get a component withMiniMessage.miniMessage().deserialize(text, resolvers...). - Colors, decorations,
reset,newline,gradient,rainbow,transition,click,hover,key,lang,fontandinsertcover almost everything. - Placeholders fill in gaps. Use
Placeholder.unparsedfor anything a player controls,parsedonly for your own text,componentfor ready-made components andFormatter.numberfor numbers. - Keep message texts in
messages.yml, quoted, with a shared prefix, so server owners can edit them without rebuilding. - Convert old
&codes once, with the MiniMessage Studio orLegacyComponentSerializerplusMiniMessage#serialize.
Quick quiz
A player types
<click:run_command:/spawn>Free items</click>in your chat feature. How do you stop that from becoming a clickable link?unparsedinserts the text as plain letters, so the tags are shown but do nothing.parsedwould read them as real tags, which is the problem.sendRichMessagereads tags too.Your message says
<gray>Hello <player>, but chat showsHello <player>. What is wrong?Unknown tags are shown as text. The placeholder name in the message and inPlaceholder.unparsed("player", ...)must be identical. A closing tag is optional at the end of a message.What does
<gold>a<bold>b</gold>cshow?The gold tag coversaandb. The closing</gold>closes gold, and also the bold that was opened inside it, socis plain.Why should a message in
messages.ymlbe wrapped in double quotes?Quotes are a YAML rule, not a MiniMessage rule. Without them, characters in the message may be read as YAML syntax and break the file.Old config text
&aHellois passed straight tosendRichMessage. What does the player see?MiniMessage does not read legacy codes and never throws for unknown text. Convert the text first with the legacy serializer, or rewrite it as<green>Hello.
Next steps
- Titles, action bars, boss bars and sounds: more places to show MiniMessage text.
- Configuration files: everything about YAML and the config API.
- MiniMessage Studio: try any tag live.
- Project: Chat formatter and filter: where unparsed placeholders protect a real chat.