Introduction to kyori-component-json
kyori-component-json is an independent, powerful and fluent Rust library designed for working with Minecraft’s JSON text components (Java Edition 1.21.5+).
This system is used and fundamental for displaying and interacting with text in Minecraft. It is used in commands like /tellraw, and in other parts, like books and signs.
Why is this library named the way it is?
We’ll start breaking down the name (kyori-component-json) in three parts:
- “kyori”: The name tries to replicate Kyori’s Adventure
ComponentAPI, while making it idiomatic in Rust; - “component”: refers to Minecraft’s text component format;
- “json”: The components are parsed (or received via JSON from the JSON text serializer), and deserialized with serde.
Now that the library has switched to PaperMC, I’m keeping the same name to avoid causing any disruptions for those who depend on this crate.
What are components?
In order to understand how to use this library, you should be familiar with Minecraft’s text components. These components enable you to send and display rich, interactive text to players.
This format is used in various parts of the game like written books, signs, command messages (e.g., /tellraw and /title), and other UI elements.
Minecraft still uses JSON text components in places like Adventure, plugins, and commands such as /tellraw. These systems expect plain JSON and work the same as before. However, starting in Java Edition 1.21.5, Minecraft now stores text components inside NBT data using SNBT instead of JSON.
This means JSON is still correct for commands and network usage, but components embedded in NBT, such as books, signs, or block/entity data, must be converted to SNBT (in our case, the server does this for us).
Structure
Text components (also known as raw JSON text) are made up of different types of content and can include formatting, interactivity, and child components that inherit properties from their parent.
For example, if a text component has a certain color or style (like bold or italic), its children will share those properties unless specifically overridden.
The format of these text components uses structured JSON objects1 that can include a wide range of properties like color, font, boldness, italics, and even hover or click events. They are highly customizable, allowing for complex interactions and styling.
For instance, you could make a piece of text:
- bold
- red
- clickable
all by using a combination of tags and nested components.
Types
There are several types of content that text components can display:
- Plain text: Just simple text to be shown directly.
- Translated text: Text that is translated based on the player’s language setting, useful for supporting multiple languages.
- Scoreboard values: Displays player scores from a scoreboard.
- Entity names: Displays names of entities (like players or mobs) based on a selector (like @a or @p).
- Keybinds: Shows the name of a control button bound to a specific action in the player’s control scheme.
- NBT values: Displays data from an entity or block, such as health or custom attributes.
Hover events and click events
Text components can also be interactive. In click events, for example, when a player clicks on certain text, it can trigger various actions:
run_command- Execute a commandsuggest_command- Suggest a command in the chat inputopen_url- Open a URL in the browseropen_file- Open a file (client-side only)change_page- Change page in bookscopy_to_clipboard- Copy text to the clipboard
Additionally, in hover events, when hovering over a text component, a tooltip can appear showing additional information:
show_text- Display another text component with formattingshow_item- Show an item’s tooltip with properties like item ID, stack count, and additional componentsshow_entity- Display entity information including name, entity type ID, and UUID (in either string or integer array format)
These interactive features allow for rich, dynamic text components that can respond to player interactions with various visual feedback and functional actions.
What can you do with this library?
Now that we understand what a text component is, we have a better understanding of the library’s purpose.
This library simplifies the creation, manipulation, and de/serialization of these complex JSON structures, allowing developers to:
- Create rich text by generating colorful chat messages with extensive formatting options;
- Add interactivity by implementing clickable text that executes commands or opens URLs when a player interacts with it;
- Provide context when a player hovers over text, displaying additional information or other text components;
- Interoperate with Minecraft clients/servers, making this library necessary for text components.
Key scenarios
kyori-component-json is particularly useful in scenarios such as:
- Command generators: Programmatically create complex
/tellraw,/title, or/bookcommands. - Custom UIs: Make interactive books, signs, or other text-based interfaces.
- Data packs: Generate dynamic text components for custom advancements, loot tables, or other data pack elements.
-
Within Minecraft they are stored as SNBT, but this library uses JSON. Since
Componentimplements serde’sSerializeandDeserializetraits, you can do more than just send components to a server: you can build them, turn them into a string, or parse them back into Components for any purpose. This book will use “JSON” to specifically refer to the JSON representation you use in/tellrawand with Adventure’s JSON text de/serializer. ↩
Creating components
Now that we know what problem this library solves, and that we understand what a Minecraft text component is, we’re going to create some components!
Basic colored text
Let’s start with something simple. First, we’ll display “Hello Minecraft!” in red and bold.
use kyori_component_json::{Component, Color, NamedColor, TextDecoration};
use serde_json; // Needed for getting the Component as JSON
fn main() {
let message = Component::text("Hello Minecraft!")
.color(Some(Color::Named(NamedColor::Red)))
.decoration(TextDecoration::Bold, Some(true));
// To use this in Minecraft, you'd serialize it to JSON:
let json_output = serde_json::to_string(&message).unwrap();
println!("{}", json_output);
// Expected output: {"text":"Hello Minecraft!","color":"red","bold":true}
}
The output JSON can then be used in a /tellraw command:
/tellraw @a {"text":"Hello Minecraft!","color":"red","bold":true}
This basic example demonstrates how easily you can create a Component and apply styling. The library handles the conversion to the specific JSON format that Minecraft understands.
The component! macro
Since the component builder can be verbose, the library provides the component! macro to simplify creation with a more declarative and readable syntax.
Here’s a simple “Hello World” with no formatting:
use kyori_component_json::component;
fn main() {
let component = component!(text: "Hello World");
}
Formatting
You can easily add colors and decorations. Colors can be specified by name (like red) or as a hex code (like #ff5555).
use kyori_component_json::{component, Component, Color, NamedColor, TextDecoration};
fn main() {
let component = component!(text: "This is important!", {
color: red,
decoration: bold & true,
});
// This is equivalent to the builder pattern:
let equivalent = Component::text("This is important!")
.color(Some(Color::Named(NamedColor::Red)))
.decoration(TextDecoration::Bold, Some(true));
assert_eq!(component, equivalent);
}
Nesting Components
To create more complex messages, you can nest component! macro calls. This is useful for applying different formatting to different parts of your message.
use kyori_component_json::{component, Component, Color, NamedColor};
fn main() {
let component = component!(text: "You can mix ", {
color: gray,
append: (component!(text: "styles", {
color: yellow,
append: (component!(text: "!", {
color: gray,
}))
}))
});
}
Adding Interactivity with Events
The macro also makes it easy to add click_event and hover_event to make your components interactive. This provides a first look at creating interactive components; the next chapter will explore all the available event types in detail.
Let’s create a message that runs a command when clicked and displays helpful text when hovered over.
use kyori_component_json::{component, ClickEvent, HoverEvent, Color, NamedColor};
fn main() {
let interactive_message = component!(
text: "Click me to teleport!", {
color: aqua,
click_event: run_command { command: "/tp @s 100 64 100".to_string() },
hover_event: show_text {
component!(
text: "Teleports you to spawn coordinates (100, 64, 100)", {
color: gray
}
)
}
}
);
}
Why not just make the builder more fluent?
As it stands, the Component enum aims to closely be a Rust representation of a raw JSON text component, with a few functions to simplify creating and handling one at a low level. As such, it’s not designed to be any more fluent than it is now.
The component!() macro is a more user-friendly way to create components, and it is recommended to use it in most cases.
Getting Started
This section provides simple examples to get you started with kyori-component-json.
Basic text components
The most fundamental way to create a component is with plain text.
use kyori_component_json::Component;
use serde_json;
fn main() {
let component = Component::text("Hello, World!");
let json_output = serde_json::to_string_pretty(&component).unwrap();
println!("{json_output}");
// Output:
// {
// "text": "Hello, World!"
// }
}
Colors and decorations
You can easily apply colors and text decorations (like bold, italic, underlined, etc.) to your components.
use kyori_component_json::{Component, Color, NamedColor, TextDecoration};
use serde_json;
fn main() {
let colorful_component = Component::text("Something colorful")
.color(Some(Color::Named(NamedColor::Red)))
.decoration(TextDecoration::Bold, Some(true))
.decoration(TextDecoration::Italic, Some(true));
let json_output = serde_json::to_string_pretty(&colorful_component).unwrap();
println!("{json_output}");
// Output:
// {
// "text": "Something colorful",
// "color": "red",
// "bold": true,
// "italic": true
// }
}
The component!() macro
Since the component builder can be verbose for complex components, there is a component!() macro to simplify it in a declarative way. Here’s a short example to get you started:
#![allow(unused)]
fn main() {
let component = component!(text: "Hello World");
}
This creates a simple hello world component with no formatting. You can find more in the Rust docs.
Click and Hover Events
In the previous chapter, we saw a basic example of how to make a component interactive. This chapter will explore the full range of actions available for both click_event and hover_event, allowing you to create rich, interactive experiences for players.
Click Events
A click_event performs an action when a player clicks on a text component. The action tag determines what happens, and it’s always paired with a value.
Here are the available actions:
open_url: Opens a URL in the user’s default web browser.run_command: Executes a command on the server as the player. For security reasons, this is limited to commands that a player has permission to run.suggest_command: Prefills the player’s chat input with a command, which they can then edit and send.change_page: Used in books to turn to a specific page.copy_to_clipboard: Copies a string to the player’s clipboard.
Here’s an example of a text component that runs a command when clicked:
use kyori_component_json::{component, ClickEvent};
fn main() {
let clickable_text = component!(
text: "Click to say hello!",
click_event: run_command { command: "/say Hello there!".to_string() }
);
}
Hover Events
A hover_event displays a tooltip when a player hovers their mouse over a text component.
Here are the available hover actions:
show_text: Displays another text component as the tooltip. This can be a simple string or a fully-featured component with its own formatting and events.show_item: Shows the tooltip of an item, as if hovering over it in the inventory. This requires the item’s ID, and can optionally include its count and NBT data.show_entity: Displays information about an entity, including its name, type, and UUID.
Here’s an example of a text component that displays a tooltip when hovered over:
use kyori_component_json::{component, HoverEvent};
fn main() {
let hoverable_text = component!(
text: "Hover over me",
hover_event: show_text { component!(text: "You found me!") }
);
}
Advanced Components
Once you’ve mastered the basics of creating simple text components, you can move on to the more advanced features of this library. This section will guide you through the powerful tools available for creating complex, interactive, and dynamic components.
We’ll cover three main areas:
-
Component API: You’ll learn how to build components programmatically. While simple components can be created easily, the full API offers fine-grained control over every aspect of a component, from styling to events.
-
Dynamic components: You’ll learn how to create components on the fly. This is useful for features like chat systems, where content is generated based on user input or other runtime data.
-
Serialization: Converting components to and from different string-based formats. This is fundamental for storing components or sending them over a network. We’ll look at the MiniMessage format in specific, a popular and powerful way to represent components.
Component API
Understanding the Component enum
At its core, kyori-component-json models Minecraft’s text component format using the Component enum. This enum represents the three primary ways Minecraft handles text:
Component::String(String): is a simple string, often used as a shorthand for basic text. When serialized, it becomes{"text": "your string"}.Component::Array(Vec<Component>): is a list of components. This is how Minecraft handles messages composed of multiple styled parts, where each part is its ownComponent.Component::Object(Box<ComponentObject>): is the most extensive form, representing a single text component with all its possible properties. Most of your rich text creation will involve buildingComponentObjects.
The ComponentObject struct:
- holds all the styling and interactive properties a text component can have, such as
color, and so on; - contains builder methods like
.color()or.decoration()that internally construct and modify theseComponentObjectinstances.
Combining multiple components
Complex messages can also be built by appending multiple Components together.
In Component, the append() method allows you to chain components, and append_newline() or append_space() add common separators.
When chaining components using append(), append_newline(), or append_space(), styling is inherited from the parent component. However, any explicit styling (e.g., color, decoration) applied to a child component will override the inherited style for that specific child and its subsequent children, unless they, in turn, explicitly override it. Think of it as a cascading style sheet for your Minecraft text.
We’ll use the low-level Component API to make this component.
use kyori_component_json::{Component, ClickEvent, Color, NamedColor, TextDecoration};
use serde_json;
fn main() {
let welcome_message = Component::text("Welcome, ")
.color(Some(Color::Named(NamedColor::White))) // Base color for the first part
.append(
Component::text("PlayerName")
.color(Some(Color::Named(NamedColor::Gold))) // Player name in gold
.decoration(TextDecoration::Bold, Some(true)) // Player name is bold
)
.append(Component::text(" to the server!")) // Appends more text
.append_newline() // Adds a newline character as a separate component
.append(
Component::text("Don\'t forget to read the ")
.color(Some(Color::Named(NamedColor::Gray))) // Gray text
)
.append(
Component::text("rules")
.color(Some(Color::Named(NamedColor::LightPurple))) // Rules in light purple
.decoration(TextDecoration::Underlined, Some(true)) // Rules are underlined
.click_event(Some(ClickEvent::OpenUrl {
url: "https://example.com/rules".to_string(), // Click to open URL
}))
)
.append(Component::text(".")); // Final punctuation
let json_output = serde_json::to_string_pretty(&welcome_message).unwrap();
println!("{}", json_output);
}
Dynamic Components
Displaying dynamic data
Minecraft’s text components can also display dynamic in-game information, which updates automatically. kyori-component-json provides specific component types for this.
Scoreboard values
The Component::score() constructor allows you to embed a player’s score from a specific objective directly into a message. The game client will then resolve and display the current score.
use kyori_component_json::{Component, ScoreContent, Color, NamedColor};
use serde_json;
fn main() {
let score_display = Component::text("Your current score: ")
.color(Some(Color::Named(NamedColor::Green)))
.append(
Component::score(ScoreContent {
name: "@p".to_string(), // Target selector (e.g., @p for nearest player)
objective: "my_objective".to_string(), // The name of the scoreboard objective
})
.color(Some(Color::Named(NamedColor::Yellow))) // Style the score value
)
.append(Component::text(" points."));
let json_output = serde_json::to_string_pretty(&score_display).unwrap();
println!("{}", json_output);
}
This JSON can be used directly in Minecraft:
/tellraw @a {"text":"Your current score: ","color":"green","extra":[{"score":{...},"color":"yellow"}, {"text":" points."}]}
Keybinds
To show players controls, you can use Component::keybind() to display the current key assigned to a specific action. The Minecraft client will automatically show the player’s configured key.
use kyori_component_json::{Component, Color, NamedColor, TextDecoration};
use serde_json;
fn main() {
let keybind_message = Component::text("Press ")
.color(Some(Color::Named(NamedColor::Gray)))
.append(
Component::keybind("key.attack") // Minecraft's internal keybind ID (e.g., "key.attack", etc.)
.color(Some(Color::Named(NamedColor::Red)))
.decoration(TextDecoration::Bold, Some(true))
)
.append(Component::text(" to attack."));
let json_output = serde_json::to_string_pretty(&keybind_message).unwrap();
println!("{}", json_output);
}
This JSON can be used directly in Minecraft:
/tellraw @a {"text":"Press ","color":"gray","extra":[{"keybind":"key.attack","color":"red","bold":true},{"text":" to attack."}]}
Serialization
Working directly with JSON: serialization and deserialization
One of the core purpose of kyori-component-json is to simplify the creation of Component structures that can be easily converted to and from JSON. This conversion is done seamlessly by serde_json, but you could convert a Component to any other format (like CBOR or YAML).
use kyori_component_json::{Component, Color, NamedColor};
use serde_json;
fn main() {
let component = Component::text("Hello, JSON!")
.color(Some(Color::Named(NamedColor::Green)));
// Serialize to a compact JSON string
let compact_json = serde_json::to_string(&component).unwrap();
println!("Compact JSON: {}", compact_json);
// Output: Compact JSON: {"text":"Hello, JSON!","color":"green"}
// Serialize to a pretty-printed JSON string for readability
let pretty_json = serde_json::to_string_pretty(&component).unwrap();
println!("Pretty JSON:\n{}", pretty_json);
// Deserialize from a JSON string back into a Component
let deserialized_component: Component = serde_json::from_str(&compact_json).unwrap();
assert_eq!(component, deserialized_component);
println!("Deserialized component matches original: {}", component == deserialized_component);
// You can also deserialize more complex JSON structures
let complex_json = r#"#
{
"text": "Part 1 ",
"color": "blue",
"extra": [
{"text": "Part 2", "bold": true},
{"text": " Part 3", "italic": true}
]
}
"#;
let complex_component: Component = serde_json::from_str(complex_json).unwrap();
println!("\nDeserialized complex component: {:?}", complex_component);
}
What is MiniMessage?
MiniMessage is a lightweight, human-friendly markup format designed for rich text. Instead of writing verbose JSON, you can use simple tags to apply colors, formatting, and even interactive events. For example, <red>Hello, <b>World!</b></red> is much easier to write and read than its JSON equivalent.
MiniMessage is particularly useful when:
- You’re getting user input, allowing players or server administrators to easily format messages without needing to understand complex JSON.
- Writing configuration files, MiniMessage will simplify storing rich text in a more readable format.
- Prototyping by typing simple messages without boilerplate Rust code or complex JSON.
You can find more about MiniMessage here: https://docs.papermc.io/adventure/minimessage/format/. This library includes a MiniiMessage parser.
Custom parsers and MiniMessage
While kyori-component-json does programmatic construction of Minecraft components decently well, you might be in situations where you need to parse text from other formats or serialize components into a more human-readable markup.
In this case, custom parsers and serializers, become a godsend. The MiniMessage format is built into this library with the minimessage feature.
The ComponentParser and ComponentSerializer traits
The kyori-component-json library provides two key traits in its parsing module that define the interface for converting between string representations and Component objects:
ComponentParser: This trait defines afrom_stringmethod that takes a string input and attempts to convert it into aComponent.ComponentSerializer: This trait defines ato_stringmethod that takes aComponentand converts it into a string representation.
These traits allow for a flexible and extensible system where you can implement support for any text format you desire.
Using the built-in MiniMessage parser
We skipped using MiniMessage to build components. The reason is that MiniMessage is a markup format that uses strings. This means errors won’t be caught at runtime or compile time. MiniMessage is set to non-strict by default and skips incorrect tags. However, these errors would be caught at runtime if the parser were set to strict mode.
The kyori-component-json library includes experimental support for MiniMessage parsing and serialization via the minimessage feature.
Enabling the minimessage Feature
To use the MiniMessage functionality, you must enable the minimessage feature in your Cargo.toml:
cargo add kyori-component-json --features minimessage
Parsing MiniMessage to Component
As MiniMessage is a markup language, we’ll see it’s even shorter than using component!() or the Component builder API:
use kyori_component_json::{minimessage::MiniMessage, Component, Color, NamedColor};
use serde_json;
fn main() {
let minimessage_string = "<green>Hello, <blue>MiniMessage</blue>!</green>";
// Create a MiniMessage parser instance (default configuration)
let mm_parser = MiniMessage::new();
// Parse the MiniMessage string into a Component
let component = mm_parser.parse(minimessage_string).unwrap();
// You can then serialize this Component to Minecraft JSON
let json_output = serde_json::to_string_pretty(&component).unwrap();
println!("Parsed Component as JSON:\n{}", json_output);
// Or inspect the component directly
if let Component::Object(obj) = component {
assert_eq!(obj.color, Some(Color::Named(NamedColor::Green)));
if let Some(extra) = obj.extra {
if let Component::Object(child_obj) = &extra[0] {
assert_eq!(child_obj.text, Some("MiniMessage".to_string()));
assert_eq!(child_obj.color, Some(Color::Named(NamedColor::Blue)));
}
}
}
}
Serializing Component to MiniMessage
You can also convert a component into a MiniMessage string. This is useful when displaying the entire component tree is difficult or impractical. Common examples include user output, logging, and configurations.
use kyori_component_json::{minimessage::MiniMessage, Component, Color, NamedColor, TextDecoration};
fn main() {
let component = Component::text("This is ")
.color(Some(Color::Named(NamedColor::Red)))
.append(
Component::text("bold and italic")
.decoration(TextDecoration::Bold, Some(true))
.decoration(TextDecoration::Italic, Some(true))
)
.append(Component::text(" text."));
// Serialize the Component back to a MiniMessage string
let minimessage_output = MiniMessage::to_string(&component).unwrap();
println!("Component as MiniMessage: {}", minimessage_output);
// Expected output: <red>This is <bold><italic>bold and italic</italic></bold> text.</red>
}
Customizing MiniMessage parsing
The MiniMessageConfig struct allows you to customize the parsing behavior:
strict: Iftrue, parsing will fail if tags are not properly closed.parse_legacy_colors: Iftrue, it will parse legacy Minecraft color codes (e.g.,&afor green).
use kyori_component_json::minimessage::{MiniMessage, MiniMessageConfig};
use kyori_component_json::Component;
fn main() {
let config = MiniMessageConfig {
strict: false, // Allow unclosed tags
parse_legacy_colors: true, // Parse & codes
};
let mm_custom_parser = MiniMessage::with_config(config);
let legacy_message = "&aHello &bWorld!";
let component = mm_custom_parser.parse(legacy_message).unwrap();
println!("Parsed legacy colors: {:?}", component);
let non_strict_message = "<red>Unclosed tag";
let component_non_strict = mm_custom_parser.parse(non_strict_message).unwrap();
println!("Parsed non-strict: {:?}", component_non_strict);
}
Custom Parsers
Implementing your own custom parser
The ComponentParser and ComponentSerializer traits are designed for extensibility. In case you need to support a different markup format (e.g., a custom Markdown-like syntax, or integration with another rich text system), these traits come in handy.
At a high level:
-
The
ComponentParsertrait defines how to convert a string representation of a text component into aComponentenum. Its primary method is#![allow(unused)] fn main() { fn from_string(input: impl AsRef<str>) -> Result<Component, Self::Err>; } -
The
ComponentSerializertrait defines how to convert aComponentenum back into a string representation. Its primary method is:#![allow(unused)] fn main() { fn to_string(component: &Component) -> Result<String, Self::Err>; }
The input type in from_string is deliberately generic to allow you to provide multiple types, such as:
&strString&StringBox<str>Cow<'_, str>without restrictions.
- Define your parser/serializer struct: Create a new struct (like
MyCustomParser). - Implement
ComponentParser:- Implement the
from_stringmethod, which will contain your logic to parse the input string and construct aComponentobject. This often involves tokenizing the input, managing a style stack, and buildingComponents based on your format’s rules.
- Implement the
- Implement
ComponentSerializer(optional):- Implement the
to_stringmethod, which will traverse aComponentobject and convert it into your desired string format.
- Implement the
Both traits have an Err associated type for your serialization errors. You have to define one, but it can be any type, including () as a placeholder.
The minimessage.rs source file is an excellent reference for how to implement these traits. By following this pattern, you can integrate kyori-component-json with virtually any rich text input or output format.
You can find more about these traits on docs.rs: