How to Create a GUI in Minecraft Bedrock Scripting

Building a custom graphical user interface (GUI) in Minecraft Bedrock Edition scripting is one of the more rewarding — and misunderstood — parts of the Bedrock Script API. Unlike Java Edition modding, where you have near-complete control over rendering, Bedrock takes a different approach. Understanding what's actually available, and how the system works under the hood, will save you hours of frustration.

What "GUI" Actually Means in Bedrock Scripting

In Bedrock Edition, you don't render custom UI elements directly onto the screen using JavaScript or TypeScript alone. Instead, Bedrock exposes a form-based UI system through the @minecraft/server-ui module. This module lets you display structured dialog boxes — not pixel-level custom interfaces, but functional, interactive screens that players can interact with in-game.

There are three core form types available:

Form TypeUse CaseInput Options
ActionFormDataMenus with buttonsButtons only
MessageFormDataSimple dialogsTwo buttons (confirm/cancel style)
ModalFormDataData collectionText fields, dropdowns, sliders, toggles

Each of these is a class you instantiate, configure, and then display to a specific player. They're not floating HUDs or custom overlays — they're modal screens, meaning gameplay pauses while the player interacts with them.

Setting Up Your Scripting Environment

Before writing a single line of UI code, your environment needs to be correctly configured. Bedrock scripting runs inside a behavior pack, and your manifest.json must declare the correct dependencies.

You'll need two modules referenced in your manifest:

  • @minecraft/server — the core scripting API
  • @minecraft/server-ui — the UI forms module

Your manifest dependencies block should reference both with compatible API versions. As of recent stable releases, these modules use a versioning system (e.g., 1.x.x format), and mismatched versions between your manifest and your import statements are one of the most common reasons scripts silently fail.

Your folder structure should look roughly like this:

my_behavior_pack/ ├── manifest.json ├── pack_icon.png └── scripts/ └── main.js (or main.ts if using TypeScript) 

If you're using TypeScript, you'll also need a build step using the Minecraft scripting toolchain (@minecraft/vanilla-data, esbuild or tsc, etc.). Pure JavaScript works without compilation but loses type safety.

Building a Basic GUI with ActionFormData

Here's a minimal working example of a button-based menu:

import { world } from "@minecraft/server"; import { ActionFormData } from "@minecraft/server-ui"; world.afterEvents.itemUse.subscribe((event) => { const player = event.source; const form = new ActionFormData() .title("Main Menu") .body("Choose an option below:") .button("Give Diamonds") .button("Heal Player") .button("Close"); form.show(player).then((response) => { if (response.canceled) return; if (response.selection === 0) { player.runCommand("give @s diamond 10"); } else if (response.selection === 1) { player.runCommand("effect @s instant_health 1 10"); } }); }); 

The .show() method returns a Promise, so you handle the player's response asynchronously. The response.selection value is a zero-based index matching the order you added buttons.

Collecting Player Input with ModalFormData

When you need text input, dropdowns, or toggles — for things like naming a warp point or configuring a setting — ModalFormData is the right tool. 🎛️

import { ModalFormData } from "@minecraft/server-ui"; const form = new ModalFormData() .title("Player Settings") .textField("Enter your nickname:", "Nickname here") .slider("Set difficulty", 1, 10, 1, 5) .toggle("Enable PvP", false); form.show(player).then((response) => { if (response.canceled) return; const nickname = response.formValues[0]; const difficulty = response.formValues[1]; const pvpEnabled = response.formValues[2]; }); 

response.formValues returns an array in the same order you added elements. Data types match the control: strings for text fields, numbers for sliders, booleans for toggles.

Common Pitfalls to Know About

Timing matters. Forms must be shown in response to a player action or event — you can't push a UI to a player on world load without a trigger. Attempting to show a form to a player who hasn't fully loaded will result in a canceled response or a silent failure.

Canceled responses aren't errors. Players can close a form by pressing Escape or the back button. Always check response.canceled before accessing response.selection or response.formValues, or you'll hit undefined value errors.

No persistent HUDs. The @minecraft/server-ui module doesn't support HUD overlays, action bar replacements, or on-screen elements that persist during gameplay. For scoreboard-style displays or action bar text, you'd use player.onScreenDisplay.setActionBar() from the core @minecraft/server module — but that's separate from form-based GUIs.

Variables That Affect Your Implementation 🧩

How complex your GUI system becomes depends on several factors specific to your project:

  • Skill level with async JavaScript — Promises and .then() chains are central to form handling; developers less familiar with async patterns often struggle here
  • Behavior pack target — a single-player map needs simpler trigger logic than a multiplayer server with concurrent players each potentially opening forms
  • Minecraft Bedrock API version — the scripting API has changed significantly across stable and beta/preview channels; some features in preview aren't available in stable releases yet
  • TypeScript vs JavaScript — TypeScript gives you autocomplete and type checking that meaningfully speeds up GUI development, but requires build tooling
  • Nesting complexity — chaining forms (a button in one form opens another form) works fine but requires careful Promise handling to avoid callback confusion

A solo developer building a small adventure map has very different constraints than someone building a full minigame lobby system with dynamic menus per player. The forms API can technically support both — but the architecture around it looks very different in each case.