Custom Element Behaving Like bpmn:ServiceTask with Custom Properties Panel

First and foremost, thanks to the Bpmn.io team for all your work :heart:

I’ve been working on creating a custom BPMN element that behaves exactly like a bpmn:ServiceTask, but with some differences in the properties panel — specifically, I’d like to change the header, icon, and introduce some custom properties.

I know this question has come up a few times before, so I apologize if this overlaps with previous discussions. However, I’ve come across several suggested solutions and thought it would be valuable to explore the nuances between them.

Here are the different approaches I’m currently looking into:

  1. Add the task to the moddle as a superClass of ServiceTask.
    Technically, this seems like the most accurate solution to capture the use case.
    However, when I create a shape (elementFactory.createShape({ type: "custom:EmailTask" });) and then export my xml, I get the error "failed to import <bpmn:SequenceFlow id="Flow_1" /> Error: <bpmn:SequenceFlow id="Flow_2" />#source Ref not specified".
    The task does not appear in exported XML.
    Am I missing something?
types: [
        {
            name: "EmailTask",
            superClass: ["bpmn:ServiceTask"],
            properties: [
                {
                    name: "title",
                    isAttr: true,
                    type: "String",
                }
           ]
       }]
  1. Customize the properties panel via headerProperty.
    As in this example: Properties panel element title - #3 by Sun95
    This is an interesting approach, but I’m wondering if it fully captures the use case from a technical perspective. As I understand it, I’d still need to define something in the moddle like the example below (using “extends”). My question is, doesn’t it feel a bit odd to have the moddle definition, yet still see my task registered as a <bpmn:ServiceTask> in the XML?
types: [
        {
            name: "EmailTask",
            extends: ["bpmn:ServiceTask"],
            properties: [
                {
                    name: "title",
                    isAttr: true,
                    type: "String",
                }
           ]
       }]

By the way, I second others’ comments that it would be really useful to have this explanation in vanilla JavaScript or something we can easily adapt to other frameworks.

  1. I found a 1-line fix that correctly updated the properties panel header, but seems like a hack:
const shape = elementFactory.createShape({ type: "bpmn:ServiceTask" });
shape.type= "custom:EmailTask"

Can you advise on which of these options are best?

Many thanks!