PHPackages                             cartograph/minecraft-text - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. cartograph/minecraft-text

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

cartograph/minecraft-text
=========================

Multi-format Minecraft text library: parse JSON / SNBT / legacy / MiniMessage components into a shared AST and render to JSON / SNBT / legacy / MiniMessage / HTML.

v1.2.0(3w ago)03MITPHPPHP ^8.5CI failing

Since May 14Pushed 3w agoCompare

[ Source](https://github.com/cartographgg/minecraft-text)[ Packagist](https://packagist.org/packages/cartograph/minecraft-text)[ Docs](https://github.com/cartographgg/minecraft-text)[ RSS](/packages/cartograph-minecraft-text/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (6)Versions (4)Used By (0)

 [![Cartograph Logo](cartograph-logo.png)](cartograph-logo.png)

Minecraft Text
==============

[](#minecraft-text)

A pure-PHP library for parsing and rendering Minecraft chat text in every format the live ecosystem uses: JSON text components, SNBT text components, legacy `§`/`&`-coded strings, MiniMessage, and HTML.

[![Packagist Version](https://camo.githubusercontent.com/23948e068b54133e16671d2698b162fb70e142228796a3e19c17869f3153dd02/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f636172746f67726170682f6d696e6563726166742d746578742e737667)](https://packagist.org/packages/cartograph/minecraft-text)[![Total Downloads](https://camo.githubusercontent.com/1bb02079d6c16c0bc09155f14505d06ae21eef21a8404fa87a13a1397419cb15/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f636172746f67726170682f6d696e6563726166742d746578742e737667)](https://packagist.org/packages/cartograph/minecraft-text)[![PHP Version](https://camo.githubusercontent.com/a71f512c45ece346e27f08dcc16cc58f6dfed2c177c0ce9cd36b8c311f794c3f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f636172746f67726170682f6d696e6563726166742d746578742e737667)](https://packagist.org/packages/cartograph/minecraft-text)[![License](https://camo.githubusercontent.com/2162af5cc58164dc7b5f2c7de1e9a55c910e959c851fe88427131db91348cc23/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f636172746f67726170682f6d696e6563726166742d746578742e737667)](LICENSE.md)

Features
--------

[](#features)

- **Five input formats**: JSON text components, SNBT text components, legacy `§`/`&`-coded strings (with the BungeeCord and Essentials hex extensions), and MiniMessage; readers are permissive by default with an opt-in strict mode
- **Five output formats**: JSON, SNBT, legacy, MiniMessage, and HTML; the HTML renderer is configurable for inline styles, CSS classes, or a hybrid, with a bundled reference stylesheet
- **Dialect-targeted rendering**: pick the Minecraft schema generation (1.20, 1.20.5, 1.21.4, 1.21.5, 26.1) and the JSON/SNBT renderers emit the canonical wire shape — correct event-key casing, allowed click and hover actions, `shadow_color` support, and `show_item` payload form
- **Round-trip integrity**: unknown click actions survive as `CustomClickEvent`; MiniMessage gradient/rainbow/transition tags round-trip verbatim through the MiniMessage renderer (other renderers lower them to plain text spans)
- **Diagnostics, not exceptions**: lossy and non-canonical inputs record a `Diagnostic` against the parse or render result rather than throwing, with stable dotted codes (`parse.minimessage.unknown-tag`, `render.legacy.translate-dropped`, `render.html.url-scheme-blocked`, ...) suitable for programmatic filtering
- **Six native component types** (Text, Translate, Keybind, Score, Selector, NBT) plus three MiniMessage-native transformations (Gradient, Rainbow, Transition)
- **Style inheritance** matching vanilla Minecraft semantics, with a tri-state Decorations record so children can suppress an inherited decoration (``)
- **Fluent builder** (`TextBuilder`) and a static facade (`Text`) for programmatic construction without manual AST assembly
- **Translation resolution** via a caller-supplied locale source, with a printf-style placeholder helper (`%s` / `%1$s` / `%%`)
- **HTML safety**: every text leaf is HTML-escaped, URL schemes in `OpenUrl` are validated against a configurable allowlist (default `http`/`https`/`mailto`), and obfuscated text is exposed via a `data-mc-text` attribute for client-side animation
- **PHPStan level max** with full generic annotations; **100% mutation score** under Infection (all surviving mutants documented as observationally equivalent)

Requirements
------------

[](#requirements)

- PHP 8.5 or newer
- `cartograph/minecraft-nbt` ^1.0 (resolved automatically by Composer)

No PHP extensions are required.

Installation
------------

[](#installation)

Install via Composer:

```
composer require cartograph/minecraft-text
```

Quick start
-----------

[](#quick-start)

Parse a legacy MOTD and render it as themed HTML:

```
use Cartograph\Text\Text;

$html = Text::renderHtml(Text::parseLegacy("§6Welcome §eto §athe server"));
// Welcome
// to
// the server
```

That's a complete, runnable example. The bundled reference stylesheet (`Html::referenceStylesheet()`) defines the default `mc-color-*` and `mc-bold` rules so the output drops straight into any page.

Build a tree programmatically and serialise it as JSON for Minecraft:

```
use Cartograph\Text\Component\NamedColor;
use Cartograph\Text\Text;

$component = Text::text(
    'Hello, ',
    children: [
        Text::text('world', style: Text::build()->color(NamedColor::Red)->build()->style()),
        Text::text('!'),
    ],
);

echo Text::renderJson($component);
// {"text":"Hello, ","extra":[{"text":"world","color":"red"},{"text":"!"}]}
```

Concepts
--------

[](#concepts)

Minecraft chat text is a tree of typed **components**. Each component carries some content (literal text, a translation key, a keybind, a score reference, a selector, or an NBT path) plus an optional **style** (colour, decorations, font, shadow colour, click/hover events, insertion text) and an "extra" list of child components that inherit the parent's effective style.

Vanilla Minecraft serialises this tree in three main ways:

- **JSON** text components, the wire format used by chat, signs, written books, advancement descriptions, and the SLP status response
- **SNBT** text components, the same shape encoded as stringified NBT for use in commands and data packs
- **Legacy `§`-codes**, the pre-1.7 wire format still emitted by plugins, written into pre-1.13 worlds, and used by SLP MOTDs

The community uses two more:

- **MiniMessage**, a tag-language popularised by the Adventure library (Paper, Velocity), with native syntax for gradients, rainbows, and click/hover events
- **HTML**, for displaying chat on websites

This library reads, writes, and converts between all five. A shared, immutable `Component` AST sits between the readers and the renderers, so any input format can be re-rendered to any output format.

Output that targets Minecraft's JSON/SNBT wire format is **dialect- driven**: a `Profile` for the target Minecraft generation decides which fields are valid, which click/hover actions are allowed, what casing event keys use (`clickEvent` vs `click_event`), and whether `shadow_color` and the legacy NBT-string `show_item` payload are supported. Fields the dialect cannot represent record a `Diagnostic`and drop, or throw `ProfileMismatchException` under strict mode.

Lossy conversions (rendering a `KeybindComponent` to legacy, an unresolved `TranslateComponent` to HTML, an unknown `` tag in MiniMessage) record dotted diagnostics on the result rather than silently failing.

Usage
-----

[](#usage)

### Parse JSON, SNBT, legacy, or MiniMessage

[](#parse-json-snbt-legacy-or-minimessage)

Every reader has two facade entry points: a one-shot version returning just the parsed `Component`, and a `*Result` version returning a `ParseResult` bundling the component with any diagnostics raised.

```
use Cartograph\Text\Text;

$c1 = Text::parseJson('{"text":"hi","color":"red"}');
$c2 = Text::parseSnbt('{text:"hi",color:"red"}');
$c3 = Text::parseLegacy('§chi');
$c4 = Text::parseMiniMessage('hi');

// Or with diagnostics:
$r = Text::parseJsonResult($input);
$component   = $r->component;
$diagnostics = $r->diagnostics; // list
```

Parsers are permissive by default — bare strings, list-as-component, both `clickEvent` and `click_event` casings, both legacy and modern `show_item` payloads, and unknown click actions all parse without throwing. Pass `new JsonOptions(strict: true)` (or the per-format equivalent) to reject non-canonical shapes.

### Render to JSON, SNBT, legacy, MiniMessage, or HTML

[](#render-to-json-snbt-legacy-minimessage-or-html)

```
use Cartograph\Text\Text;

echo Text::renderJson($c);
echo Text::renderSnbt($c);
echo Text::renderLegacy($c);
echo Text::renderMiniMessage($c);
echo Text::renderHtml($c);

// `*Result` variants return diagnostics alongside the text:
$r = Text::renderHtmlResult($c);
$html        = $r->text;
$diagnostics = $r->diagnostics;
```

The JSON and SNBT renderers take a `JsonOptions` / `SnbtOptions` with a `dialect` field — pick the Minecraft schema generation you're targeting:

```
use Cartograph\Text\Json\JsonOptions;
use Cartograph\Text\Profile\JsonDialect;
use Cartograph\Text\Text;

echo Text::renderJson($c, new JsonOptions(JsonDialect::Mc1_20));
// Pre-1.21.5 wire shape: clickEvent (camelCase), legacy show_item, no shadow_color

echo Text::renderJson($c, new JsonOptions(JsonDialect::Mc26_1));
// Latest wire shape: click_event (snake_case), components-form show_item, shadow_color
```

### Build trees programmatically

[](#build-trees-programmatically)

Two equivalent APIs:

The `Text` facade has a static factory for every component type:

```
use Cartograph\Text\Component\NamedColor;
use Cartograph\Text\Component\Style;
use Cartograph\Text\Component\Decorations;
use Cartograph\Text\Text;

$c = Text::text(
    'Hello',
    style: new Style(color: NamedColor::Gold, decorations: new Decorations(bold: true)),
);
```

`TextBuilder` is a fluent, immutable helper for chained construction:

```
use Cartograph\Text\Build\TextBuilder;
use Cartograph\Text\Component\NamedColor;
use Cartograph\Text\Component\Events\OpenUrl;
use Cartograph\Text\Text;

$c = Text::build()
    ->append(TextBuilder::text('Click here')
        ->color(NamedColor::Aqua)
        ->underlined()
        ->onClick(new OpenUrl('https://example.com')))
    ->append(TextBuilder::text(' to continue.'))
    ->build();
```

### Resolve translations

[](#resolve-translations)

`TranslationResolver` walks a tree replacing every `TranslateComponent`with its resolved text. The package ships no locale data — implement the `Translator` interface against your own locale source:

```
use Cartograph\Text\Translation\TranslationResolver;
use Cartograph\Text\Translation\Translator;

$translator = new class implements Translator {
    public function translate(string $key, ?string $locale = null): ?string
    {
        return match ($key) {
            'chat.type.text' => ' %s',
            default          => null,
        };
    }
};

$resolved = new TranslationResolver($translator)->resolve($component);
```

The `HtmlRenderer` accepts an optional `Translator` via `HtmlOptions` and runs the resolution pass automatically before walking the tree.

### Interpret legacy codes inside text fields

[](#interpret-legacy-codes-inside-text-fields)

JSON, SNBT, and MiniMessage parsers leave `§`/`&` codes inside a `text`field as raw characters — that matches vanilla Minecraft's own behaviour but doesn't help with the very common ecosystem case of a JSON MOTD or SNBT block carrying legacy-coded text:

```
{"text": "§6§lMythical§e§lMC\n§r§a§l Original Minigames 1.12.2+"}
```

`Text::interpretLegacyInText()` walks a tree and expands every legacy-coded `TextComponent` in place via `LegacyReader`. The wrapping node's own style and trailing children are preserved; the legacy-parsed styled runs are spliced in as the first children of a rewrapped `TextComponent` carrying the original style:

```
use Cartograph\Text\Text;

$tree = Text::interpretLegacyInText(Text::parseJson($motd));
$html = Text::renderHtml($tree);
```

The transform is opt-in (the parsers themselves never run it automatically) and lossy in one direction: once codes have been expanded into styled runs, the original raw text is gone, so a tree transformed this way will no longer re-serialise byte-identically back to its source wire format. Pass a `LegacyOptions` to enable `&` interpretation, Essentials `&#RRGGBB` hex, or BungeeCord `§x§R§R§G§G§B§B` hex (the last is on by default):

```
use Cartograph\Text\Legacy\LegacyOptions;
use Cartograph\Text\Legacy\Marker;
use Cartograph\Text\Text;

$tree = Text::interpretLegacyInText(
    Text::parseJson($motd),
    new LegacyOptions(parseMarkers: Marker::SECTION | Marker::AMPERSAND),
);
```

### Customise HTML rendering

[](#customise-html-rendering)

```
use Cartograph\Text\Html\EventStrategy;
use Cartograph\Text\Html\HtmlOptions;
use Cartograph\Text\Html\StaticPalette;
use Cartograph\Text\Html\StylingMode;
use Cartograph\Text\Text;

$opts = new HtmlOptions(
    stylingMode:       StylingMode::Inline,        // Inline / Classes / Hybrid (default)
    classPrefix:       'mc-',
    events:            EventStrategy::AnchorPlusData,
    palette:           StaticPalette::sign(),      // chat (default) or sign
    allowedUrlSchemes: ['https'],                  // default ['http','https','mailto']
    omitUnstyledSpans: true,                       // default true; set false for an always-emit-span DOM
);

echo Text::renderHtml($component, $opts);
```

By default, any component that would render as a `` carrying no class, inline style, click/hover data, insertion, or obfuscation payload is emitted as bare body text — so plain text renders as `hi`, not `hi`, and the empty-text root container that comes out of `LegacyReader` and `LegacyInterpreter` no longer prepends a stray ``. An `` envelope from an `OpenUrl` click is kept and wraps the body directly. Pass `omitUnstyledSpans: false` if your consumer needs a stable one-span-per-leaf DOM for CSS hooks or position-indexed JS.

The bundled reference stylesheet (`Html::referenceStylesheet()`) defines the default `mc-color-*`, `mc-bold`, `mc-italic`, etc. classes plus a CSS-only animation for `mc-obfuscated`.

Class overview
--------------

[](#class-overview)

The public surface splits into the AST (`Component` namespace), the five readers, the five renderers, the dialect/profile system, and a small set of supporting types.

### Entry point

[](#entry-point)

ClassMethod groupReturns`Text``parseJson`/`parseSnbt`/`parseLegacy`/`parseMiniMessage` (+`*Result`)`Component` (or `ParseResult`)`Text``renderJson`/`renderSnbt`/`renderLegacy`/`renderMiniMessage`/`renderHtml` (+`*Result`)`string` (or `RenderResult`)`Text``interpretLegacyInText``Component` (legacy codes in text fields expanded into styled runs)`Text``text`/`translate`/`keybind`/`score`/`selector`/`nbt`/`gradient`/`rainbow`/`transition`the matching `*Component``Text``color(string|int|NamedColor)``Color``Text``build()``TextBuilder` seeded with empty text### Component AST (`Cartograph\Text\Component`)

[](#component-ast-cartographtextcomponent)

ClassRole`Component`sealed interface implemented by every node`TextComponent`literal text (JSON `{"text": "..."}`)`TranslateComponent`locale key + `with` arguments (JSON `{"translate": ...}`)`KeybindComponent`keybind reference, e.g. `key.jump``ScoreComponent`scoreboard score reference`SelectorComponent`target selector, e.g. `@p`, with optional separator`NbtComponent`NBT path read from a block/entity/storage`GradientComponent`MiniMessage-native colour gradient`RainbowComponent`MiniMessage-native HSL hue cycle`TransitionComponent`MiniMessage-native single sampled colour`Style`immutable container for colour, decorations, font, events, etc.`Decorations`tri-state record of bold/italic/underlined/strikethrough/obfuscated`Color` / `NamedColor` / `HexColor`colour interface and its two implementations`NbtSource`enum: `Block`, `Entity`, `Storage``Events\ClickEvent`sealed: `OpenUrl`, `RunCommand`, `SuggestCommand`, `CopyToClipboard`, `ChangePage`, `CustomClickEvent``Events\HoverEvent`sealed: `ShowText`, `ShowItem`, `ShowEntity`### Readers and renderers

[](#readers-and-renderers)

FormatReaderRendererOptions classJSON`JsonReader``JsonRenderer``JsonOptions`SNBT`SnbtReader``SnbtRenderer``SnbtOptions`Legacy`LegacyReader``LegacyRenderer``LegacyOptions`MiniMessage`MiniMessageReader``MiniMessageRenderer``MiniMessageOptions`HTML—`HtmlRenderer``HtmlOptions`### Profiles and diagnostics

[](#profiles-and-diagnostics)

ClassRole`Profile\JsonDialect`enum: `Mc1_20`, `Mc1_20_5`, `Mc1_21_4`, `Mc1_21_5`, `Mc26_1``Profile\Profile`per-dialect field naming and action allowlists`Render\Diagnostic`severity + dotted code + message + optional path/offset`Render\Severity`enum: `Info`, `Warning`, `Error``Read\ParseResult`parsed component + diagnostics`Render\RenderResult`rendered text + diagnostics### Extension points

[](#extension-points)

Class / interfacePurpose`MiniMessage\TagHandler`Register custom MiniMessage tags via `TagRegistry::register``Html\Palette` / `Html\StaticPalette`Map `NamedColor` to RGB; ship `chat()` and `sign()` palettes`Translation\Translator`Caller-supplied locale lookupCompatibility
-------------

[](#compatibility)

- **Minecraft Java versions**: parsers accept every JSON/SNBT shape emitted between 1.20 and 26.1 (Minecraft Java's current major). Renderers target the same range via `JsonDialect`; pick the dialect matching the server/client you're talking to.
- **Legacy codes**: classic `§` plus `&`, with BungeeCord `§x§R§R§G§G§B§B` hex (opt-in default-on) and Essentials `&#RRGGBB`hex (opt-in)
- **MiniMessage**: the vanilla tag vocabulary — colours and aliases, decorations with `` form, `click`/`hover`/`insertion`/`font`, content tags (``, ``, ``, ``, ``), transformations (``, ``, ``), plus `` and ``. Custom tags pluggable via `TagHandler`.
- **HTML**: emits semantic markup with configurable styling mode, scheme-allowlisted click handlers, and a `data-mc-*` attribute convention for consumer-side JavaScript

Bedrock Edition's text format variants are not supported.

Contributing
------------

[](#contributing)

Bug reports, feature requests, and pull requests are welcome at [github.com/cartographgg/minecraft-text](https://github.com/cartographgg/minecraft-text). See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup and the required checks (tests, static analysis, code style, and mutation testing). Each check has a Composer script: `composer test`, `composer static`, `composer style`, and `composer mutation`.

License
-------

[](#license)

Released under the [MIT License](LICENSE.md). © Cartograph contributors.

---

Maintained as part of [Cartograph](https://cartograph.gg), a Minecraft server directory and monitoring platform. The library is self-contained and has no Cartograph-specific behaviour; use it anywhere you need to work with Minecraft chat text in PHP.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance96

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

21d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/469515?v=4)[Ollie Read](/maintainers/ollieread)[@ollieread](https://github.com/ollieread)

---

Top Contributors

[![ollieread](https://avatars.githubusercontent.com/u/469515?v=4)](https://github.com/ollieread "ollieread (8 commits)")

---

Tags

adventure-apicartographchatchat-formattinghtmljsonlegacy-color-codesminecraftminecraft-serverminimessagemotdphpphp8snbttext-componenthtmltextchatminecraftmotdtext-componentminimessage

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/cartograph-minecraft-text/health.svg)

```
[![Health](https://phpackages.com/badges/cartograph-minecraft-text/health.svg)](https://phpackages.com/packages/cartograph-minecraft-text)
```

###  Alternatives

[froala/wysiwyg-editor

A beautiful jQuery WYSIWYG HTML rich text editor. High performance and modern design make it easy to use for developers and loved by users.

5.4k315.3k5](/packages/froala-wysiwyg-editor)[ckeditor/ckeditor

JavaScript WYSIWYG web text editor.

5244.3M79](/packages/ckeditor-ckeditor)[tinymce/tinymce

Web based JavaScript HTML WYSIWYG editor control.

1697.9M118](/packages/tinymce-tinymce)[unisharp/laravel-ckeditor

JavaScript WYSIWYG web text editor (for laravel).

375765.1k5](/packages/unisharp-laravel-ckeditor)[caxy/php-htmldiff

A library for comparing two HTML files/snippets and highlighting the differences using simple HTML.

21421.8M20](/packages/caxy-php-htmldiff)[froala/yii2-froala-editor

A beautiful WYSIWYG HTML text editor based on HTML5 technology. Cross browser, with mobile support, high performance and Retina Ready modern design.

111235.9k6](/packages/froala-yii2-froala-editor)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
