Wiki 9Minecraft Wiki 9Minecraft

Server Forms (@minecraft/server-ui)

Create interactive menus, dialogs, and input forms for players using the Bedrock Scripting API.

5Sections
4Code examples
3Known errors

Server Forms allow behaviour packs to display native graphical user interfaces to players without requiring custom resource pack UI files. Provided by the @minecraft/server-ui module, these forms pause the player's movement and collect choices, confirmation, or custom inputs like text strings and numbers. You reach for server forms whenever your add-on needs an interactive menu, such as a server teleporter, a shop interface, or an admin configuration panel.

Manifest Dependency Setup

Before writing any form code, your behaviour pack must declare a dependency on the @minecraft/server-ui module inside its manifest.json file. This module is separate from the core @minecraft/server module, though you will almost always use both together so you can get a Player object to show the form to.

Add both modules to the dependencies array in your behaviour pack manifest. Make sure the entry module type is set to script and points to your main JavaScript file.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "Server Forms Pack",
    "description": "Demonstrates @minecraft/server-ui forms",
    "uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "version": [1, 0, 0],
    "min_engine_version": [1, 20, 80]
  },
  "modules": [
    {
      "type": "script",
      "language": "javascript",
      "uuid": "f1e2d3c4-b5a6-4f5e-8d9c-0b1a2f3e4d5c",
      "entry": "scripts/main.js",
      "version": [1, 0, 0]
    }
  ],
  "dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "1.10.0"
    },
    {
      "module_name": "@minecraft/server-ui",
      "version": "1.2.0"
    }
  ]
}

The Three Form Types

The @minecraft/server-ui module provides three distinct form classes, each suited to a specific kind of player interaction. You cannot mix and match elements between them; for example, you cannot put a text input box inside an ActionFormData menu.

Every form follows the same lifecycle: you instantiate the class, chain configuration methods to add text or controls, and call the .show(player) method. The .show() method returns a JavaScript Promise that resolves once the player either submits the form or closes it.

Class NameResponse TypeWhat it does
ActionFormDataActionFormResponseDisplays a title, body text, and a vertical list of clickable buttons with optional icons. Best for navigation menus and selectors.
MessageFormDataMessageFormResponseDisplays a simple dialog box with a title, body text, and exactly two buttons. Best for Yes/No confirmations and warnings.
ModalFormDataModalFormResponseDisplays a customizable input form supporting text fields, sliders, dropdowns, and toggles, with a single submit button at the bottom.

Building Button Menus with ActionFormData

ActionFormData is the workhorse of server UI. You set a title with .title(), optional descriptive text with .body(), and add as many buttons as you need by calling .button(). The button method accepts a label string and an optional texture path relative to the resource pack root (such as textures/items/apple).

When the Promise from .show() resolves, check the canceled property first. If the player pressed Escape or the close button, canceled will be true and selection will be undefined. Otherwise, response.selection contains the zero-based index of the button that was clicked, matching the order in which you added them.

BP/scripts/main.js
import { world } from "@minecraft/server";
import { ActionFormData } from "@minecraft/server-ui";

world.afterEvents.itemUse.subscribe((event) => {
  const player = event.source;
  if (event.itemStack.typeId !== "minecraft:compass") return;

  const form = new ActionFormData()
    .title("Teleporter Menu")
    .body("Choose a destination:")
    .button("Spawn Point", "textures/items/bed_red")
    .button("Market", "textures/items/emerald")
    .button("Arena", "textures/items/iron_sword");

  form.show(player).then((response) => {
    if (response.canceled) {
      player.sendMessage("Menu closed.");
      return;
    }

    if (response.selection === 0) {
      player.teleport({ x: 0, y: 64, z: 0 });
    } else if (response.selection === 1) {
      player.teleport({ x: 100, y: 64, z: 100 });
    } else if (response.selection === 2) {
      player.teleport({ x: -100, y: 64, z: -100 });
    }
  });
});

Collecting Inputs with ModalFormData

When you need more than a single click from the player, ModalFormData lets you build a multi-field form. It supports four input methods: .dropdown() for selecting from a string array, .slider() for choosing a number within a range, .textField() for freeform text entry, and .toggle() for boolean true/false switches.

When the player clicks the submit button at the bottom of a modal form, the response returns a formValues array. Each element in formValues corresponds to an input control in the exact order they were added to the form. A toggle returns a boolean, a slider or dropdown returns a number, and a text field returns a string.

BP/scripts/modal_example.js
import { ModalFormData } from "@minecraft/server-ui";

export function showSettingsForm(player) {
  const modal = new ModalFormData()
    .title("Player Settings")
    .textField("Custom Nickname", "Enter name here...", player.name)
    .dropdown("Class Role", ["Warrior", "Archer", "Mage"], 0)
    .slider("Difficulty Level", 1, 10, 1, 5)
    .toggle("Enable PvP", false);

  modal.show(player).then((response) => {
    if (response.canceled) return;

    const [nickname, roleIndex, difficulty, pvpEnabled] = response.formValues;

    player.sendMessage(`Saved! Name: ${nickname}, Role ID: ${roleIndex}`);
    player.sendMessage(`Difficulty: ${difficulty}, PvP: ${pvpEnabled}`);
  });
}

Handling Busy Players and Cancellations

A form cannot open if the player already has another UI screen open, such as their inventory, a chest, or the chat window. When this happens, the form resolves immediately with response.canceled set to true and response.cancelationReason set to FormCancelationReason.UserBusy.

This is especially common when triggering a form from a custom chat command, because the chat window takes a few ticks to close after the player presses Enter. You can handle this by checking the cancellation reason and retrying the .show() call inside a system.runTimeout loop until the player's screen is clear.

BP/scripts/form_helper.js
import { system } from "@minecraft/server";
import { FormCancelationReason } from "@minecraft/server-ui";

export function forceShowForm(player, form) {
  form.show(player).then((response) => {
    if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy) {
      system.runTimeout(() => {
        forceShowForm(player, form);
      }, 5);
      return;
    }
    if (response.canceled) return;

    player.sendMessage(`Selected option: ${response.selection}`);
  });
}

Which versions this applies to

Stable in Minecraft Bedrock 1.19.50 and later. Version 1.2.0 of @minecraft/server-ui requires Bedrock 1.20.80+. No experimental toggles are needed to use standard release versions of this module.

When it does not work

What you seeWhyThe fix
Content Log error: ReferenceError: error loading module '@minecraft/server-ui'The behaviour pack manifest.json is missing @minecraft/server-ui in its dependencies array, or requests a version higher than the game supports.Add the @minecraft/server-ui dependency object to BP/manifest.json with a stable version string such as "1.2.0".
The form never appears on screen when triggered from a chat event, and immediately acts as if the player closed it.The player still has the chat UI open on the tick the event fires, causing the form to fail with FormCancelationReason.UserBusy.Check if response.cancelationReason equals FormCancelationReason.UserBusy and use system.runTimeout to delay showing the form by a few ticks until the chat screen closes.
Scripting error: Native function [ActionFormData::show] do not have required privileges.You called form.show() inside a beforeEvent callback (such as world.beforeEvents.itemUse), which runs in read-only mode and forbids modifying game or UI state.Switch to an afterEvent (such as world.afterEvents.itemUse) or wrap the form.show() call inside a system.run(() => { ... }) callback.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.