Skip to content

Events and lifecycle

BDEngine startup, mode, and Share Party events: payloads, safe subscriptions, listener cleanup, and editorAPI.animator methods.

Listen for editor events with window.addEventListener(). editorAPI does not provide a separate on() / off() event bus. Remove listeners with window.removeEventListener() using the same function, or use an AbortController.

bde:started

bde:started is a plain Event without detail. The editor dispatches it after editorAPI.initGUI(), when the interface API, including editorAPI.headPaint, is available.

Installed plugins run before this event. Installing a new plugin saves its file for execution after restarting the editor. If you run code manually from the console in an editor that is already ready, the event has already occurred and will not be replayed. Use both branches below to support either way of running the example:

(() => {
function init() {
window.editorAPI.addButtonTool('icon-info', () => {
console.log('[my-plugin] The editor is ready');
});
}
if (window.editorAPI?.headPaint) {
init();
} else {
window.addEventListener('bde:started', init, { once: true });
}
})();

Here, the presence of headPaint indicates that GUI initialization has finished. Checking only window.editorAPI is insufficient: the main object is created before the interface.

The event means the interface is ready for the plugin; it is not a project-open event. Some startup content loading and subsequent actions happen later. Do not assume that a project from a link is already open or that the last session has been restored. This API group does not provide a separate public event for every project load.

bde:change-mode

A CustomEvent whose event.detail is a string containing the current mode:

Value Mode
editor Model editing.
animation Animator.
sound Sounds.
structure Structures.
func Functions and interactivity.
function onModeChange(event) {
const mode = event.detail;
console.log('[my-plugin] Current mode:', mode);
}
window.addEventListener('bde:change-mode', onModeChange);
// When cleaning up your code:
// window.removeEventListener('bde:change-mode', onModeChange);

Use animation, not animator: the interface label and event value differ. There are no detail.mode or detail.previousMode properties. The event is dispatched at the end of the main mode update; opening HeadPaint or the screenshot window does not add new string values to this list.

Subscribing does not automatically report the current state. If you need it immediately, you can read window.editor.currentMode; this accesses an internal editor object, rather than a separate stable editorAPI getter. Handle subsequent events instead of relying on a stored assumption about the mode.

bde:server-connected

A CustomEvent without additional data: event.detail === null. It is dispatched when Share Party has received room entry confirmation (welcome) and processed the initial participant list.

This means more than opening a WebSocket: the transport can open before the room entry is confirmed. Reconnecting can dispatch the event again. It does not mean that every participant’s scene has finished loading, and it does not provide a room or user ID.

bde:server-disconnected

A CustomEvent with detail === null. It occurs when a Share Party connection closes, when it is explicitly disconnected, and when the editor forces a disconnect before reconnecting.

The event does not include a close code, reason, or retry decision. Your handler must tolerate repeated notifications and a disconnect without a preceding successful room entry. For example, hiding a connection indicator should also be safe when the indicator is already hidden.

bde:server-error

A CustomEvent with detail === null. The current implementation dispatches it on an error event from the active Share Party WebSocket. The original error object is not passed in detail. The editor may then dispatch bde:server-disconnected and begin connection recovery.

Do not use this as a general handler for all editor errors. Rejected room entry and other situations may be handled without bde:server-error.

All three bde:server-* events belong to Share Party. They are unrelated to the HTTP Server API used to download exports and do not describe a Minecraft Live Link connection.

HeadPaint and Minecraft Live Link

The bde:headPaint:started, bde:headPaint:paint, bde:headPaint:ended, bde:headPaint:fillStarted, and bde:headPaint:fillEnded events carry information about the tool, color, and painting operation. Their payloads and custom tool limitations are documented in the HeadPaint reference.

The internal Minecraft Live Link client dispatches two more CustomEvent events. This is a separate subsystem, for which editorAPI does not provide a dedicated connection-management interface:

Event event.detail
bde:minecraft-live-link:status A snapshot from the client’s status(), including loaded, connecting, connected, ready, state, version, lastError, playerPosition, chatLog, sync, and worldPreview.
bde:minecraft-live-link:chat A log entry with type, message, and time; player messages may also contain sender and raw.

A chat entry’s time is the result of Date.now(), in milliseconds. State fields may be null, while the details of sync, worldPreview, and the raw message depend on internal subsystems. Account for possible changes between editor versions when using them. Check the specific fields your plugin needs, and do not treat connected and ready as interchangeable.

editorAPI.animator methods

The public API has three synchronous methods for the current animation, sound, and visible timeline length. Call them after the interface is initialized. They return null if the required subsystem or current entry is unavailable.

Method Arguments and return value
setCurrentAnimationName(name) A nonempty string. Trims surrounding whitespace, replaces whitespace sequences with _, and keeps Latin letters, digits, _, and parentheses. If the name matches another name case-insensitively, adds _1, _2, and so on. Returns the resulting name, or null if the name is unusable.
setCurrentSoundBPM(value) Despite its name, accepts a step length in ticks, not BPM. Uses parseInt(value, 10) and clamps the result to 1-3. Returns the accepted number or null.
setLength(seconds, resetCurrent = false) Changes the visible timeline length in seconds. Uses parseInt(seconds, 10); the lower bound is the greater of 10 seconds and the existing animation length, and the upper bound is 3600. Returns the accepted value or null. Setting resetCurrent to true moves playback to the beginning.

For setCurrentSoundBPM(), the interface calculates BPM as 60000 / (tick * 50 * 4): 1 corresponds to 300 BPM, 2 to 150, and 3 to 100. Passing 120 sets 3, not 120 BPM.

const actualName = editorAPI.animator.setCurrentAnimationName('door open');
const actualTick = editorAPI.animator.setCurrentSoundBPM(2);
const visibleSeconds = editorAPI.animator.setLength(30);

Changes to the animation name and sound step are recorded in Undo history. Setting a value that is already in effect does not add a new command. setLength() changes the displayed timeline range without trimming keys or setting the animation resource’s duration. This setting does not create an Undo command. Renaming can throw an error if the editor cannot find a unique name within 1000 attempts.

Adding and removing listeners

The loader has no official bde:plugin-unload event or automatically called dispose() method. The plugin must organize its own cleanup. Removing its button or script from the DOM does not remove window listeners, timers, or other resources it created.

To support rerunning your own code, you can store one cleanup function under a uniquely named global key:

(() => {
const cleanupKey = '__samplePluginModeCleanup';
window[cleanupKey]?.();
const subscriptions = new AbortController();
window[cleanupKey] = () => subscriptions.abort();
function init() {
window.addEventListener('bde:change-mode', event => {
console.log('[sample-plugin] Mode:', event.detail);
}, { signal: subscriptions.signal });
}
if (window.editorAPI?.headPaint) init();
else window.addEventListener('bde:started', init, {
once: true,
signal: subscriptions.signal,
});
})();

The global key and cleanup call belong to this example; they are not built-in BDEngine hooks. abort() removes only the listeners registered with that signal. Clean up timers, DOM elements, windows, external connections, and graphics resources separately.

For checks covering reinstallation and plugin cleanup, see Debugging and cleanup.