Add import or export actions to the File menu
Register custom File menu actions and handle file selection, asynchronous work and export errors.
We will add two menu entries: one reads a local project file, and the other saves a model’s JSON export. The editor provides a place in the menu, availability controls and support for awaiting the handler. The plugin handles file selection, input validation and downloading the result.
First, make sure you understand the difference between public and internal entry points. For testing, prepare a copy of a small project without real block structures.
Workflow and data formats
Download the complete file-actions.js plugin. After installing and restarting, these entries appear:
- File → Import → Add content from a project file adds the contents of a
.bdenginefile to the current project. - File → Export → Download the JSON model export obtains Minecraft export data and saves it as
.json.
These are two different data flows. The downloaded JSON export is not a project file and is not intended to be imported back through this entry. The example does not register a new extension with the editor’s file-opening system or add drag-and-drop handling.
Public operations are called through editorAPI. To check that the user has not switched
projects or modes while choosing a file, the example reads editor.objects and mode flags.
This is an internal dependency that must be checked separately when the editor is updated.
Register a menu entry
Import and export use the same options structure:
const action = editorAPI.addButtonImportMenu({ id: 'my-plugin.import-project', title: 'Add content from a project file', icon: 'icon-file-input', onClick: async (event) => { await importProject(); }});importProject represents your handler here; the complete version is in the downloadable file.
id, a nonempty title and an onClick function are required. icon is a single icon CSS class
and can be omitted. The optional enabled property sets the initial availability.
Give id a prefix unique to your plugin. Registering the same id again in the same section
updates the existing entry instead of creating another. The same id in import and export
belongs to two different sections.
The entry appears after the built-in actions and a divider. Its label is treated as plain text. See the contracts for adding an import action and adding an export action.
Availability and asynchronous work
The returned handle supports two methods:
action.setEnabled(false);action.setEnabled(true);action.remove();setEnabled requires a boolean and returns the same handle. remove() removes the entry
and returns true; calling it again returns false. Removing an entry does not cancel
file reading or other asynchronous work that is already running.
While onClick is running, the editor prevents another invocation of that entry.
For this protection to cover asynchronous work, return the Promise or use async and await:
// Correct: the menu waits for the operation to finish.onClick: () => importProject()
// Incorrect: the Promise is not returned, so the menu considers the handler finished.onClick: () => { importProject(); }In the complete example, a shared busy flag and setEnabled(false) temporarily disable
both entries so the plugin’s import and export operations do not overlap. finally restores
availability after success, cancellation or an error. Other editor commands remain available.
The menu invokes the handler before its first await. Open the file picker immediately
inside the handler: waiting for a network request first can lose the user activation
the browser requires to open the picker.
Handle the file yourself
The example creates a hidden input with type="file" and accept=".bdengine", then calls click().
The change event provides the selected file; cancel finishes the operation without one.
The temporary element is removed after either event. Cancellation is not an error.
accept filters the picker; it does not validate file contents. The handler also checks
the extension, a nonzero size and a 10 MB limit. That limit belongs to the example plugin,
not to a promised BDEngine limit. The file is read as bytes:
const bytes = new Uint8Array(await file.arrayBuffer());await editorAPI.mergeContent(bytes, true);BDEngine’s importer validates the project itself. Renaming arbitrary JSON to .bdengine
does not make it a valid project. The example targets ordinary modeling mode; real block
structures cannot be added this way. Files containing custom models require the complete
project container, not just scene text extracted from it.
Handle arrayBuffer exceptions, JSON parsing errors when reading JSON yourself, missing
export results and cancellation. The example shows errors to the user and writes them
to the console. finally restores the menu state.
Get or add content
mergeContent(content, decode) adds content to the current project while keeping its existing objects.
With decode: true, it reads a container or a supported encoded representation.
With decode: false, it expects a BDEngine scene JSON string, not a parsed object or SNBT:
await editorAPI.mergeContent(JSON.stringify(projectScene), false);In this snippet, projectScene is already prepared BDEngine scene data.
For its structure and limits, see working with project data.
The editorAPI.mergeContent wrapper does not pass through the internal importer’s boolean result.
A fulfilled await does not prove that objects were added: the editor can handle an error
itself and display a message. Inspect the scene result; do not show an unconditional
“Import successful” message just because the Promise fulfilled.
Minecraft export data uses a different method:
const content = await editorAPI.exportToJSON(false);if (!content) throw new Error('No export was returned.');const text = JSON.stringify(content, null, 2);false turns off the full-export request in the export window’s settings, but it does not
take the editor out of Animator: the result in Animator can still have type: "full".
The complete example therefore requires ordinary modeling mode before exporting and checks
content.type === 'modelOnly' afterwards. If the mode or result type is unsuitable,
it shows a message and does not download a file.
The method uses the editor’s export window, changes its type and selected version, and shows it;
it is not background project serialization. The complete example saves text using a Blob,
a temporary URL and a link with download, then releases the URL.
The result has no HTTP content wrapper, and the [BDESERVERTAG] marker is not replaced
with a tag value. For failure conditions and parameters, see
getting export data.
Test the menu and invalid input
- Find both entries after restarting. Make sure opening the menu repeatedly does not create duplicates.
- Cancel the file picker. Both entries should become available again.
- Try an empty file, the wrong extension and a file larger than 10 MB. The plugin should report the problem before calling the importer.
- Add a small model from a real
.bdenginefile. Existing objects should remain; check the new ones in the object tree, rather than relying on an empty error console. - Switch projects through another available editor action while choosing a file. The old read operation must not begin importing into the new project root.
- In ordinary modeling mode, download the export, open it as JSON and check
type: "modelOnly"andpassengers. Then run the entry in Animator: the example should ask you to switch to modeling without starting an export. Unprepared textures or export errors must not leave the menu disabled. - In your own version, keep the handle and test
remove(): only your entry should disappear.
The cancel event and downloads depend on the browser’s file interface.
Before distributing the plugin, test both actions in the target web editor and BDEngine App.