← All articles

Advancements as a quest system

Vanilla has no quest system, and it does not need one, because advancements already are that in every way that matters: they chain, they gate, they reward, and they show progress. The trick is that the most useful trigger for custom quests is the one that never fires on its own.

Open the advancement builder

The shape of a tree

An advancement with no parent is a root, and a root gets its own tab on the advancements screen. Everything that names it as parent becomes a node in that tab, and those nodes can have children of their own. That is the whole tree model — there is no separate concept of a chapter or a quest line, just parentage.

harbor:harbor_quest_1        (root, own tab)
  └─ harbor:harbor_hunt      "parent": "harbor:harbor_quest_1"
       └─ harbor:harbor_end  "parent": "harbor:harbor_hunt"

Only the root carries a background, the texture tiled behind the tab. Setting one on a child does nothing, which is why a builder that knows the difference is worth more than a template.

The impossible trigger is the point

Every advancement needs at least one criterion, and a criterion needs a trigger. Vanilla ships plenty of them — kill a thing, enter a place, pick up an item — but a real quest rarely maps onto a single one of those. "Deliver the letter to the harbourmaster" is not a vanilla event.

So you use the trigger that never fires:

"criteria": {
  "delivered": {
    "trigger": "minecraft:impossible"
  }
}

Nothing in the world completes it. You do, from your own logic:

/advancement grant @s only harbor:harbor_quest_1

That command goes wherever the quest actually concludes — a command block by the pier, a function called from a villager trade, a check inside your tick function. The advancement stops being a detector and becomes a record: the thing that remembers this player got that far, survives restarts, and gates whatever comes next.

This is the pattern nearly every custom quest system on a vanilla server is built from.

Rewards, and the one that surprises people

Four kinds of reward can hang off an advancement:

"rewards": {
  "experience": 50,
  "recipes": ["harbor:reinforced_block"],
  "loot": ["harbor:quest_bundle"],
  "function": "harbor:on_quest_complete"
}

The function reward is the powerful one, because it turns an advancement into a hook: complete it and arbitrary commands run. Give an item, start a bossbar, set a scoreboard, open a gate.

The recipes reward is the one that surprises people, and it explains a mystery from the other side of the site: a custom recipe does not appear in the recipe book until something unlocks it, and this is that something. Vanilla ships a hidden advancement for every single one of its recipes, doing nothing but this. If your custom recipe crafts fine but never shows in the book, you have not written this file yet.

Testing without playing the quest

Two commands cover the whole loop:

/advancement grant @s only harbor:harbor_quest_1
/advancement revoke @s only harbor:harbor_quest_1

Swap only for until, from or through to move a whole branch at once, which saves a lot of clicking when you are testing a five-step chain. Revoke is what lets you run the same step again without a fresh world.

Two renames that break older examples

ChangeBeforeAfter
Folder, in 1.21advancements/advancement/
Display icon, in 1.20.5{"item": "minecraft:map"}{"id": "minecraft:map"}

The folder rename is the nastier of the two, in the same way it is for loot tables: the pack still loads and reports as enabled, and the advancement simply is not there. The icon rename at least tends to complain in the log.

Small things that cost an evening

Where to go next

The advancement builder writes the file and the matching grant command, and knows which fields belong to a root versus a child. For the container it goes in, see the data pack generator; to give the quest something worth handing over, the loot table generator and the recipe generator cover rewards and unlocks.