resourcepack.ai
DOCS

Dialogs

Dialog Engine

Dialog Engine is a plugin for Minecraft's own dialog screens: the real menus the client has drawn since 1.21.6, with text, items, text boxes, dropdowns, switches, sliders and buttons. You write them in YAML, and the plugin opens them, fills them in for each player and runs what their buttons say.

  • Server owners write dialogs in a folder and give them commands, permissions, conditions, lists and actions. No code.
  • Developers ship dialogs with their own plugin, add their own actions, placeholders and lists, and hear every click as an event.

It needs no resource pack. A dialog is drawn by the game itself, in the game's own look. (The one exception is drawn dialogs, which are pictures and bring a small pack of their own.)

Installing#

  1. 1

    Get the jar

    Download Dialog Engine from SpigotMC.

  2. 2

    Drop it in

    The jar into plugins/, then restart. It runs on Spigot or Paper 1.21.7 or newer, one jar for both, on Java 21. Players need a 1.21.6 or newer client, since that is when dialogs arrived in the game. PlaceholderAPI is optional.

  3. 3

    Look at what it made

    plugins/DialogEngine/
      config.yml
      AI-GUIDE.md        ← the format, for an AI assistant
      dialogs/           ← yours
        samples/         ← ten working examples
      drawn/
        samples/         ← two drawn examples

On a server older than 1.21.7 the plugin says so in the console and disables itself.

What talks to the internet

Two things, and both can be turned off in config.yml. update-check asks SpigotMC for the latest version. telemetry sends an anonymous count every 30 seconds: a random id, the number of players online and the names of plugins that depend on Dialog Engine. Nothing else.

Drawn dialogs are the third: /de edit, and a drawn dialog whose dialog.yml you edited by hand, send that one dialog to the editor to be drawn. Plain YAML dialogs never leave the server.

Your first dialog#

  1. 1

    Make a file

    plugins/DialogEngine/dialogs/myserver/hello.yml:

    hello:
      command: hello
      title: "<gold><bold>Hello"
      body:
        - "<gray>Welcome, <white>{player}</white>. There are {online} of us online."
      buttons:
        - label: "<green>Thanks"
          actions:
            - "[message] <green>Have fun!"
            - "[close]"

    That is the dialog myserver:hello. The folder is the namespace and the key is the name.

  2. 2

    Reload

    /de reload

    It tells you how many dialogs loaded and lists every problem it found, with the file each one is in.

  3. 3

    Open it

    /hello

    Or /de open myserver:hello, which works on any dialog whether it has a command or not.

Where dialogs live#

Every folder in dialogs/ is a namespace, and every top-level key in a .yml file inside it is a dialog: welcome in dialogs/samples/menus.yml is samples:welcome. Subfolders are read too and stay in the same namespace. Files sitting directly in dialogs/ are in the namespace server.

Names are lower case: a-z, 0-9, _, . and -. The same name twice in one namespace is reported, and the later one wins.

When one dialog names another ([open] confirm, if-missing: pick), a name without a namespace means one in the same namespace.

Text everywhere is MiniMessage (<gold>, <bold>, <#3670f8>), never & codes. Quote any string that starts with <, [, {, % or *, or has : in it, or YAML will read it as something else.

Writing dialogs with an AI

The plugin writes plugins/DialogEngine/AI-GUIDE.md on every start. It is the format plus the rules that catch the usual mistakes, written for an AI assistant to read. Hand it over with what you want the screen to do.

The format#

A complete dialog, a shop with a slider:

shop:
  command: shop                 # /shop opens it (optional)
  aliases: [store]
  permission: myserver.shop     # who may open it themselves (optional)
  title: "<gold><bold>Shop"
  columns: 2                    # buttons side by side
  escape: true                  # Escape closes it (default true)
  after: wait                   # wait | close | none, see config.yml
  body:
    - "<gray>You have <white>%vault_eco_balance%</white> coins."   # text
    - item: diamond             # an item, with words beside it
      name: "<aqua>Diamond"
      description: "<gray>A coin each"
  inputs:
    - key: amount
      type: slider
      label: "How many"
      min: 1
      max: 64
      step: 1
      initial: 16
  buttons:
    - label: "<green>Buy"
      tooltip: "Buy {amount} diamonds"
      actions:
        - "[if] %vault_eco_balance% >= {amount}"    # stop unless they can pay
        - "[console] eco take {player} {amount}"
        - "[console] give {player} diamond {amount}"
        - "[message] <green>Bought {amount} diamonds."
        - "[close]"
  exit:                         # the button Escape also presses
    label: "Close"

The dialog itself#

KeyWhat it does
title:The heading. MiniMessage
external-title:A shorter title for where the game names the dialog outside it, such as on a button that opens it
command:A command that opens it, one word of a-z, 0-9, _ and -. See Its own command
aliases:More names for that command
permission:What a player needs to open it themselves, by its command or from another dialog
args:Names for the command's arguments, which become placeholders. See A dialog about somebody
if-missing:The dialog to open instead when an argument is left out
defaults:Values the dialog starts with, as key: value
type:notice, confirmation, multi_action (or menu) or links. Leave it out and the buttons decide
columns:How many buttons side by side. Default 2
escape:Whether Escape closes it. Default true
pause:Whether it pauses a single-player game. Default false, and not allowed with after: none
after:What the screen does the moment a button is pressed: wait, close or none. Default from default-after in config
body:What is drawn above the inputs. See Body
inputs:Text boxes, dropdowns, switches and sliders. See Inputs
buttons:See Buttons
exit:The button at the bottom, which Escape also presses
lists:Lists of rows the dialog can repeat over. See Lists and pages
json:Send a file of the game's own dialog JSON instead. See Raw JSON dialogs

Types#

The layout follows the buttons. One button and no exit: is a notice. More is a grid of buttons, columns: wide, with the exit: button under it.

Say type: confirmation, or just write confirm: and cancel: buttons in place of buttons:, for a yes/no screen. type: links is the game's server links screen, the links your server sends in its configuration.

rules_decline:
  title: "Decline the rules?"
  type: confirmation
  escape: false
  body:
    - "<gray>You can't play here without accepting them."
  confirm:
    label: "<red>Leave"
    actions: ["[console] kick {player} You need to accept the rules to play."]
  cancel:
    label: "Read them again"
    actions: ["[back]"]

Body#

A body entry is a string, which is a line of text, or a map:

KeyWhat it does
text:MiniMessage. A list is several lines. width: 1 to 1024, default 200
item:An item id, drawn with description: beside it. Also name:, lore:, amount:, model: (an item model id), glow:, tooltip: (false turns off its hover tooltip), decorations: (false hides its count and durability bar), and width:/height: 1 to 256
head:A player's name: the item is their head
if:Only drawn when this holds. See Conditions
for-each:Drawn once per row of a list. See Lists and pages
body:
  - head: "{target}"
    width: 24
    height: 24
    description: "<gray>Tell us what <white>{target}</white> did."
  - text:
      - "<white>1.</white> <gray>Be kind."
      - "<white>2.</white> <gray>No cheats."
    width: 260

Inputs#

type:KeysWhat {key} holds
textmax-length, lines and height for a text area, initialWhat was typed
optionsoptions:, a list of {id, text, initial} (or - id: Text), or a for-eachThe chosen id. {key.label} is its text
toggleinitial, on-true, on-falsetrue or false. {key.text} is on-true or on-false
slidermin, max, step, initial, format (a translation key)The number

Every input needs a key: of letters, digits and _, and takes label:, label-visible:, width: and if:.

inputs:
  - key: reason
    type: options
    label: "Reason"
    options:
      - { id: cheating, text: "Cheating", initial: true }
      - { id: chat, text: "Chat abuse" }
  - key: details
    type: text
    label: "What happened?"
    lines: 3
    height: 48
    max-length: 200
    required: true
    min-length: 10
    error: "Tell us a little more: at least 10 characters."

Checks#

A text input can have required: true, min-length:, pattern: (a Java regex the whole value must match) and error: (the message to show). When a button is pressed with an input that fails, the dialog is drawn again with what was typed still in it and the message on top. The message is also {error}, which the dialog can print where it likes instead.

A button with validate: false skips the checks, which is what a Cancel button wants. The exit: button never checks.

What a player typed is escaped wherever it is printed, so nobody can put a colour or a click into somebody else's screen.

Buttons#

KeyWhat it does
label:Its text
tooltip:Shown on hover
width:1 to 1024, default 150
permission:Hidden from a player without it
if:Only shown when this holds
for-each:One button per row of a list
validate:false skips the input checks
id:A name for your own plugin to recognise it by
actions:What it does. See Actions

For a button that does one thing there are shorthands, each one action: command:, console:, open:, url:, copy:, suggest:, message:, close: true and back: true.

buttons:
  - label: "Warps"
    open: warps
  - label: "Website"
    url: "https://example.com"
  - label: "Close"
    close: true

Actions#

A button's actions: run in order, top to bottom. A line without a [tag] is a command the player runs.

ActionWhat it does
[player] cmdThe player runs it, with their own permissions
[console] cmdThe console runs it
[message] textTells the player. MiniMessage
[broadcast] textTells everybody
[actionbar] textShows it above the hotbar
[title] title | subtitleShows a title
[sound] id [volume] [pitch]Plays a sound: [sound] entity.experience_orb.pickup 1 1.4
[open] id [key=value ...]Opens another dialog, carrying this one's values (and what was typed) over
[back]The dialog this one was opened from, as it was
[refresh]This one again, with what was typed still in it
[page] next|prev|first|last|NTurns the page of a paged list
[set] key=value ...Sets a value for the actions after, and any dialog opened
[clear] [keys]Forgets what was typed: every input, or the keys named
[close]Closes the dialog
[delay] ticksWaits before the rest. 20 ticks is a second
[if] conditionStops here unless it holds
[permission] nodeStops here unless the player has it
[stop]Stops here
[url] linkOpens a link
[copy] textCopies to the clipboard
[suggest] /cmdPuts a command in the chat box, ready to finish
[myplugin:tag] ...Whatever another plugin registered

[url], [copy] and [suggest] are the game's own button actions, so they do exactly that only when they are a button's only action. Mixed in with others, [url] sends the link in chat to click, and [copy] and [suggest] do nothing.

End a button that should leave the screen with [close], or it stays up.

What a player typed, in a command#

A [console] or [player] command takes a typed value (a text input, a command's args:, values from /de open, {player_display}) only when it is letters, digits, _, . and -: a name, a number, an id. Anything else (@a, a space, a quote) and the button runs nothing. A text input is drawn again with the unsafe-value message on it, anything else gets the message in chat, and the console says which placeholder it was. The check happens before the first action runs, so a click never stops halfway with the money taken and nothing given.

Where more than that is safe, say how the value is used:

FilterWhat it does
{key|raw}As typed, on one line. For the last, free-text argument of a plugin's command: staffchat {player}: {details|raw}. Not for vanilla's say, msg or tell, which read @a in it. In a [player] command it is usually fine: the player could type that command themselves
{key|json}Escaped for the inside of a quoted JSON string: tellraw @a {"text":"{msg|json}"}
{key|snbt}Escaped for the inside of a quoted SNBT string: give {player} paper[custom_name='{name|snbt}']

Values from the dialog itself, from plugins and PlaceholderAPI, and from sliders, toggles and dropdowns (only options the dialog drew count) are not checked. A filter works on them too.

When a command fails#

A command that fails stops the rest. When a [console] or [player] command fails as far as the server can tell, the actions after it do not run. For [console] the player is told (command-failed) and the console logs it.

The server can only tell when the command does not exist (the plugin behind it is missing, say) or throws an error. A command that ran and refused, like an eco take without the money in the account, has succeeded as far as the server knows, whatever it printed. So check before taking anything, as the shop above does with [if] %vault_eco_balance% >= {amount}, using PlaceholderAPI's Vault expansion.

Placeholders#

{name} works anywhere: titles, text, labels, tooltips, actions and conditions. A name nothing knows is left as written.

  • What the dialog was opened with: /de open shop Steve coins=40, an API call, a command's args:, defaults:, or an [open] or [set] action.
  • Everything typed into its inputs, once a button is pressed.
  • The built-ins:
PlaceholderValue
{player}The player's name (also {player_name})
{player_uuid}Their UUID
{player_display}Their display name, as plain text
{player_world}The world they are in
{player_x} {player_y} {player_z}Their block position
{player_health}Their health, rounded up
{player_level}Their experience level
{player_gamemode}survival, creative, adventure or spectator
{online}Players online
{max_players}The server's player limit
{dialog}This dialog's id

PlaceholderAPI#

With PlaceholderAPI installed, every %placeholder% works too, in the same places. A % in what a player typed is printed as typed and never filled, so nobody can type a placeholder into a text box to read somebody else's.

Choices#

{mode?on:Enabled|off:Disabled|Unknown} prints the words for the value mode has, and the last entry when none matches. $value in the words prints the value itself:

- "<gray>Chat: {chat?true:<green>on|false:<red>off|$value}"

Filters#

{name|raw}, {name|json} and {name|snbt} are for putting what a player typed into a command. See What a player typed, in a command.

Conditions#

if: works on a body entry, an input and a button, and [if] is the same thing as an action.

{muted}                             true unless empty, false, 0, no, off or null
!{muted}
{warnings} >= 3                     numbers compare as numbers, anything else as words
{rank} == admin && perm:server.staff
({page} > 1 || {mode} != "quiet mode")
  • ==, !=, >, <, >= and <= compare. Two numbers compare as numbers, anything else as words, ignoring case.
  • &&, ||, ! and brackets combine them.
  • perm:node is whether the player has that permission.
  • A placeholder nothing could fill counts as false.
  • Put a value with spaces in it in quotes.

A button's if: and permission: are asked again when it is pressed, so a button that has stopped applying since the dialog was drawn does nothing.

Lists and pages#

for-each: name on a body entry, a button or an input's options: repeats it once per row of a list. In each one:

  • {row.field} is a field of the row. as: renames row.
  • {row.index}, {row.first} and {row.last} say where in the list it is.

per-page: N pages it, and sets {page}, {pages}, {has_next} and {has_prev}. [page] next turns. limit: N caps how many rows are shown.

Built in:

ListFields
online_playersname, uuid, world, ping
other_playersThe same, without the player looking
worldsname, players, environment

Vanished players stay hidden from whoever cannot see them.

A dialog can declare its own under lists:, and plugins register more.

warps:
  title: Warps
  lists:
    places:
      - { id: spawn, name: "Spawn" }
      - { id: arena, name: "<red>Arena" }
  buttons:
    - for-each: places
      label: "{row.name}"
      actions: ["[player] warp {row.id}", "[close]"]

A paged list of players, eight to a page:

players:
  title: "Online <dark_gray>({online})"
  body:
    - "<gray>Page {page} of {pages}"
  buttons:
    - for-each: online_players
      per-page: 8
      label: "{row.name}"
      tooltip: "{row.world} · {row.ping}ms"
      actions: ["[open] player_card target={row.name}"]
    - label: "<gray>← Previous"
      if: "{has_prev}"
      actions: ["[page] prev"]
    - label: "<gray>Next →"
      if: "{has_next}"
      actions: ["[page] next"]
  exit:
    label: "Close"

Opening a dialog#

Its own command#

command: shop makes /shop open it, and aliases: adds more names. These commands come and go with /de reload, no restart. With a permission:, a player without it is refused and does not see the command in tab completion.

A command name another plugin already has stays that plugin's: the dialog goes without, and the console says so.

A dialog about somebody#

args: [target] makes /punish Steve open the dialog with {target} set to Steve. Tab completion offers online players. The last argument takes the rest of the line, so args: [target, reason] reads /punish Steve being rude as Steve and being rude.

if-missing: other_dialog opens another dialog when an argument is left out, typically a list of players to pick from:

report:
  command: report
  args: [target]
  if-missing: report_pick
  title: "Report <red>{target}"
  # ...
 
report_pick:
  title: "Who are you reporting?"
  buttons:
    - for-each: other_players
      label: "{row.name}"
      actions: ["[open] report target={row.name}"]

From the console, or for somebody else#

/de open <id> [player] [key=value ...]

/de open samples:report Steve target=Alex opens the report on Steve's screen, about Alex. Staff opening a dialog this way skip its permission:; dialogengine.open is the permission that counts. The console and command blocks always name a player.

From another dialog#

[open] id opens another, carrying every value over, and [back] on that one returns to the first as it was. The target dialog's permission: is still checked.

Raw JSON dialogs#

For a dialog Minecraft's format can say and this one cannot, json: file.json (a file beside the YAML) sends a minecraft:dialog object exactly as written, with {placeholders} filled. Its buttons do whatever the JSON says, and none of Dialog Engine's actions or checks apply to them.

Drawn dialogs#

A plain dialog is the game's own widgets. A drawn dialog is a picture: a screen designed in the dialog editor in the browser, in the game's own look or any other, shown with a small resource pack. Its buttons still run Dialog Engine's actions, so [console], [open], [message] and the rest all work, and placeholders are filled when it is opened and clicked.

/de edit server:shop_drawn

That prints a link to the editor, which needs no account. The link works once and lasts three hours. Draw the screen, press Send to server, and it is written into plugins/DialogEngine/drawn/server/shop_drawn/ and loaded. Run /de edit on it again to change it.

/de edit only draws. A name a YAML dialog already has is edited in its file, and the command says so. A name without a namespace is in server.

Each drawn dialog's folder holds a dialog.yml, with its command: and commandPermission: at the top, like a YAML dialog's. You can edit it by hand and /de reload: a changed file is sent to the editor to be redrawn, which needs the internet.

The resource pack#

Drawn dialogs are drawn by one small pack, built from all of them. It is sent beside whatever pack your server already sends, not instead of it, and it is only built once there is a drawn dialog at all. A server that never uses /de edit never asks anybody to download anything.

By default the plugin serves the pack itself, on port 8165. Set pack.public-host to your server's public address or domain, and open that port, or players outside your machine cannot download it. Or host the pack yourself: /de pack writes it to a file, and pack.url says where you put it. See Config.

Players with dialogengine.pack.notify are told the address the pack is downloaded from when it fails to load for them, which is the quickest way to spot a missing public-host.

Samples#

On the first start the plugin writes ten sample dialogs into plugins/DialogEngine/dialogs/samples/, one file each: a welcome notice, server rules, a shop, kits, warps, a player list, a profile, a report form, a settings page with every input type, and a menu of them all. Open the menu with /dialog-samples. Only players with dialogengine.samples (ops) can open them.

Two more are drawn dialogs, in plugins/DialogEngine/drawn/samples/: a punishment panel (/sample-punish <player>) and a six-page social settings menu (/sample-social). /de edit samples:punish opens one in the editor. Their pack is sent when a player opens one, never on join, so the samples alone never ask anybody to download anything.

They are yours to copy, change or delete. Deleting one file keeps it deleted; deleting the whole folder brings them all back on the next start. Set samples: false in config.yml and they are never written.

Commands#

/dialogengine, with /de and /dialogs as aliases.

CommandWhat it doesPermission
/de listEvery dialog, by namespace, with its commanddialogengine.list
/de open <id> [player] [key=value ...]Open one, with valuesdialogengine.open
/de info <id>What a dialog is made of: type, buttons, inputs, command, permissiondialogengine.info
/de edit <id>Draw one in the browser editor, or make a new onedialogengine.edit
/de packWrite the drawn dialogs' resource pack to a filedialogengine.pack
/de reloadRead everything again, and say what is wrongdialogengine.reload

A subcommand you cannot run is not offered in tab completion, and /de on its own lists only the ones you can.

/dialogclick is what a click on a drawn dialog runs. It does only what the dialog on that player's screen allows, so it needs no permission, and typing it yourself does nothing.

Permissions#

One node per subcommand, always dialogengine.<subcommand>, and every one of them is op-only by default.

NodeWhat it allows
dialogengine.list/de list, every dialog on the server and its command
dialogengine.open/de open <id> on yourself: any dialog, whatever permission: the dialog itself asks for
dialogengine.open.others/de open <id> <player> on somebody else, on top of dialogengine.open
dialogengine.info/de info <id>, what a dialog is made of
dialogengine.edit/de edit <id>, drawing a dialog in the browser editor or making a new one
dialogengine.pack/de pack, writing the drawn dialogs' resource pack to a file
dialogengine.reload/de reload, reading every dialog again and seeing what is wrong
dialogengine.pack.notifyBeing told the address players download the drawn dialogs' pack from, when it fails to load for you
dialogengine.samplesOpening the sample dialogs
dialogengine.updateBeing told on joining that a newer Dialog Engine is on SpigotMC
dialogengine.adminAll of the above, in one node

dialogengine.admin is a parent for a server that does not want to think about it. Nothing checks it directly.

dialogengine.edit is the one worth a second look before granting it widely: pressing Send in the editor writes files into your server's dialog folder.

A dialog's own permission: (who may open it) and a button's (who sees it) are whatever node you write there. Dialog Engine declares none of them, so give them to players with your permissions plugin as usual:

/lp group default permission set myserver.shop true
/lp group admin permission set dialogengine.admin true

Config#

plugins/DialogEngine/config.yml. /de reload reads it again.

KeyDefaultWhat it does
samplestrueWrite the sample dialogs when their folder is missing
update-checktrueSay in the console and on join when an update is out
telemetrytrueThe anonymous usage count described under Installing
click-lifetime-minutes30How long a dialog's buttons keep working after it is drawn. Drawing it again starts the clock over
default-afterwaitWhat a dialog does the moment a button is pressed, when it does not say with after:. See below
close-delay-ticks10Under wait, how long to hold the waiting screen for a plugin to answer before closing it
click-soundui.button.clickThe sound a click on a drawn dialog makes. Blank for none
validation-format<red>⚠ {error}How a failed input check is shown, when the dialog does not print {error} itself
messages.*Everything the plugin says to players. MiniMessage
edit.urlhttps://studio.resourcepack.aiWhere the dialog editor is. Must be https:// (plain http:// only for one on the same machine)
pack.sendtrueSend the drawn dialogs' pack to players as they join
pack.requiredfalseKick players who decline it
pack.port8165The port the built-in pack server listens on
pack.bindblankThe address it listens on. Blank is every address the machine has
pack.public-hostblankThe address players download it from. Set this to your server's public address or domain
pack.urlblankHost the pack yourself and put its URL here; the built-in server is then off
pack.promptThe line shown when the pack is offered

The three after behaviours:

  • wait: the screen stays up with "waiting for the server" until the plugin answers, so one dialog replaces another with no flash of the world in between. Recommended.
  • close: it closes at once, then the next dialog (if any) opens.
  • none: it stays exactly as it is.

How a click is kept safe#

Every button is a server callback bound to the drawing of the dialog the player saw: the values it was drawn with, and its inputs. Nothing a player can type in chat presses a button.

  • Only the latest drawing can be pressed. A new one (another dialog, a refresh, a failed check) retires the buttons of the one before.
  • A button's permission: and if: are asked again when it is pressed.
  • Buttons stop working click-lifetime-minutes after the dialog was drawn, and a player gets at most ten clicks a second.
  • A drawn dialog's clicks work once per showing, only while that dialog is on the player's screen, and only run a command that is in its dialog.yml.

For developers#

Add depend: [DialogEngine] (or softdepend, and check DialogEngine.isEnabled() first) to your plugin.yml, and compile against the jar. The API is in ai.resourcepack.dialogs.api, and every call is main-thread only, like the rest of Bukkit.

DialogEngineAPI dialogs = DialogEngine.api();
 
// Ship your dialogs: copy defaults into your data folder, then hand it over.
// Server owners edit them there; /de reload reads them again.
saveResource("dialogs/menu.yml", false);
dialogs.registerFolder(this, "myplugin", new File(getDataFolder(), "dialogs"));
 
// Open one, with values for its {placeholders}.
dialogs.open(player, "myplugin:menu", Map.of("coins", "120"));
dialogs.refresh(player);   // the player's current dialog, drawn again
dialogs.close(player);
 
// A new action: [myplugin:give] diamond 3
dialogs.registerAction(this, "myplugin:give", (ctx, arg) -> {
    String[] p = arg.split(" ");
    ctx.viewer().getInventory().addItem(new ItemStack(Material.matchMaterial(p[0]), Integer.parseInt(p[1])));
    ctx.refresh();
});
 
// {myplugin_coins}
dialogs.registerPlaceholders(this, "myplugin_", (viewer, ctx, key) ->
        key.equals("coins") ? String.valueOf(economy.balance(viewer)) : null);
 
// for-each: myplugin_quests
dialogs.registerList(this, "myplugin_quests", (viewer, ctx) -> quests.of(viewer).stream()
        .map(q -> Map.of("id", q.id(), "name", dialogs.escape(q.name()), "done", String.valueOf(q.done())))
        .toList());

A dialog built in code is a ConfigurationSection in the same format: registerDialog(plugin, "myplugin:x", section).

Values you pass are read as MiniMessage. Anything a player wrote goes through dialogs.escape(...) first. An action's argument arrives with its placeholders filled and what a player typed in it as typed, so check it before it goes into a command.

Everything a plugin registers is dropped when it disables.

Beyond those, the API has current(player) and values(player) for the dialog a player has open, ids() and info(id) for what is loaded, canOpen(player, id) for a dialog's permission:, textJson(...) to render MiniMessage the way a dialog does, and reload().

Events#

EventWhen
DialogOpenEventA dialog is about to be drawn: opened, refreshed, paged or returned to. Cancellable; values() can be changed, and isReopened() says whether it is the same dialog again
DialogClickEventA button was pressed, its inputs passed, its actions not yet run. Cancellable; getButtonId(), getInputs(), getActions() (changeable), and getContext() to open, refresh or close
DialogInputErrorEventAn input failed its check; the message can be changed
DialogEngineReloadEventEverything was read again

A button with an id: and no actions is a button your plugin answers in DialogClickEvent:

@EventHandler
public void onClick(DialogClickEvent event) {
    if (!event.getDialogId().equals("myplugin:menu")) return;
    if (event.getButtonId().equals("claim")) {
        rewards.claim(event.getPlayer());
        event.getContext().close();
    }
}

Support#

Questions, bugs and ideas: the ResourcePack AI Discord.