Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Component API, 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:

  1. Plain text: Just simple text to be shown directly.
  2. Translated text: Text that is translated based on the player’s language setting, useful for supporting multiple languages.
  3. Scoreboard values: Displays player scores from a scoreboard.
  4. Entity names: Displays names of entities (like players or mobs) based on a selector (like @a or @p).
  5. Keybinds: Shows the name of a control button bound to a specific action in the player’s control scheme.
  6. 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 command
  • suggest_command - Suggest a command in the chat input
  • open_url- Open a URL in the browser
  • open_file - Open a file (client-side only)
  • change_page - Change page in books
  • copy_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 formatting
  • show_item - Show an item’s tooltip with properties like item ID, stack count, and additional components
  • show_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:

  1. Command generators: Programmatically create complex /tellraw, /title, or /book commands.
  2. Custom UIs: Make interactive books, signs, or other text-based interfaces.
  3. Data packs: Generate dynamic text components for custom advancements, loot tables, or other data pack elements.

  1. Within Minecraft they are stored as SNBT, but this library uses JSON. Since Component implements serde’s Serialize and Deserialize traits, 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 /tellraw and 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 own Component.
  • 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 building ComponentObjects.

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 these ComponentObject instances.

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 a from_string method that takes a string input and attempts to convert it into a Component.
  • ComponentSerializer: This trait defines a to_string method that takes a Component and 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: If true, parsing will fail if tags are not properly closed.
  • parse_legacy_colors: If true, it will parse legacy Minecraft color codes (e.g., &a for 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 ComponentParser trait defines how to convert a string representation of a text component into a Component enum. Its primary method is

    #![allow(unused)]
    fn main() {
    fn from_string(input: impl AsRef<str>) -> Result<Component, Self::Err>;
    }
  • The ComponentSerializer trait defines how to convert a Component enum 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:

  • &str
  • String
  • &String
  • Box<str>
  • Cow<'_, str> without restrictions.
  1. Define your parser/serializer struct: Create a new struct (like MyCustomParser).
  2. Implement ComponentParser:
    • Implement the from_string method, which will contain your logic to parse the input string and construct a Component object. This often involves tokenizing the input, managing a style stack, and building Components based on your format’s rules.
  3. Implement ComponentSerializer (optional):
    • Implement the to_string method, which will traverse a Component object and convert it into your desired string format.

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: