Skip to content

Building Advancement Bundles

KabanFriends edited this page Aug 27, 2026 · 3 revisions

The advancement viewer never reads datapack JSON. You compile a datapack into a bundle first, with mcw-adv-preprocess, and the widget loads the bundle. This page shows how.

The preprocessor parses and validates your advancement JSON the way the game does, builds the tree, runs the same layout algorithm the game runs, downloads an icon for every item you referenced, and writes the result. Layout happens here rather than in the browser, so the widget only has to draw.

Get the tool

git clone https://github.com/KabanFriends/mcwidgets.git
cd mcwidgets
npm ci
npm run build

You need Node 20.19 or later, or Node 22.12 or later. The build produces the preprocessor at packages/advancements/bin/mcw-adv-preprocess.mjs.

Building icons needs network access. The preprocessor downloads item renders and vanilla textures from public CDNs, pinned to one Minecraft version so two runs of the same input produce the same output.

Build a bundle

Point --input at your datapack and --output at an empty directory.

node packages/advancements/bin/mcw-adv-preprocess.mjs \
  --input ~/server/world/datapacks/my_pack \
  --output ./public/advancements \
  --bake-lang ~/server/lang/en_us.json

It reports what it built on stderr:

16 advancements -> 1 tabs / 16 nodes
wrote ./public/advancements/bundle.json

What --input accepts

Two layouts, detected automatically.

A datapack. Any directory containing data/. Advancements are read from data/<namespace>/advancement/**/*.json, or advancements/ for older packs. The advancement id comes from the namespace and the path, so data/mypack/advancement/mining/deep.json becomes mypack:mining/deep.

A flat folder. Any other directory. Every .json file under it, at any depth, becomes minecraft:<relative path without .json>.

What comes out

public/advancements/
  bundle.json
  icons/minecraft/grass_block.png
  icons/minecraft/wooden_pickaxe.png
  ...
  backgrounds/            (only for custom tab backgrounds)

bundle.json references the icons by relative path, and the widget resolves them against the bundle's own URL. Move or rename the directory freely. Split it up and the icons stop loading.

Serve the whole directory. If the page that runs the widget is on another origin, bundle.json needs Access-Control-Allow-Origin, and the icons beside it do not. See Embedding with an iframe for which origin actually matters.

Options

Option Effect
--input <dir> Datapack root or folder of advancement JSON. Required.
--output <dir> Output directory. Default ./dist.
--exclude <name> Skip JSON under any folder with this name. Repeatable.
--bake-lang <file.json> Resolve translate components at build time. See Text and fonts.
--tab-order <list> Comma-separated tab ids or path prefixes, in display order.
--icon <id>=<file or url> Replace one item's icon. Repeatable.
--icon-map <file.json> The same overrides as a JSON object of id to file or URL.
--icon-source <ref> Git ref of the item render source. Default master.
--source <string> A provenance string recorded in the bundle's metadata.
--single-file Inline every icon and background as a data: URI.
--quiet Suppress warnings.
--json-errors Print errors and warnings as JSON.
--version Print the preprocessor version.
--help Print the option list.

--exclude

Vanilla ships one recipe-toast advancement per recipe, over a thousand of them, anchored under a root with no display. They produce no tab and nothing worth showing.

--exclude recipes

The name matches any path segment below --input, so this works for both datapack layouts.

--tab-order

Tabs are ordered by advancement id by default, because JSON files carry no order of their own. The game orders them by datapack registration order instead, so vanilla comes out alphabetical rather than in the familiar sequence.

Name the order you want. A key matches a root's full id, its path, or a path prefix.

--tab-order story,adventure,husbandry,nether,end

Roots you do not name keep their id order, after the named ones. A key matching nothing is reported as a warning.

--icon and --icon-map

By default every display.icon is fetched as the 3D item render the game draws in inventories. Custom items your datapack invents have no such render, so override them with a local PNG or a URL.

--icon mypack:soul_shard=./textures/soul_shard.png
--icon minecraft:diamond=https://example.com/my_diamond.png

An id with no namespace is read as minecraft:. For more than a handful, put them in a file:

{
  "mypack:soul_shard": "./textures/soul_shard.png",
  "mypack:frost_blade": "./textures/frost_blade.png"
}
--icon-map ./icons.json

An icon that cannot be resolved becomes the purple and black missing-texture square, and the run prints a warning and continues. A missing icon is not worth failing a build over, but it is worth noticing.

--single-file

Inlines every icon and background into bundle.json as a data: URI, so the bundle is one file with no directory around it. Convenient to drop on a host that only takes single files, and much larger over the wire, since base64 costs a third and the icons no longer cache separately.

Rules the preprocessor enforces

It rejects the build, with the offending file named, when:

  • Two advancements share an id.
  • An advancement names a parent that does not exist.
  • Parent links form a cycle.
  • criteria is missing or empty, or a criterion has no trigger.
  • requirements does not cover exactly the criteria that exist.
  • display is missing icon, title, or description.
  • display.frame is not task, goal, or challenge.
  • display contains an unknown field. This catches typos that would otherwise be silently ignored.
  • Every root advancement lacks a display, leaving no tab to show.

Server-only fields such as rewards and sends_telemetry_event are ignored and are not published in the bundle.

Visibility rules

An advancement with no display is invisible, and its children attach to the nearest visible ancestor instead. This is what the game does, and it is how a hidden organisational node disappears without orphaning the branch below it.

A root advancement with no display produces no tab. Its children still attach. The run prints a warning naming the root.

An advancement with "hidden": true is left out of the rendered tree until it is marked complete, matching the game, where the server never sends an unearned hidden advancement. Turn on showHidden to draw them anyway, which is usually what a guide wants. See Advancement viewer reference.

How many tabs fit

Tabs fill the top edge, then the bottom, then the left, then the right. How many fit depends on the size of the widget, not the size of the bundle. A tab with no room left is not drawn and cannot be reached.

At the minimum window size the capacities match the game exactly: 8 above, 8 below, 5 left, and 5 right. A larger widget fits more per side. If your pack has many roots, give the embed a larger box and check the result at the size your readers will see.

Use the bundle

<iframe
  src="https://mcwidgets.kaban.sh/advancements/embed.html?bundle=https://example.com/advancements/bundle.json"
  style="width: 100%; height: 420px; border: 0;"
  title="Server advancements"
></iframe>

Or mount it yourself. See Using the JavaScript API.

Clone this wiki locally