It seems hard to write Editor Tools with Cocos Creator

I have read the instructions here:

it seems really confusing and not enough example to follow.

What if I want to create a custom button like this with editor only function ? I want to build the editor only tools for the game.

I really think cocos should have an editor class by default so that user only have to extend from the exposed api from that class for easy and simple usage. (something like ccclass)

you could see how straightforward and simple it is to write editor script with unity


i tried to follow the page here

it has error when compiling the code from automatic rendering

export function update(this: Selector<typeof $> & typeof methods, dump: any) {
    // Use ui-porp to auto-render, set the type of prop to dump
    // render pass in a dump data to be able to automatically render the corresponding interface
    // Auto-rendered interface can automatically commit data after modification

I managed to make it work by looking at other examples around the forum. I create a custom editor class that will call any method inside another component, without entering play mode.

It looks like this (ignore Test 2 button)

It will use the node id of a node, search for a component with a specific component name, then call a method inside that component.
If Node UUID is empty, it will use the current selection inside editor click.

Just incase someone stuck in this in the future, here are my steps

import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

export class MyCustomEditor extends Component {

    @property({tooltip : "hi"})
    componentName = '';

    methodName = '';

    nodeUUID = '';

First you need to create class inside your project folder
Then create an extention
Then create a link to custom class inside package.json

	"package_version": 2,
	"version": "1.0.0",
	"name": "custom-button",
	"description": "i18n:custom-button.description",
	"main": "./dist/main.js",
	"devDependencies": {
		"@types/node": "^16.0.1",
		"typescript": "^4.3.4"
	"author": "Cocos Creator",
	"editor": ">=3.8.1",
	"scripts": {
		"build": "tsc -b",
		"watch": "tsc -w"
	"contributions": {
		"inspector": {
			"section": {
				"node": {
					"MyCustomEditor" : "./dist/contributions/inspector/MyCustomEditor.js"

then create this custom class in a specific path. Mine is:

F:\Cocos Projects\TestCocosAsset\extensions\custom-button\src\contributions\inspector

Then run cmd command “npm run build” inside the extention folder.
Then reload the engine.

// @ts-nocheck
"use strict";

import { INode, Vec3 } from "../../../@types/packages/scene/@types/public";

type Selector<$> = { $: Record<keyof $, any | null> };

export const template = `

    <ui-prop type="dump" id="label1"></ui-prop>
    <ui-prop type="dump" id="label2"></ui-prop>
    <ui-prop type="dump" id="label3"></ui-prop>

<div style="display:flex;justify-content:center;align-items:center;margin-top:10px;">
    <ui-button id="btn1" style="height:24px;padding:0 16px;margin: 5px 10px">Call method</ui-button>
    <ui-button id="btn2" style="height:24px;padding:0 16px;margin: 5px 10px">Test 2</ui-button>


export const $ = {
  btn1: "#btn1",
  btn2: "#btn2",
  label1: "#label1",
  label2: "#label2",
  label3: "#label3",

type PanelThis = Selector<typeof $> & { dump: any };

export function update(this: PanelThis, dump: any) {
  // Cache 'dump' data, please hang on 'this', otherwise there may be problems when multiple opening
  this.dump = dump;
  // Pass the 'dump' data to the prop element that helped submit the data
  this.$.label1.dump = dump.value.componentName;

  this.$.label2.dump = dump.value.methodName;

  this.$.label3.dump = dump.value.nodeUUID;

  // if (typeof this.$.editor.render === "function") {
  //   this.$.editor.render(dump.value.label);
  // }

export function ready(this: Selector<typeof $> & any) {
  this.$.btn1.addEventListener("confirm", async () => {

    let selectedNode: string = '';
    if (this.dump.value.nodeUUID.value) {
      selectedNode = this.dump.value.nodeUUID.value;
    else {
      selectedNode = Editor.Selection.getSelected("node");

    const node: INode = await Editor.Message.request("scene", "query-node", selectedNode);
    //const node: INode = await Editor.Message.request("scene", "query-node", Editor.Selection.getSelected("node"));
    if (!node) {
      console.warn(`no node selected`);

    const ComponentName = this.dump.value.componentName.value;
    const methodName = this.dump.value.methodName.value;

      console.error("must enter component name!");

      console.error("must enter methodName name!");

    console.log(`Activate method: '${methodName}' inside component: '${ComponentName}'`);

    const index = node.__comps__.findIndex((v: any) => v.type === ComponentName);
    if (index === -1) {
      console.warn(`Cannot find ${ComponentName} component on this node`);

    const component = node.__comps__[index];

    const go = await Editor.Message.request("scene", "execute-component-method", {
      uuid: component.value.uuid.value,
      name: methodName,

  this.$.btn2.addEventListener("confirm", async () => {
    console.log("btn2 OK");

1 Like