Wiki 9Minecraft Wiki 9Minecraft

Dynamic Content Generation in JSON UI

Spawning, sizing, and binding runtime UI elements using factories and grids.

4Sections
3Code examples
3Known errors

Static JSON UI definitions cannot know ahead of time how many players are in a world, how many status effects an entity has, or how many buttons a server form will send. To handle variable-length data, Bedrock's UI engine provides two runtime generation mechanisms: the factory property and the grid element. You reach for these tools whenever your interface must grow, shrink, or populate its child controls based on hardcoded engine collections or server-driven forms.

Core Generation Mechanisms

Because Bedrock's JSON UI lacks a procedural scripting layer inside the resource pack, you cannot write a traditional loop to create elements. Instead, you declare a template control and instruct a parent container to stamp out copies of that template at runtime. The engine exposes two distinct structures for this: grids and factories.

Grids are designed for two-dimensional or one-dimensional arrangements of uniform items, such as inventory slots, scoreboard rows, or recipe book pages. Factories are more flexible; they are commonly attached to stack panels to instantiate heterogeneous or sequential elements, like incoming chat messages or the buttons inside an Action Form. In both systems, the C++ engine dictates the total count of items via a named collection, and each spawned child inherits a collection index so its bindings know which slice of data to read.

PropertyWhat it doesNotes
factoryDefines a name, a template control, and optional variables for spawning children on demand.Most commonly used inside a stack_panel or a root panel.
collection_nameLinks a grid or collection panel to a C++ data array provided by the current screen context.Cannot be invented arbitrarily; must match a collection the screen already exposes.
grid_item_templatePoints to the UI element definition that the grid will clone for every cell.Takes a namespace-qualified or local element reference.
grid_dimension_bindingBinds the width and height of a grid (in rows and columns) to an engine variable.Used when the C++ code exposes a vec2 dimension property.
grid_rescaling_typeTells the grid to automatically wrap items based on available pixel width or height.Accepts 'horizontal', 'vertical', or 'none'.

Using the Factory Property

A factory is declared as an object inside a container element, typically a stack_panel so that generated children automatically stack vertically or horizontally without overlapping. Inside the factory block, you must provide a name that matches an internal engine factory identifier, and a control_id (or a map of control_ids) that tells the factory which JSON element to instantiate.

When the game triggers the factory—for example, when a script calls ActionFormData.show() and the server_form screen opens—the container clears or appends instances of your template. If the factory is backed by a collection, you also set collection_name on the parent container so each spawned child can bind to collection-specific properties like button text or icons.

RP/ui/server_form.json
{
  "namespace": "server_form",
  "dynamic_button_template": {
    "type": "panel",
    "size": ["100%", 32],
    "controls": [
      {
        "button@common_buttons.light_text_button": {
          "size": ["100% - 4px", "100% - 4px"],
          "$pressed_button_name": "button.form_button_click",
          "$button_text": "#form_button_text",
          "$button_text_binding_type": "collection",
          "$button_text_grid_collection_name": "form_buttons",
          "bindings": [
            {
              "binding_type": "collection_details",
              "binding_collection_name": "form_buttons"
            }
          ]
        }
      }
    ]
  },
  "button_list_panel": {
    "type": "stack_panel",
    "orientation": "vertical",
    "size": ["100%", "100%c"],
    "collection_name": "form_buttons",
    "factory": {
      "name": "buttons",
      "control_id": "server_form.dynamic_button_template"
    }
  }
}

Dynamic Grids and Collection Bindings

When your layout requires a strict row-and-column structure or needs to bind directly to an inventory array, you use a control of type grid. Instead of a factory block, a grid uses grid_item_template to specify the child element and collection_name to specify the data source. If the number of items is variable, you replace the static grid_dimensions array with either grid_dimension_binding or grid_rescaling_type.

Every element cloned by a grid or a collection-backed factory needs to know which index it represents. In your child template's bindings array, you set binding_type to collection and binding_collection_name to match the parent's collection_name. If a child control is a complex template (like a standard button from common_buttons), you also include a binding of type collection_details so the button's press event sends the correct index back to the game engine.

RP/ui/hud_screen.json
{
  "namespace": "hud",
  "status_effect_slot": {
    "type": "panel",
    "size": [24, 24],
    "controls": [
      {
        "icon": {
          "type": "image",
          "size": [18, 18],
          "bindings": [
            {
              "binding_name": "#mob_effect_texture",
              "binding_name_override": "#texture",
              "binding_type": "collection",
              "binding_collection_name": "mob_effects_collection"
            }
          ]
        }
      }
    ]
  },
  "effect_grid": {
    "type": "grid",
    "size": [120, "default"],
    "grid_item_template": "hud.status_effect_slot",
    "grid_rescaling_type": "horizontal",
    "collection_name": "mob_effects_collection",
    "bindings": [
      {
        "binding_name": "#mob_effects_grid_size",
        "binding_type": "global"
      }
    ]
  }
}

Feeding Data from Behaviour Packs

A frequent point of confusion for add-on creators is trying to invent a custom collection_name in JSON UI and populate it from a script. The UI engine does not allow arbitrary collections to be created from JSON alone; the C++ screen context must own the collection. To drive dynamic UI from a behaviour pack, you piggyback on existing engine collections and update them via the @minecraft/server or @minecraft/server-ui modules.

The two cleanest pathways are Server Forms and the Scoreboard. When you build an ActionFormData in script and append buttons inside a loop, the client's server_form screen populates the form_buttons collection with one entry per button. Alternatively, for persistent on-screen HUD elements without cursor lock, you can pass variable strings through player.onScreenDisplay.setTitle() and parse them on the HUD screen using factory controls bound to the title text, or read entries from the scoreboard_players_collection.

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

export function showOnlinePlayersMenu(viewer) {
  const form = new ActionFormData();
  form.title("Active Players");

  const players = world.getAllPlayers();
  for (const target of players) {
    form.button(target.name, "textures/ui/Friend2");
  }

  form.show(viewer).then((response) => {
    if (response.canceled) return;
    const selectedPlayer = players[response.selection];
    if (selectedPlayer) {
      viewer.sendMessage(`You selected: ${selectedPlayer.name}`);
    }
  });
}

Which versions this applies to

Applies to Minecraft Bedrock 1.16.100 and newer (including current 1.20 and 1.21 releases). JSON UI factories and grids are stable core features and require no experimental toggles. Using @minecraft/server-ui to populate form collections is also stable.

When it does not work

What you seeWhyThe fix
Dynamic elements spawn on top of each other in a single pile instead of forming a list.You placed the factory property inside a standard panel instead of a stack_panel. A standard panel positions all children at its anchor point (usually the center) unless manually offset.Change the parent container's type from "panel" to "stack_panel", set "orientation": "vertical" (or "horizontal"), and set the axis size to "100%c" so the container resizes to fit its generated children.
Every generated item in a grid or factory displays the exact same text or texture (usually from the first slot), or displays raw binding strings like '#form_button_text'.The child template's binding is missing "binding_type": "collection" or its "binding_collection_name" does not match the parent's "collection_name". Without those keys, the engine looks for a global binding or defaults to index 0.Add "binding_type": "collection" and the exact "binding_collection_name" string to every binding object inside the template control.
Clicking any generated button in a custom form always triggers the action for the first button, or does nothing at all.The button control inside your template does not know which collection index it belongs to when firing its press event back to the C++ screen controller.Include an additional binding object in the button's bindings array containing "binding_type": "collection_details" and "binding_collection_name" set to the parent collection.

Reference, straight from the game files

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