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

Dialogs

Verified against Minecraft 26.3 · Part XIII · You click a server in the multiplayer list and, before the world has loaded — before you are in a world at all — a form appears with text boxes on it, and a vanilla server sends one only from a command it registers behind debug flags.

A dialog is a data pack’s form: a title, some body text, some inputs and some buttons, decoded from JSON and put on your screen. Nothing about that is surprising until you notice which protocol phase it works in. ClientboundShowDialogPacket is registered in both the play and the configuration protocols, with a different codec in each, so a server can interrupt the join handshake to ask you something — configuration being the phase a joining client is parked in, and the one a play session can be sent back to (protocol phases). Vanilla only ever does it from a dev-flag-gated command — but the machinery is there, complete, in the shipped jar.

And the reason it works there is not a special case bolted on; it is a second codec, and it explains itself. The configuration buffer is a plain byte buffer with no registry access (packets and stream codecs), so the packet cannot carry a holder id. Dialog.CONTEXT_FREE_STREAM_CODEC therefore sends the whole dialog inline. The dialog is encoded with no registry access at all, so a dialog it refers to goes inline too, and an element that can only be named by id — an enchantment on an item body’s item, say — cannot be sent at all.

A dialog is also one of this part’s two clearest instances of a pattern found across the game — game tests is the other — a thing made a registry element loaded from a data pack rather than a Java class. That argument is made once, for all its instances, in the data-driven type pattern; this page assumes it. Four of the pattern’s registries are dialog registries: BuiltInRegistries.DIALOG_TYPE for the dialog itself, and BuiltInRegistries.DIALOG_BODY_TYPE, BuiltInRegistries.INPUT_CONTROL_TYPE and BuiltInRegistries.DIALOG_ACTION_TYPE for the three kinds of part inside it.

The cast

classwhat it decidesside
Dialogthe registry element. Dialog.DIRECT_CODEC dispatches on BuiltInRegistries.DIALOG_TYPE; there are two stream codecs, and which one is used decides the whole pageboth
CommonDialogDatawhat every dialog embeds: titles, whether escape closes it, whether it pauses the game, the after-action, the body elements and the inputs. Its MapCodec is where the pause validation livesboth
DialogActionclose, none, or wait-for-response — and DialogAction.willUnpause is what that validation testsboth
InputControlTextInput, SingleOptionInput, BooleanInput, NumberRangeInput. An Input is a key plus a control, and the key must be a valid macro variable nameboth
Actionproduces an optional ClickEvent from the live input values, through Action.ValueGetterclient
ClickEventextended with ClickEvent.ShowDialog, which is how a chat message, a book, a dialog or a sign can open a dialog, and ClickEvent.Custom, which carries a custom actionboth
DialogScreensthe codec-to-screen-factory map, with DialogScreen as the base and DialogControlSet owning the live gettersclient
DialogConnectionAccessthe phase-specific way back to the server — and the configuration-phase one refuses to run commandsclient

net/minecraft/server/dialog spans four packages, all in the server jar; the screens that render them are client-only in net/minecraft/client/gui/screens/dialog — SimpleDialogScreen for both simple kinds, and MultiButtonDialogScreen, DialogListDialogScreen and ServerLinksDialogScreen over an abstract ButtonListDialogScreen — which is the same one-more-screen pattern GUI and screens covers everywhere else. The five kinds DialogTypes.bootstrap registers are NoticeDialog and ConfirmationDialog (both SimpleDialog) and MultiActionDialog, DialogListDialog and ServerLinksDialog (all ButtonListDialog) — and both of those supertypes are interfaces, not classes.

From a JSON file to a click the server reads

sequenceDiagram
    box Server
    participant DlgC as DialogCommand
    participant SP as ServerPlayer
    participant MS as MinecraftServer
    end
    box Client
    participant CComPL as ClientCommon<br/>PacketListenerImpl
    participant DlgS as DialogScreen
    participant DCS as DialogControlSet
    end

    DlgC->>SP: openDialog
    SP->>CComPL: ClientboundShowDialogPacket — a holder id, or inline
    CComPL->>CComPL: handleShowDialog — a screen for the dialog's type
    CComPL->>DlgS: the new screen
    DlgS->>DCS: addInput, per input — a live getter each
    DCS->>DCS: the click — the action reads the getters now
    DCS->>DlgS: runAction, with the click event it made
    DlgS->>CComPL: DialogConnectionAccess.<br/>sendCustomAction
    CComPL->>MS: ServerboundCustom<br/>ClickActionPacket
    MS->>MS: handleCustom<br/>ClickAction

One /dialog show and one click on a custom button — the input values are read at the click, by the control set that has held a live getter for each input since the screen was built, and the packet that carries them back reaches a handler that, in vanilla, writes a debug log line and nothing else.

The trace turns on one decision: when are the input values read? Not at packet time and not at screen construction. DialogControlSet.addInput registers a live Action.ValueGetter for each input as the screen is built, and Action.createAction calls them at the moment of the click — which is why the same Action object produces a different command each time, and why CommandTemplate can be a template rather than a string. The button hands the click event it made to DialogScreen.runAction, and a custom one leaves through DialogConnectionAccess.sendCustomAction, the phase’s way back to the server. ActionTypes registers nine kinds. Seven are generated in a loop over the click-event kinds a server is allowed to send, each keeping that kind’s own name, so a click-event kind is automatically a dialog action — and the one kind that is not allowed, opening a local file, can never be one. The other two are registered by hand under names of their own: CommandTemplate as dynamic/run_command, and CustomAll as dynamic/custom, which packs every input value into an NBT compound.

The packet on the way out is the play-phase form, which names a registered dialog by its holder id and sends an unregistered one inline; in the configuration phase the same packet is always inline, for the reason at the top of this page.

Nothing on the server side of this ever ticks. A dialog is a packet send from whatever ran the command or handled the click; the reply hops off the Netty thread onto the server’s PacketProcessor before MinecraftServer.handleCustomClickAction sees it, and on the client ClientCommonPacketListenerImpl.handleShowDialog hops to the client’s processor before touching the screen stack. Exactly one thing in this system ticks: WaitingForResponseScreen, counting ticks to reveal and then enable its Back button.

Four ways a dialog opens, and two of them never ask the server

Four things open a dialog in play; the configuration-phase sender, DebugConfigCommand, sends its packet itself (below). Two of the four go through ServerPlayer.openDialog, the server-side entry point, and the other two open it on the client with no packet at all.

/dialog show is the obvious one. A click event dispatched on the client is the interesting one, because “a component with a click event” is not the same as “a component whose click events are dispatched”: there are exactly three places on the client where a show dialog event actually is — chat, a book, and DialogScreen itself, which dispatches its own buttons and body text. An item’s name or lore is tooltip text and dispatches nothing.

A sign is clicked, but its click event is never dispatched on the client. SignBlockEntity reads the event server-side and calls ServerPlayer.openDialog directly — the same way it runs a run command event, and the reason a sign is the one clickable thing the client never gets to vet (permissions).

A tag is the fourth, and it needs no server at all in the moment. DialogTags.PAUSE_SCREEN_ADDITIONS and DialogTags.QUICK_ACTIONS let a data pack put dialogs behind a pause-menu button and behind a hotkey (with the shipped tags empty, that button opens the server’s links, when it sent any), and Dialogs holds the three the jar ships. Closing one from the server is ClientboundClearDialogPacket, and it is registered in both phases, as the two packets already on this page are — ClientboundShowDialogPacket going out and ServerboundCustomClickActionPacket coming back. Three packets, both protocols, one system.

What a dialog is made of

Inside a dialog, the parts dispatch on registries of their own the same way the dialog does: DialogBody over BuiltInRegistries.DIALOG_BODY_TYPE (PlainMessage and ItemBody), InputControl over BuiltInRegistries.INPUT_CONTROL_TYPE, and ActionButton carrying a CommonButtonData of label, tooltip and width. DialogBodyTypes, InputControlTypes and ActionTypes are the three bootstraps that fill them in code, StaticAction is the plain “do this fixed click event” action, and DialogCommand is /dialog itself. DialogBodyHandlers and InputControlHandlers are the client-side factory maps that mirror them.

One of those parts reaches outside the system. An input’s key is validated by ParsedTemplate against StringTemplate.isValidVariableName — the same rule a macro function’s parameters obey — which is why every input can be named in a CommandTemplate, and the seam into functions and macros.

What a data pack cannot do

Three defences are built into the model rather than into any particular dialog, and each one exists because the feature would otherwise be a way to trap a player.

The exit is not optional. DialogScreen’s initialisation is final: it always adds a warning button that opens a nested confirm screen offering to disconnect, and repositions it if a layout would push it off-screen. A dialog whose after-action waits for a response swaps in WaitingForResponseScreen, which reveals a Back button after a second and enables it after five.

Pausing is validated by the codec, wherever it decodes. A dialog that pauses the game with an after-action that never unpauses is rejected, because it would strand the player in a paused world. The check sits on CommonDialogData’s codec rather than on the loader, and MapCodec.validate is applied to both directions of the codec — so it runs on the server as it encodes and on the client as it decodes, which is what covers a dialog sent inline in the configuration phase.

A button that runs a command is not simply a chat command. It goes through ClientPacketListener.sendUnattendedCommand, which parses it up to twice and, on three of its four outcomes, shows you a confirmation screen before sending anything (permissions owns the four outcomes). What is this system’s own is the phase: the configuration-phase DialogConnectionAccess refuses to run a command at all, logging a warning instead, so a button on a dialog shown before you are in a world can do everything except that.

The extension point vanilla does not use

MinecraftServer.handleCustomClickAction is one line, logging at debug. The entire custom-action mechanism — an arbitrary id plus an arbitrary NBT payload, sent from a dialog, a chat message or a book (a sign’s is handed to it on the server) — exists for data packs and server software to build on. The game itself only defines the transport, and defends it with a 32 KB NBT accounter and a 64 KB cap on the payload’s own length prefix.

The same is true one level up: the only vanilla sender of a configuration-phase dialog is DebugConfigCommand, which the game registers only in a dedicated server’s command set, and only when started with its debug system properties (Brigadier and commands). A server really can put a form in front of you before you are in the world. Vanilla does it only there.

Where to look

Dialog and CommonDialogData for the model, then Action — the value-getter indirection is the only subtle thing in the whole system. DialogScreens for how a codec becomes a screen, and MinecraftServer.handleCustomClickAction for the one line that is the extension point.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.