blockbench-plugins
Provides APIs and structure for creating and debugging JavaScript plugins for the Blockbench 3D editor.
Install
mkdir -p .claude/skills/blockbench-plugins && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15347" && unzip -o skill.zip -d .claude/skills/blockbench-plugins && rm skill.zipInstalls to .claude/skills/blockbench-plugins
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Blockbench plugin/extension development for the 3D modeling tool. Use when creating, modifying, or debugging JavaScript plugins for Blockbench including actions, dialogs, panels, menus, toolbars, model manipulation, animation APIs, and custom formats/codecs. Triggers on Blockbench plugin, Blockbench extension, Blockbench API, BBPlugin, model editor plugin, or 3D modeling tool extension.Key capabilities
- →Create clickable commands with actions
- →Display forms and dialogs
- →Add sidebar panels with Vue components
- →Modify model elements with undo support
- →Implement custom import/export formats
- →Register plugins with metadata
How it works
This skill guides the development of Blockbench plugins by providing templates and API usage for creating UI elements, interacting with the model, and handling events.
Inputs & outputs
When to use blockbench-plugins
- →Creating a custom Blockbench tool
- →Building a UI plugin for modeling
- →Extending Blockbench functionality
About this skill
Blockbench Plugin Development
Overview
Blockbench runs on Electron (desktop) and as a web PWA, using THREE.js for 3D rendering and Vue 2 for reactive UI. Plugins have full access to global APIs within an isolated execution context.
Quick Reference
| Task | Approach |
|---|---|
| Create clickable command | new Action() - add to menus/toolbars |
| Show form/dialog | new Dialog() with form fields |
| Add sidebar panel | new Panel() with Vue component |
| Modify model elements | Use Undo.initEdit() → modify → Undo.finishEdit() |
| Custom import/export | new Codec() + new ModelFormat() |
| React to changes | Blockbench.on('event_name', callback) |
Plugin File Structure
plugins/
└── my_plugin/
├── my_plugin.js # Main file (required, ID must match filename)
├── about.md # Extended docs (optional)
└── icon.png # 48x48 icon (optional)
Plugin Registration Template
(function() {
// Store references for cleanup
let myAction, myPanel, myDialog, eventCallback;
Plugin.register('my_plugin', {
title: 'My Plugin',
author: 'Author Name',
description: 'Short description',
icon: 'extension', // Material icon name
version: '1.0.0',
variant: 'both', // 'desktop', 'web', or 'both'
min_version: '4.8.0',
tags: ['Utility'],
onload() {
// Initialize all components here
},
onunload() {
// CRITICAL: Delete ALL components here
},
oninstall() {
Blockbench.showQuickMessage('Installed!');
}
});
})();
Actions
Actions are clickable commands for menus, toolbars, and keybindings.
myAction = new Action('my_action_id', {
name: 'Action Name',
description: 'Tooltip text',
icon: 'star',
category: 'edit', // For keybind settings
condition: () => Cube.selected.length > 0,
keybind: new Keybind({ key: 'k', ctrl: true }),
click(event) {
// Action logic
}
});
MenuBar.addAction(myAction, 'filter'); // Add to Filter menu
// Cleanup
myAction.delete();
Menu locations: 'file', 'edit', 'transform', 'filter', 'tools', 'view', 'help'
Action variants:
// Toggle (on/off state)
new Toggle('toggle_id', {
name: 'Feature',
default: false,
onChange(value) { /* handle */ }
});
// Tool (viewport interaction)
new Tool('tool_id', {
name: 'My Tool',
cursor: 'crosshair',
onCanvasClick(data) { /* handle */ },
onCanvasDrag(data) { /* handle */ }
});
Dialogs
myDialog = new Dialog({
id: 'my_dialog',
title: 'Dialog Title',
width: 540,
form: {
name: { label: 'Name', type: 'text', value: 'default' },
count: { label: 'Count', type: 'number', value: 10, min: 1, max: 100 },
enabled: { label: 'Enabled', type: 'checkbox', value: true },
mode: {
label: 'Mode',
type: 'select',
options: { a: 'Option A', b: 'Option B' },
value: 'a'
},
color: { label: 'Color', type: 'color', value: '#ff0000' },
// Conditional field
advanced: {
label: 'Advanced',
type: 'text',
condition: (form) => form.enabled
}
},
onConfirm(formData) {
console.log(formData.name, formData.count);
this.hide();
}
});
myDialog.show();
// Quick dialogs
Blockbench.textPrompt('Enter Value', 'default', (text) => { });
Blockbench.showMessageBox({ title: 'Alert', message: 'Text', buttons: ['OK'] });
Panels
Panels appear in sidebars with Vue components.
myPanel = new Panel('my_panel', {
name: 'My Panel',
icon: 'dashboard',
condition: () => Format.animation_mode,
default_position: {
slot: 'left_bar', // 'left_bar', 'right_bar', 'bottom'
height: 300
},
component: {
template: `
<div>
<h3>{{ title }}</h3>
<ul><li v-for="item in items">{{ item.name }}</li></ul>
<button @click="refresh">Refresh</button>
</div>
`,
data() {
return { title: 'Items', items: [] };
},
methods: {
refresh() {
this.items = Cube.selected.map(c => ({ name: c.name }));
}
}
}
});
// Cleanup
myPanel.delete();
Model Manipulation (with Undo)
CRITICAL: Always wrap modifications in Undo for user reversibility.
// Start tracking
Undo.initEdit({ elements: Cube.selected });
// Modify elements
Cube.selected.forEach(cube => {
cube.from[0] += 5;
cube.to[1] = 20;
cube.rotation[1] = 45;
});
// Update view
Canvas.updateView({
elements: Cube.selected,
element_aspects: { geometry: true, transform: true }
});
// Commit
Undo.finishEdit('Move cubes');
Creating Elements
// Cube
let cube = new Cube({
name: 'my_cube',
from: [0, 0, 0],
to: [16, 16, 16],
origin: [8, 8, 8],
rotation: [0, 45, 0]
}).init();
cube.addTo(Group.selected[0]); // Add to group
// Group (bone)
let group = new Group({
name: 'bone_arm',
origin: [0, 12, 0]
}).init();
group.addTo(); // Add to root
// Texture
let texture = new Texture({ name: 'my_texture' });
texture.fromPath('/path/to/file.png'); // or .fromDataURL()
texture.add(true); // true = add to undo
Global Collections
| Collection | Description |
|---|---|
Cube.all / Cube.selected | All cubes / selected cubes |
Group.all / Group.selected | All groups / selected groups |
Mesh.all / Mesh.selected | All meshes / selected meshes |
Texture.all / Texture.selected | All textures / selected |
Animation.all / Animation.selected | All animations / selected |
Outliner.elements | All outliner elements |
Event System
// Subscribe
eventCallback = (data) => { /* handle */ };
Blockbench.on('update_selection', eventCallback);
// Unsubscribe (use SAME function reference)
Blockbench.removeListener('update_selection', eventCallback);
Common events: update_selection, select_project, new_project, load_project, save_project, close_project, add_cube, add_group, add_texture, add_animation, select_animation, render_frame, undo, redo, finish_edit
See references/events.md for full list.
Custom Menus
let menu = new Menu([
'existing_action_id',
myAction,
'_', // Separator
{
name: 'Custom Item',
icon: 'star',
click() { /* handle */ }
},
{
name: 'Submenu',
children: [ /* more items */ ]
}
]);
menu.open(event); // Open at mouse position
Format and Codec (Import/Export)
const myCodec = new Codec('my_codec', {
name: 'My Format',
extension: 'mymodel',
compile(options) {
// Model → file content
let data = { bones: [] };
Group.all.forEach(g => {
data.bones.push({
name: g.name,
pivot: g.origin,
cubes: g.children.filter(c => c instanceof Cube).map(c => ({
from: c.from, to: c.to
}))
});
});
return JSON.stringify(data, null, 2);
},
parse(content, path) {
// File content → model
let data = JSON.parse(content);
newProject(myFormat);
data.bones.forEach(b => {
let group = new Group({ name: b.name, origin: b.pivot }).init();
b.cubes.forEach(c => {
new Cube({ from: c.from, to: c.to }).init().addTo(group);
});
});
Canvas.updateAll();
}
});
const myFormat = new ModelFormat('my_format', {
id: 'my_format',
name: 'My Format',
icon: 'icon_name',
codec: myCodec,
box_uv: true,
bone_rig: true,
animation_mode: true
});
// Cleanup
myFormat.delete();
myCodec.delete();
Condition Patterns
// Function
condition: () => Format.id === 'bedrock' && Cube.selected.length > 0
// Object (combined with AND)
condition: {
formats: ['bedrock', 'java_block'],
modes: ['edit', 'paint'],
project: true,
selected: { cube: 1 },
method: () => customCheck()
}
Code Style Conventions
| Type | Convention | Examples |
|---|---|---|
| Classes | PascalCase | OutlinerElement, Animation |
| Methods | camelCase | updateTransform, getSelectedFaces |
| Properties | snake_case | data_points, uv_offset |
| Event/Action IDs | snake_case | select_project, my_action |
Critical Cleanup Pattern
MUST delete all components in onunload() to prevent memory leaks.
let action, panel, dialog, toolbar, format, codec, eventCallback, css;
Plugin.register('my_plugin', {
onload() {
action = new Action('id', { /* ... */ });
panel = new Panel('id', { /* ... */ });
format = new ModelFormat('id', { /* ... */ });
codec = new Codec('id', { /* ... */ });
eventCallback = (data) => { };
Blockbench.on('update_selection', eventCallback);
css = Blockbench.addCSS('.my-class { color: blue; }');
},
onunload() {
action.delete();
panel.delete();
format.delete();
codec.delete();
Blockbench.removeListener('update_selection', eventCallback);
css.delete();
}
});
Anti-Patterns
// ❌ No stored reference = memory leak
onload() { new Action('leaked', { }); }
// ❌ No Undo = users can't reverse
Cube.selected.forEach(c => c.from[0] += 1);
// ❌ Bundling THREE.js (already global)
import * as THREE fro
---
*Content truncated.*
When not to use it
- →When developing for a 3D modeling tool other than Blockbench
- →When the task does not involve JavaScript plugin development
- →When the goal is to bundle built-in libraries like THREE.js or Vue
Limitations
- →Plugins must be written in JavaScript
- →Plugins run within Blockbench's Electron or web PWA environment
- →All components must be deleted in onunload() to prevent memory leaks
How it compares
This skill provides specific Blockbench API calls and structural templates for plugin development, unlike general JavaScript programming.
Compared to similar skills
blockbench-plugins side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| blockbench-plugins (this skill) | 0 | 3mo | Review | Intermediate |
| obj-exporter | 3 | 6mo | No flags | Intermediate |
| zustand | 113 | 2mo | No flags | Intermediate |
| turborepo | 61 | 2mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
obj-exporter
benchflow-ai
Three.js OBJExporter utility for exporting 3D geometry to Wavefront OBJ format. Use when converting Three.js scenes, meshes, or geometries to OBJ files for use in other 3D software like Blender, Maya, or MeshLab.
zustand
lobehub
Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.
turborepo
vercel
Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries. Use when user: configures tasks/workflows/pipelines, creates packages, sets up monorepo, shares code between apps, runs changed/affected packages, debugs cache, or has apps/packages directories.
vitest
antfu
Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.
typescript-write
metabase
Write TypeScript and JavaScript code following Metabase coding standards and best practices. Use when developing or refactoring TypeScript/JavaScript code.
react
lobehub
React component development guide. Use when working with React components (.tsx files), creating UI, using @lobehub/ui components, implementing routing, or building frontend features. Triggers on React component creation, modification, layout implementation, or navigation tasks.