Skip to main content
Version: 2.4

Task Templates

Task Templates help you configure BPMN tasks quickly and consistently in tSM Process Designer. Instead of setting many technical properties by hand (topics, mappings, listeners, script parameters), you select a template and fill in a guided form.

A Task Template is design-time only: it helps you model faster, but it does not introduce a new runtime concept. After applying a template, you still end up with a standard BPMN element — fully inspectable, exportable, and deployable.


Why Task Templates matter

Many BPMN tasks require precise technical configuration to work correctly:

Configuring these by hand is error-prone and requires knowledge of the underlying implementation. Task Templates solve this by packaging the correct configuration into a reusable, form-driven experience.


How you use Task Templates

Applying a template to an existing element

  1. Select an existing BPMN element (Service Task, User Task, Receive Task, …).
  2. In the Properties panel, choose a Task Template.
  3. When the template is applied, all template-managed defaults are written into the element — they overwrite any previous values for the properties the template manages.
  4. The standard properties panel is extended with the template's form — use it to edit the template-specific properties.
  5. For properties that map to standard BPMN/Camunda attributes (e.g. task name, candidate groups, due date), you can also edit them using the standard tools in the properties panel. However, be aware that properties bound via from (form-driven bindings) will be overwritten by the template the next time you change the template form. Only properties set via set (one-time static bindings) or properties not managed by the template at all will retain your manual edits.

Favorite templates in the palette

You can mark frequently used templates as Favorites. Favorite templates appear directly in the task palette, so you can drag them into the diagram without opening the template catalog first.

Editing a template-configured task

If a task is already bound to a template, selecting it shows the template form in the properties panel. You can change the template-specific properties at any time. tSM reads the current values from the BPMN element and shows them in the form (round-trip editing).


What happens when you apply a template

When you apply a Task Template, tSM writes configuration into the selected BPMN element, including:

  • standard BPMN fields (e.g. element name)
  • extension attributes (e.g. external task type/topic, async flags, delegate expressions)
  • extension elements (e.g. input/output mappings, execution listeners, script binding fields)
  • optional tSM-specific metadata (e.g. color, description)

All template defaults are applied immediately. Properties the template does not manage remain unchanged.

The result is still plain BPMN XML. You can inspect it, export it, deploy it, and edit it manually.


Template binding and versioning

Binding to the element

When a template is applied, the BPMN element is bound to the template via the tsm:templateCode attribute. This allows tSM to:

  • recognize which template was used to configure the element
  • show the correct template form when the element is selected
  • support round-trip editing (read values from BPMN back into the form)

Change propagation

When a template definition is updated, existing process instances are not modified automatically. The BPMN elements retain the configuration that was written when the template was last applied.

Future improvement

In future tSM releases, a dedicated tool is planned to:

  • search all processes containing tasks with a specific tsm:templateCode
  • show differences between the current template version and the applied configuration
  • allow selective re-application of the updated template
  • automatically create a new version of the affected process and deploy it

Currently, you can search for all process tasks with a given template ID and re-apply the template manually to update them.


Template types

Templates are not limited to one execution style. Common types include:

Preconfigured Task

A template for a complex BPMN element (e.g. a User Task with listeners, due dates, documentation, or tSM metadata). It does not require scripts or external workers — just a reusable configuration pattern.

Script-backed task (Script Binding)

A template that configures a task and/or listeners to run an existing tSM script using Script Binding. The template provides a form for script parameters and writes the scriptCode + paramsJson fields into the BPMN element.

External Task

A template that configures a Service Task as an External Task, delegating execution to an external worker. The template sets the correct topic and configures input mapping variables expected by the worker. Because external workers define a strict contract, templates are the recommended way to configure them.

Kafka Task

A template that configures a Receive Task as a Kafka Task, setting up the request/response topics, payload, and the Kafka listener delegate. Like External Tasks, Kafka consumers expect a specific message contract, making templates the natural fit.

Project-specific patterns

Templates can encode any repeatable integration pattern — tSM Connector calls (REST/SOAP/TCP), multi-step configurations, or project-specific combinations.


Defining a Task Template

A Task Template definition is a JSON object with the following structure:

TaskTemplate properties

PropertyTypeRequiredDescription
idstringyesUnique identifier (UUID)
codestringyesUnique code used as tsm:templateCode on BPMN elements
namestringyesHuman-readable name shown in the template catalog
descriptionstringnoDescription shown in the catalog
versionstringnoSemantic version (e.g. "1.0.0")
appliesTostring[]noBPMN element types this template can be applied to (e.g. ["userTask"])
formCodestringnoCode of a tSM Form (JSON Schema-based) displayed in the properties panel when the template is active
bindingsTemplateBinding[]noArray of bindings that map form fields to BPMN storage locations (see below)
colorstringnoColor associated with the template (e.g. hex code "#FF5733", max 128 chars)
iconstringnoIcon identifier or URL for the template (max 2048 chars)
showInPalettebooleannoWhether this template is displayed in the modeler palette (default false)
dataTagsstring[]noTags / categories for filtering in the catalog
configobjectnoArbitrary configuration metadata
localizationDataobjectnoLocalized strings for name, description, etc.
validityFromstringnoISO date — template is valid from this date
validityTostringnoISO date — template is valid until this date

Configuration form

When formCode is set, the properties panel shows the referenced tSM Form below the standard fields. The form defines * what the user edits* — form field values are then mapped to BPMN storage locations via bindings.

  • The form is a standard tSM Form (JSON Schema-based, rendered by fluent-forms).
  • Form fields may accept SpEL expressions in the form #{...}, evaluated at runtime.

Bindings

Bindings are the core of the template system. Each binding maps a value to a BPMN storage location. Bindings work in both directions:

  • Write (apply): when you fill in the form or select the template, values are written into the BPMN element.
  • Read (extract): when you open the template form on an existing element, values are read back from the BPMN into the form.

Value sources

Every binding that carries a value uses one of three sources:

SourceSyntaxBehavior
set"set": "value"Static value, written once when the template is applied. Not updated on form changes.
from"from": "/path"Dynamic value read from the template form. Updated every time the form changes. Path uses JSON Pointer-like syntax: "/field" or "/nested/field".
setJson"setJson": { ... }JSON object serialized to a string. Available for camundaField and delegate field values (scriptCode, params, fields[].value).

set and from are mutually exclusive on the same binding — use one or the other.

SpEL expressions in set values

Static set values may contain #{...} SpEL expressions. These are not evaluated at design time — they are written literally into the BPMN XML and evaluated by the Camunda engine at runtime.

{
"type": "inputParameter",
"name": "ticketId",
"set": "#{ticket.id}"
}

Binding types

attribute — BPMN / Camunda attribute

Sets a direct attribute on the BPMN element or its Camunda extension.

{
"type": "attribute",
"name": "<attributeName>",
"set": "value"
},
{
"type": "attribute",
"name": "<attributeName>",
"from": "/formField"
}
PropertyTypeRequiredDescription
type"attribute"yes
namestringyesAttribute name (see table below)
set / fromstringyes (one of)Value source

Available attribute names:

Attribute nameTaskDefinition fieldApplies toNotes
namenameallElement display name
descriptiondescriptionallHTML description
documentationdocumentationallHTML documentation
colorcolorallElement color in designer
iconiconall tasksPrimeIcons class (e.g. pi pi-user)
taskSpecificationtaskSpecificationall tasks
asyncBeforeasyncBeforeall tasksBoolean as string
asyncAfterasyncAfterall tasksBoolean as string
exclusiveexclusiveall tasksBoolean as string
jobPriorityjobPriorityall tasks
failedJobRetryTimeCyclefailedJobRetryTimeCycleall tasksISO 8601 duration (e.g. R3/PT10M)
hideInInstanceGraphhideInInstanceGraphServiceTask, SendTaskBoolean as string
showInActiveTasksshowInActiveTasksReceiveTaskBoolean as string
assigneeassigneeUserTaskSpEL expression
candidateGroupscandidateUserGroupsUserTaskComma-separated or array
defaultUserGroupdefaultUserGroupUserTask
skillsskillsUserTaskComma-separated or array
formKeyformKeyUserTask
taskPrioritytaskPriorityUserTask
disableChangeUserdisableChangeUserUserTaskBoolean
disableChangeUserGroupdisableChangeUserGroupUserTaskBoolean
hideDefaultDoneStatushideDefaultDoneStatusUserTaskBoolean
defaultOpenStatusdefaultOpenStatusUserTaskStatus code
defaultCloseStatusdefaultCloseStatusUserTaskStatus code
expressionconditionExpressionServiceTask, SendTaskMain expression
resultVariableresultVariableServiceTask, SendTask
implementationTypeimplementationTypeServiceTask, SendTaskexpression / external
implementationTopicimplementationTopicServiceTask, SendTaskExternal task topic
decisionRefdecisionRefBusinessRuleTaskDMN decision key
mapDecisionResultmapDecisionResultBusinessRuleTasksingleEntry / singleResult / collectEntries / resultList
calledElementcallActivityProcessDefinitionIdCallActivityProcess definition code or expression
nameFromCalledSubprocessnameFromCalledSubprocessCallActivityBoolean
privilegeSkipTaskconfig.privilegeSkipTasktasksPrivilege role code
privilegeRestartTaskconfig.privilegeRestartTasktasksPrivilege role code
privilegeInterruptTaskconfig.privilegeInterruptTasktasksPrivilege role code
privilegeSetTimeJobconfig.privilegeSetTimeJobtasksPrivilege role code
privilegeExecuteJobNowconfig.privilegeExecuteJobNowtasksPrivilege role code
privilegeNotAssignUserconfig.privilegeNotAssignUserUserTaskPrivilege role code
privilegeNotAssignUserGroupconfig.privilegeNotAssignUserGroupUserTaskPrivilege role code

Example:

[
{
"type": "attribute",
"name": "assignee",
"set": "#{tsmExpressionService.currentUser()}"
},
{
"type": "attribute",
"name": "candidateGroups",
"from": "/userGroup"
},
{
"type": "attribute",
"name": "formKey",
"set": "FORM_TASK_DETAIL"
},
{
"type": "attribute",
"name": "color",
"set": "BLUE"
},
{
"type": "attribute",
"name": "asyncBefore",
"set": "true"
},
{
"type": "attribute",
"name": "privilegeSkipTask",
"set": "ROLE_ADMIN"
}
]

inputParameter — Camunda Input Parameter

Adds or updates a camunda:inputParameter in the camunda:inputOutput extension element.

[
{
"type": "inputParameter",
"name": "paramName",
"set": "#{expression}"
},
{
"type": "inputParameter",
"name": "paramName",
"from": "/formField"
}
]
PropertyTypeRequiredDescription
type"inputParameter"yes
namestringyesParameter name
set / fromstringyes (one of)Value source

Example:

[
{
"type": "inputParameter",
"name": "ticketId",
"set": "#{ticket.id}"
},
{
"type": "inputParameter",
"name": "emailTo",
"from": "/recipientEmail"
}
]

outputParameter — Camunda Output Parameter

Adds or updates a camunda:outputParameter. Same structure as inputParameter.

[
{
"type": "outputParameter",
"name": "paramName",
"set": "#{expression}"
},
{
"type": "outputParameter",
"name": "paramName",
"set": "script"
}
]
PropertyTypeRequiredDescription
type"outputParameter"yes
namestringyesParameter name
set / fromstringyes (one of)Value source

camundaField — Top-level Camunda Field

Adds or updates a top-level camunda:field in extension elements (outside of listeners).

[
{
"type": "camundaField",
"name": "fieldName",
"set": "value"
},
{
"type": "camundaField",
"name": "fieldName",
"setJson": { "key": "value" }
}
]
PropertyTypeRequiredDescription
type"camundaField"yes
namestringyesField name (e.g. scriptCode, paramsJson, retryCount)
set / from / setJsonstring / objectyes (one of)Value source. from reads from the template form. setJson is serialized to a JSON string.
Using setJson instead of escaped strings

Instead of writing escaped JSON in set:

{
"set": "{\"comment\":\"Hello world\"}",
"name": "paramsJson",
"type": "camundaField"
}

use setJson for readability:

{
"setJson": {
"comment": "Hello world"
},
"name": "paramsJson",
"type": "camundaField"
}

Both produce the same camunda:field with <camunda:string>{"comment":"Hello world"}</camunda:string> (the paramsJson field always uses the camunda:string child element; other fields use the stringValue attribute).

Example — Script Binding via camundaField:

[
{
"type": "camundaField",
"name": "scriptCode",
"set": "SCRIPT_EMAIL_SENDER"
},
{
"type": "camundaField",
"name": "paramsJson",
"setJson": {
"recipient": "#{ticket.chars.email}",
"template": "welcome-email"
}
}
]

executionListener — Execution Listener

Creates a camunda:executionListener on the BPMN element. Two variants exist: expression-based and **delegate-based **.

Expression variant

Simple listener that evaluates a SpEL expression.

{
"type": "executionListener",
"event": "start",
"set": "#{logService.info('Task started', ticket.id)}",
"name": "log-on-start",
"documentation": "Logs task start"
}
PropertyTypeRequiredDescription
type"executionListener"yes
event"start" | "end"yesListener event
set / fromstringyes (one of)SpEL expression
namestringnoListener name (used for identification and upsert)
documentationstringnoHuman-readable description
Delegate variant

Listener that calls a delegate bean, optionally with scriptCode, params, and custom fields.

{
"type": "executionListener",
"event": "start",
"name": "notify-on-start",
"delegateExpression": "#{tsmScriptListenerExecutor}",
"scriptCode": {
"set": "notification.create"
},
"params": {
"setJson": {
"code": "task.assigned",
"payload": {
"message": "#{task.name} - #{execution.processBusinessKey}",
"recipientGroup": "#{execution.getVariable('candidateGroup')}"
}
}
},
"fields": [
{
"name": "targetSystem",
"value": {
"from": "/integrationTarget"
}
},
{
"name": "action",
"value": {
"set": "FINALIZE"
}
},
{
"name": "config",
"value": {
"setJson": {
"retry": 3,
"timeout": 5000
}
}
}
]
}
PropertyTypeRequiredDescription
type"executionListener"yes
event"start" | "end"yesListener event
namestringnoListener name
documentationstringnoDescription
delegateExpressionstringnoDelegate bean. If omitted and scriptCode is present, defaults to #{tsmScriptListenerExecutor}
scriptCodeBindingFieldValuenoScript code (set, from, or setJson) → written as camunda:field name="scriptCode"
paramsParamBinding[] | BindingFieldValuenoParameters → written as camunda:field name="paramsJson". Can be an array of named params (new) or a single BindingFieldValue (legacy). See Params formats below.
fieldsDelegateFieldBinding[]noAdditional named fields → written as camunda:field elements

Each DelegateFieldBinding has:

PropertyTypeDescription
namestringField name
valueBindingFieldValueValue — { "set": "..." }, { "from": "/..." }, or { "setJson": { ... } }
delegateExpression auto-default

If you specify scriptCode but omit delegateExpression, the system automatically uses #{tsmScriptListenerExecutor}. This is the standard tSM script executor delegate.

Params formats

The params property supports two formats. Both produce a single camunda:field name="paramsJson" containing a JSON string.

Array format (recommended) — define each parameter individually with its own value source:

{
"type": "executionListener",
"event": "start",
"name": "notify-on-start",
"scriptCode": { "set": "Notify.Script.Comment" },
"params": [
{ "name": "orderId", "value": { "from": "/orderId" } },
{ "name": "comment", "value": { "from": "/comment" } },
{ "name": "data", "setJson": { "version": "1" } }
]
}

Each ParamBinding has:

PropertyTypeRequiredDescription
namestringyesKey in the resulting paramsJson object. Always used as the key, regardless of from.
valueBindingValueone of these{ "set": "..." } or { "from": "/..." } — resolved at apply time
setJsonanyone of theseStatic JSON value — written directly (no quoting). Mutually exclusive with value.
Key in paramsJson

The key in the resulting JSON object is always the name property of the param binding. The from path only determines which form field the value is read from — it does NOT affect the key name. For example, { "name": "comment", "value": { "from": "/commentMessage" } } reads the value from form field commentMessage but writes it as key "comment" in paramsJson.

Example with nested paths and name ≠ from:

{
"params": [
{ "name": "orderId", "value": { "from": "/orderId" } },
{ "name": "comment", "value": { "from": "/commentMessage" } },
{ "name": "region", "value": { "from": "/address/region" } },
{ "name": "data", "setJson": { "version": "1" } }
]
}

Given form values { "orderId": 123, "commentMessage": "hello", "address": { "region": "EU" } }, the resulting paramsJson is:

{"orderId": 123, "comment": "hello", "region": "EU", "data": {"version": "1"}}

Note how comment comes from form field commentMessage, and region comes from the nested path address.region. The from path determines where to read from the form; name determines the key in paramsJson.

With the array format, each from binding is individually extractable — when you reopen the element, each param value is read back into its corresponding form field.

Object format (legacy) — a single BindingFieldValue that produces the entire paramsJson at once:

{
"params": {
"setJson": {
"code": "task.assigned",
"payload": { "message": "#{task.name}" }
}
}
}

Both formats are fully supported. Use the array format when individual parameters should be editable via the template form, and the object format when paramsJson is a static blob or comes from a single form field.

How params remapping works

When a delegate listener (execution/task/status) uses the array params format, each ParamBinding with value.from acts as a renaming rule between form field names and paramsJson keys. Understanding this mechanism is essential for designing templates correctly.

The rules are:

  1. No value.from params → form values flow through unchanged. The form field name IS the paramsJson key.
  2. value.from params → the name becomes the paramsJson key, the from path determines which form field to read. This allows renaming form fields to different paramsJson keys.
  3. setJson params → always injected as static values, regardless of the form.
  4. Unmapped form fields → any form field NOT referenced by a value.from binding passes through as-is (the form field name becomes the paramsJson key directly).

These rules apply both when writing (form → paramsJson) and when reading (paramsJson → form).

Key insight

params with value.from are renaming rules, not filters. They don't remove unmapped fields — they only rename the ones they explicitly target. Everything else passes through unchanged.

Example — no params at all (pure pass-through):

Template binding:

{
"type": "executionListener",
"event": "start",
"scriptCode": { "set": "Notify.Script.Comment" }
}

When params is omitted entirely, there are no ParamBinding entries at all. The script's form provides the fields (e.g. {orderId: string, commentMessage: string}) and all form values flow directly into paramsJson without any transformation — form field names ARE the paramsJson keys.

Given form values { "orderId": "ORD-42", "commentMessage": "hello" }, the resulting paramsJson is:

{"orderId": "ORD-42", "commentMessage": "hello"}
<camunda:field name="scriptCode" stringValue="Notify.Script.Comment" />
<camunda:field name="paramsJson">
<camunda:string>{"orderId":"ORD-42","commentMessage":"hello"}</camunda:string>
</camunda:field>

This is the simplest case — no remapping, no static injection, just the raw form output.

Example — only setJson params (pass-through + static injection):

Template binding:

{
"type": "executionListener",
"event": "start",
"scriptCode": { "set": "Notify.Script.Comment" },
"params": [
{ "name": "data", "setJson": { "version": "1" } }
]
}

The script Notify.Script.Comment has a form with fields {orderId: string, commentMessage: string}. Since there are no value.from bindings, the form field names are used directly as paramsJson keys. The setJson param data is always injected.

Given form values { "orderId": "ORD-42", "commentMessage": "hello" }, the resulting paramsJson is:

{"data": {"version": "1"}, "commentMessage": "hello", "orderId": "ORD-42"}
<camunda:field name="scriptCode" stringValue="Notify.Script.Comment" />
<camunda:field name="paramsJson">
<camunda:string>{"data":{"version":"1"},"commentMessage":"hello","orderId":"ORD-42"}</camunda:string>
</camunda:field>

Example — full remapping (all form fields have value.from):

Template binding:

{
"type": "executionListener",
"event": "start",
"scriptCode": { "set": "Notify.Script.Comment" },
"params": [
{ "name": "orderId", "value": { "from": "/orderId" } },
{ "name": "comment", "value": { "from": "/commentMessage" } },
{ "name": "data", "setJson": { "version": "1" } }
]
}

Here, form field commentMessage is renamed to paramsJson key comment. The orderId mapping keeps the same name (identity mapping). Since all form fields are covered by value.from bindings, nothing passes through unmapped.

Given form values { "orderId": "ORD-42", "commentMessage": "hello" }, the resulting paramsJson is:

{"orderId": "ORD-42", "comment": "hello", "data": {"version": "1"}}
<camunda:field name="scriptCode" stringValue="Notify.Script.Comment" />
<camunda:field name="paramsJson">
<camunda:string>{"orderId":"ORD-42","comment":"hello","data":{"version":"1"}}</camunda:string>
</camunda:field>

Note: commentMessage from the form became comment in paramsJson — that's the renaming effect.

Example — partial remapping (some fields renamed, others pass through):

Template binding:

{
"type": "executionListener",
"event": "start",
"scriptCode": { "set": "Notify.Script.Comment" },
"params": [
{ "name": "comment", "value": { "from": "/commentMessage" } },
{ "name": "data", "setJson": { "version": "1" } }
]
}

Here, only commentMessage is remapped to comment. The form field orderId has no corresponding value.from binding, so it passes through unchanged — the form field name orderId becomes the paramsJson key directly.

Given form values { "orderId": "ORD-42", "commentMessage": "hello" }, the resulting paramsJson is:

{"comment": "hello", "data": {"version": "1"}, "orderId": "ORD-42"}
<camunda:field name="scriptCode" stringValue="Notify.Script.Comment" />
<camunda:field name="paramsJson">
<camunda:string>{"comment":"hello","data":{"version":"1"},"orderId":"ORD-42"}</camunda:string>
</camunda:field>

Note: orderId kept its form field name because no value.from binding targets it.

Round-trip: reading paramsJson back into the form

When you reopen the element, the reverse mapping applies:

  • Params with value.from: the paramsJson key (name) is read, and the value is placed at the form path (from). E.g. paramsJson {"comment": "hello"} → form value {"commentMessage": "hello"}.
  • setJson params: actively blocked during read — the setJson key is excluded from pass-through, so its value does not leak into the form. This is correct because setJson values are static and not form-driven.
  • Unmapped keys: passed through as-is (paramsJson key = form field name).

This ensures the form always shows the correct values regardless of any renaming.

Edge case: form field name collides with setJson param name

If a form field has the same name as a setJson param's name, the form field value wins — it overwrites the setJson static value in the resulting paramsJson. This is because setJson is injected first, and the pass-through loop writes over it. Avoid naming form fields the same as setJson param names.

When to use params remapping

Use value.from remapping when:

  • The script expects different parameter names than what your form fields are called.
  • You want to decouple the form field naming from the script's parameter contract.

If the form field names already match the script's expected parameter names, you don't need value.from — just use setJson for static values and let form values pass through directly.


taskListener — Task Listener

Same structure as executionListener, but for task lifecycle events. Only available on User Tasks.

PropertyDifference from executionListener
type"taskListener"
event"create" | "complete" | "assignment" | "delete"

Example:

[
{
"type": "taskListener",
"event": "create",
"set": "#{notificationService.notifyAssignee(task)}",
"name": "notify-assignee"
},
{
"type": "taskListener",
"event": "complete",
"name": "custom-delegate-complete",
"delegateExpression": "#{myCustomDelegateBean}",
"fields": [
{
"name": "action",
"value": {
"set": "FINALIZE"
}
},
{
"name": "targetSystem",
"value": {
"from": "/integrationTarget"
}
}
]
}
]

statusListener — Status Listener (tSM-specific)

Listener that fires when a tSM task transitions to one of the specified statuses. Same expression/delegate variants as execution listeners, but with statuses instead of event.

PropertyTypeRequiredDescription
type"statusListener"yes
statusesstring[]yesStatus codes that trigger the listener (e.g. ["IN_PROGRESS", "DONE"])
namestringnoListener name
documentationstringnoDescription
+ expression or delegate fieldsSame as executionListener

Example:

[
{
"type": "statusListener",
"statuses": [
"IN_PROGRESS"
],
"set": "#{notificationService.notifyStatusChange(ticket, 'IN_PROGRESS')}"
},
{
"type": "statusListener",
"statuses": [
"ESCALATED"
],
"name": "escalation-handler",
"delegateExpression": "#{escalationHandlerDelegate}",
"fields": [
{
"name": "escalationLevel",
"value": {
"set": "L2"
}
},
{
"name": "notifyGroup",
"value": {
"from": "/escalationGroup"
}
}
]
}
]

inputVariable — Call Activity Input Variable

Adds a camunda:in element for passing variables into a called subprocess. Only for Call Activity.

{
"type": "inputVariable",
"source": "ticket",
"target": "parentTicket",
"sourceType": "source",
"local": false
}
PropertyTypeRequiredDescription
type"inputVariable"yes
sourcestringyesSource variable name or expression
targetstringyesTarget variable name in the subprocess
sourceType"source" | "sourceExpression"noDefault "source". Use "sourceExpression" when source is a SpEL expression.
localbooleannoIf true, the variable is local to the execution scope

Example:

[
{
"type": "inputVariable",
"source": "ticket",
"target": "parentTicket",
"sourceType": "source"
},
{
"type": "inputVariable",
"source": "#{ticket.chars.customerName}",
"target": "customerName",
"sourceType": "sourceExpression"
},
{
"type": "inputVariable",
"source": "#{tsmExpressionService.currentUser()}",
"target": "initiatorUser",
"sourceType": "sourceExpression",
"local": true
}
]

outputVariable — Call Activity Output Variable

Adds a camunda:out element. Same structure as inputVariable, but maps variables from the subprocess back to the parent.

[
{
"type": "outputVariable",
"source": "childResult",
"target": "subprocessResult",
"sourceType": "source"
},
{
"type": "outputVariable",
"source": "#{childTicket.id}",
"target": "createdChildTicketId",
"sourceType": "sourceExpression"
}
]

status — Status Definition (tSM-specific)

Defines a status and its allowed transitions in the tSM task state machine. Only for User Tasks.

{
"type": "status",
"statusCode": "IN_PROGRESS",
"transitions": [
{
"statusCode": "DONE"
},
{
"statusCode": "WAITING",
"name": "Wait for customer",
"condition": "#{ticket.chars.customerContact != null}"
},
{
"statusCode": "ESCALATED",
"name": "Escalate",
"hasAuthority": "ROLE_SUPERVISOR",
"confirmMessage": "Are you sure?"
}
]
}
PropertyTypeRequiredDescription
type"status"yes
statusCodestringyesStatus code
transitionsStatusTransition[]noAllowed transitions from this status

Each StatusTransition has:

PropertyTypeDescription
statusCodestringTarget status code (required)
namestringTransition button label
descriptionstringTooltip description
formKeystringForm to show during transition
charsPropertyNamestringProperty name for form data
conditionstringSpEL condition — transition is only available if it evaluates to true
hasAuthoritystringRequired privilege role
confirmMessagestringConfirmation dialog message

Example — full state machine:

[
{
"type": "status",
"statusCode": "NEW",
"transitions": [
{
"statusCode": "IN_PROGRESS"
},
{
"statusCode": "WAITING",
"name": "Wait for customer",
"condition": "#{ticket.chars.customerContact != null}"
}
]
},
{
"type": "status",
"statusCode": "IN_PROGRESS",
"transitions": [
{
"statusCode": "DONE"
},
{
"statusCode": "WAITING",
"formKey": "FORM_WAIT_REASON",
"charsPropertyName": "waitReason"
},
{
"statusCode": "ESCALATED",
"hasAuthority": "ROLE_SUPERVISOR",
"confirmMessage": "Are you sure you want to escalate?"
}
]
},
{
"type": "status",
"statusCode": "WAITING",
"transitions": [
{
"statusCode": "IN_PROGRESS"
}
]
},
{
"type": "status",
"statusCode": "ESCALATED",
"transitions": [
{
"statusCode": "IN_PROGRESS"
},
{
"statusCode": "DONE"
}
]
},
{
"type": "status",
"statusCode": "DONE"
}
]

kpi — KPI Configuration (tSM-specific)

Sets deadline calculation parameters on the task.

{
"type": "kpi",
"minTime": 60,
"maxTime": 1440,
"manualDueDate": false
}
PropertyTypeRequiredDescription
type"kpi"yes
minTimenumbernoMinimum expected duration in minutes
maxTimenumbernoMaximum expected duration (deadline) in minutes
manualDueDatebooleannoIf true, allows manual due date override

Binding types summary

Binding typeValue sourcesApplies toBPMN target
attributeset / fromallBPMN attribute or tSM extension attribute
inputParameterset / fromall taskscamunda:inputParameter
outputParameterset / fromall taskscamunda:outputParameter
camundaFieldset / from / setJsonall taskscamunda:field (top-level)
executionListenerset / from + delegate fieldsall taskscamunda:executionListener
taskListenerset / from + delegate fieldsUserTaskcamunda:taskListener
statusListenerset / from + delegate fieldsUserTasktsm:statusListener
inputVariablestatic (source/target)CallActivitycamunda:in
outputVariablestatic (source/target)CallActivitycamunda:out
statusstatic (statusCode/transitions)UserTasktsm:status
kpistatic (minTime/maxTime/manualDueDate)all taskstsm:kpi

Complete template example

A User Task template with a form, attribute bindings, listeners, statuses, and KPI:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"code": "task.user.backoffice-review",
"name": "Backoffice Review",
"version": "1.0.0",
"description": "User task for backoffice ticket review with SLA and notifications",
"appliesTo": [
"userTask"
],
"formCode": "forms.backoffice-review",
"dataTags": [
"backoffice",
"review"
],
"bindings": [
{
"type": "attribute",
"name": "assignee",
"set": "#{tsmExpressionService.currentUser()}"
},
{
"type": "attribute",
"name": "candidateGroups",
"from": "/userGroup"
},
{
"type": "attribute",
"name": "formKey",
"set": "FORM_TASK_DETAIL"
},
{
"type": "attribute",
"name": "color",
"set": "BLUE"
},
{
"type": "attribute",
"name": "asyncBefore",
"set": "true"
},
{
"type": "attribute",
"name": "privilegeSkipTask",
"set": "ROLE_ADMIN"
},
{
"type": "inputParameter",
"name": "ticketId",
"set": "#{ticket.id}"
},
{
"type": "inputParameter",
"name": "emailTo",
"from": "/recipientEmail"
},
{
"type": "outputParameter",
"name": "taskResult",
"set": "#{taskResultVariable}"
},
{
"type": "executionListener",
"event": "start",
"name": "notify-on-start",
"scriptCode": {
"set": "notification.create"
},
"params": {
"setJson": {
"code": "task.assigned",
"payload": {
"message": "#{task.name} - #{execution.processBusinessKey}",
"recipientGroup": "#{execution.getVariable('candidateGroup')}"
}
}
}
},
{
"type": "taskListener",
"event": "complete",
"name": "custom-delegate-complete",
"delegateExpression": "#{myCustomDelegateBean}",
"fields": [
{
"name": "action",
"value": {
"set": "FINALIZE"
}
},
{
"name": "targetSystem",
"value": {
"from": "/integrationTarget"
}
}
]
},
{
"type": "camundaField",
"name": "notificationType",
"set": "EMAIL"
},
{
"type": "camundaField",
"name": "paramsJson",
"setJson": {
"channel": "email",
"template": "backoffice-review-notification"
}
},
{
"type": "status",
"statusCode": "NEW",
"transitions": [
{
"statusCode": "IN_PROGRESS"
}
]
},
{
"type": "status",
"statusCode": "IN_PROGRESS",
"transitions": [
{
"statusCode": "DONE"
},
{
"statusCode": "ESCALATED",
"hasAuthority": "ROLE_SUPERVISOR",
"confirmMessage": "Escalate?"
}
]
},
{
"type": "status",
"statusCode": "ESCALATED",
"transitions": [
{
"statusCode": "IN_PROGRESS"
},
{
"statusCode": "DONE"
}
]
},
{
"type": "status",
"statusCode": "DONE"
},
{
"type": "statusListener",
"statuses": [
"ESCALATED"
],
"name": "escalation-handler",
"delegateExpression": "#{escalationHandlerDelegate}",
"fields": [
{
"name": "escalationLevel",
"value": {
"set": "L2"
}
},
{
"name": "notifyGroup",
"value": {
"from": "/escalationGroup"
}
}
]
},
{
"type": "kpi",
"minTime": 60,
"maxTime": 1440,
"manualDueDate": false
}
]
}

Notes:

  • The same bindings are used for reading (extract) and writing (apply).
  • set values may contain #{...} SpEL expressions — these are stored literally and evaluated at runtime.
  • from paths use JSON Pointer-like syntax (e.g. "/userGroup", "/nested/field").
  • tsm:templateCode is written automatically when a template is applied — you do not need a binding for it.
  • inputVariable / outputVariable / status / kpi are static bindings — they do not use set / from and are applied once when the template is selected.

Examples

The following examples show different template types and the BPMN XML they produce. In each case the user fills in a simple form — the template writes all the technical configuration.

AI Agent Call (Script Binding)

A Service Task that calls an AI agent via Script Binding. The user provides the agent ID, a prompt, and the variable to store the result.

Bindings:

[
{
"type": "camundaField",
"name": "scriptCode",
"set": "ai.agent.call"
},
{
"type": "camundaField",
"name": "paramsJson",
"setJson": {
"agentId": "agent-support-l1",
"prompt": "#{prompt}",
"resultVariable": "agentResponse"
}
}
]

Resulting BPMN XML:

<bpmn:serviceTask id="Activity_ai_agent" name="AI Agent Call"
camunda:delegateExpression="#{tsmScriptDelegateExecutor}"
tsm:templateCode="task.script.ai-agent/1.0.0">
<bpmn:extensionElements>
<camunda:field name="scriptCode" stringValue="ai.agent.call"/>
<camunda:field name="paramsJson">
<camunda:string>{"agentId":"agent-support-l1","prompt":"#{prompt}","resultVariable":"agentResponse"}</camunda:string>
</camunda:field>
</bpmn:extensionElements>
</bpmn:serviceTask>

Notify on Start (Execution Listener)

An execution listener template that sends a notification when a task starts. It can be combined with any User Task.

Bindings:

[
{
"type": "attribute",
"name": "candidateGroups",
"set": "backoffice"
},
{
"type": "executionListener",
"event": "start",
"name": "notify-on-create",
"scriptCode": {
"set": "notification.create"
},
"params": {
"setJson": {
"code": "task.assigned",
"payload": {
"message": "#{task.name}",
"recipientGroup": "#{execution.getVariable('candidateGroup')}"
}
}
}
}
]

Resulting BPMN XML:

<bpmn:userTask id="Activity_review" name="Review Request"
camunda:candidateGroups="backoffice"
tsm:templateCode="listener.notify-on-create/1.0.0">
<bpmn:extensionElements>
<camunda:executionListener event="start"
delegateExpression="#{tsmScriptListenerExecutor}"
tsm:name="notify-on-create">
<camunda:field name="scriptCode" stringValue="notification.create"/>
<camunda:field name="paramsJson">
<camunda:string>{"code":"task.assigned","payload":{"message":"#{task.name}","recipientGroup":"#{execution.getVariable('candidateGroup')}"}}</camunda:string>
</camunda:field>
</camunda:executionListener>
</bpmn:extensionElements>
</bpmn:userTask>

Comment Script with Named Params (Array params)

A Service Task that calls a script with individually-mapped form parameters using the array params format.

Bindings:

[
{
"type": "executionListener",
"event": "start",
"name": "add-comment",
"scriptCode": {
"set": "Notify.Script.Comment"
},
"params": [
{
"name": "orderId",
"value": { "from": "/orderId" }
},
{
"name": "comment",
"value": { "from": "/comment" }
},
{
"name": "metadata",
"setJson": { "source": "template", "version": 1 }
}
]
}
]

Resulting BPMN XML (assuming form values orderId = "ORD-42", comment = "Please review"):

<bpmn:serviceTask id="Activity_comment" name="Add Comment"
tsm:templateCode="task.script.comment/1.0.0">
<bpmn:extensionElements>
<camunda:executionListener event="start"
delegateExpression="#{tsmScriptListenerExecutor}"
tsm:name="add-comment">
<camunda:field name="scriptCode" stringValue="Notify.Script.Comment"/>
<camunda:field name="paramsJson">
<camunda:string>{"orderId":"ORD-42","comment":"Please review","metadata":{"source":"template","version":1}}</camunda:string>
</camunda:field>
</camunda:executionListener>
</bpmn:extensionElements>
</bpmn:serviceTask>

Each from param is individually extracted when you reopen the element, so changing one form field only updates that parameter in the paramsJson — the rest are preserved.


JSON ↔ XML Binding Examples

The following examples show the bindings JSON and the resulting BPMN XML it produces. Use these as a reference when creating new templates.

Example 1: UserTask — attributes, statuses, statusListener, input/output

A comprehensive UserTask showing attribute bindings, status definitions with full transition properties, a statusListener, and input/output parameters.

Bindings JSON:

[
{"type": "attribute", "name": "assignee", "set": "#{tsmExpressionService.currentUser()}"},
{"type": "attribute", "name": "candidateGroups", "set": "SUPPORT_L1"},
{"type": "attribute", "name": "defaultUserGroup", "set": "SUPPORT_L1"},
{"type": "attribute", "name": "formKey", "set": "Ticket.Detail"},
{"type": "attribute", "name": "defaultOpenStatus", "set": "NEW"},
{
"type": "status",
"statusCode": "NEW",
"transitions": [
{
"statusCode": "IN_PROGRESS",
"name": "Start processing",
"formKey": "Ticket.AcceptForm",
"condition": "#{ticket.chars.priority != null}",
"hasAuthority": "ROLE_SUPPORT",
"confirmMessage": "Accept this ticket and start processing?"
}
]
},
{
"type": "status",
"statusCode": "IN_PROGRESS",
"transitions": [{"statusCode": "DONE"}]
},
{
"type": "statusListener",
"statuses": ["NEW"],
"name": "log-new-ticket",
"set": "#{auditService.logStatusChange(ticket, 'NEW')}"
},
{"type": "inputParameter", "name": "ticketId", "set": "#{ticket.id}"},
{"type": "outputParameter", "name": "resolution", "set": "#{taskResult}"}
]

Resulting BPMN XML:

<bpmn:userTask id="Activity_review" name="Ticket Review"
camunda:assignee="#{tsmExpressionService.currentUser()}"
camunda:candidateUserGroups="SUPPORT_L1"
camunda:formKey="Ticket.Detail"
tsm:templateCode="task.ticket-review"
tsm:defaultUserGroup="SUPPORT_L1"
tsm:defaultOpenStatus="NEW"
tsm:color="WHITE">
<bpmn:extensionElements>
<tsm:status status="NEW">
<tsm:transition status="IN_PROGRESS"
name="Start processing"
formKey="Ticket.AcceptForm"
condition="#{ticket.chars.priority != null}"
hasAuthority="ROLE_SUPPORT"
confirmMessage="Accept this ticket and start processing?" />
</tsm:status>
<tsm:status status="IN_PROGRESS">
<tsm:transition status="DONE" />
</tsm:status>
<tsm:statusListener expression="#{auditService.logStatusChange(ticket, 'NEW')}"
statuses="NEW"
name="log-new-ticket" />
<camunda:inputOutput>
<camunda:inputParameter name="ticketId">#{ticket.id}</camunda:inputParameter>
<camunda:outputParameter name="resolution">#{taskResult}</camunda:outputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
</bpmn:userTask>

Example 2: ServiceTask — executionListener with scriptCode + array params

A ServiceTask that sends a notification using an execution listener with tsmScriptListenerExecutor. Uses the array params format where individual parameters are mapped from form fields (from) or set as static JSON (setJson).

Bindings JSON:

[
{
"type": "executionListener",
"event": "start",
"name": "send-notification",
"scriptCode": {"set": "Notification.Send"},
"params": [
{"name": "recipientId", "value": {"from": "/recipientId"}},
{"name": "message", "value": {"from": "/message"}},
{"name": "config", "setJson": {"channel": "email", "priority": "high"}}
]
}
]
delegateExpression auto-default

Notice the binding does not specify delegateExpression. Because scriptCode is present, the system automatically uses #{tsmScriptListenerExecutor}.

Resulting BPMN XML (assuming form values recipientId = "user-123", message = "Order shipped"):

<bpmn:serviceTask id="Activity_notify" name="Send Notification"
tsm:templateCode="task.notification"
tsm:color="WHITE"
tsm:icon="tsm-icon-notification">
<bpmn:extensionElements>
<camunda:executionListener delegateExpression="#{tsmScriptListenerExecutor}"
event="start"
tsm:name="send-notification">
<camunda:field name="scriptCode" stringValue="Notification.Send" />
<camunda:field name="paramsJson">
<camunda:string>{"recipientId":"user-123","message":"Order shipped","config":{"channel":"email","priority":"high"}}</camunda:string>
</camunda:field>
</camunda:executionListener>
</bpmn:extensionElements>
</bpmn:serviceTask>
Array params → paramsJson

The three params are merged into a single paramsJson field: {"recipientId": <from form>, "message": <from form>, "config": {"channel":"email","priority":"high"}} (the key for each param is the name property — e.g. name: "message" → key "message" in paramsJson). Each from param is individually extractable — changing one form field updates only that key.


Example 3: ServiceTask — camundaField bindings (scriptCode + paramsJson)

A ServiceTask using tsmScriptDelegateExecutor with top-level camunda:field bindings. This is the simpler approach when all parameters are static.

Bindings JSON:

[
{"type": "camundaField", "name": "scriptCode", "set": "Order.CreateInvoice"},
{
"type": "camundaField",
"name": "paramsJson",
"setJson": {
"orderId": "#{order.id}",
"invoiceType": "standard"
}
}
]

Resulting BPMN XML:

<bpmn:serviceTask id="Activity_invoice" name="Create Invoice"
camunda:delegateExpression="#{tsmScriptDelegateExecutor}"
tsm:templateCode="task.create-invoice"
tsm:color="WHITE"
tsm:icon="tsm-icon-invoice">
<bpmn:extensionElements>
<camunda:field name="scriptCode" stringValue="Order.CreateInvoice" />
<camunda:field name="paramsJson">
<camunda:string>{"orderId":"#{order.id}","invoiceType":"standard"}</camunda:string>
</camunda:field>
</bpmn:extensionElements>
</bpmn:serviceTask>
camundaField vs executionListener

Use camundaField for top-level fields on the task element itself (e.g. tsmScriptDelegateExecutor). Use executionListener/taskListener delegate when the fields belong inside a listener element.


Example 4: ServiceTask — inputParameter / outputParameter

A ServiceTask demonstrating inputParameter and outputParameter bindings with both set (static SpEL) and from (form field) value sources.

Bindings JSON:

[
{"type": "inputParameter", "name": "ticketId", "set": "#{ticket.id}"},
{"type": "inputParameter", "name": "targetSystem", "from": "/targetSystem"},
{"type": "outputParameter", "name": "responseCode", "set": "#{responseCode}"},
{"type": "outputParameter", "name": "resultVariable", "from": "/resultVariable"}
]

Resulting BPMN XML (assuming form values targetSystem = "CRM", resultVariable = "#{crmResponse}"):

<bpmn:serviceTask id="Activity_process" name="Process Request"
tsm:templateCode="task.process-request"
tsm:color="WHITE">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="ticketId">#{ticket.id}</camunda:inputParameter>
<camunda:inputParameter name="targetSystem">CRM</camunda:inputParameter>
<camunda:outputParameter name="responseCode">#{responseCode}</camunda:outputParameter>
<camunda:outputParameter name="resultVariable">#{crmResponse}</camunda:outputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
</bpmn:serviceTask>
from vs set

"set": "#{ticket.id}" writes the literal string #{ticket.id} — it is evaluated by Camunda at runtime. "from": "/targetSystem" reads the current value from the template form field /targetSystem and writes it literally into the XML. The value CRM in the XML above came from the form at apply time.


Example 5: UserTask — status-only bindings

A minimal UserTask with only status bindings — a simple state machine (OPEN → IN_PROGRESS → RESOLVED). The first transition demonstrates all optional transition properties.

Bindings JSON:

[
{
"type": "status",
"statusCode": "OPEN",
"transitions": [
{
"statusCode": "IN_PROGRESS",
"name": "Start work",
"formKey": "Ticket.AssignForm",
"condition": "#{ticket.chars.assignee != null}",
"hasAuthority": "ROLE_OPERATOR",
"confirmMessage": "Start working on this ticket?"
}
]
},
{
"type": "status",
"statusCode": "IN_PROGRESS",
"transitions": [{"statusCode": "RESOLVED"}]
}
]

Resulting BPMN XML:

<bpmn:userTask id="Activity_support" name="Support Ticket"
tsm:templateCode="task.support-ticket"
tsm:color="WHITE">
<bpmn:extensionElements>
<tsm:status status="OPEN">
<tsm:transition status="IN_PROGRESS"
name="Start work"
formKey="Ticket.AssignForm"
condition="#{ticket.chars.assignee != null}"
hasAuthority="ROLE_OPERATOR"
confirmMessage="Start working on this ticket?" />
</tsm:status>
<tsm:status status="IN_PROGRESS">
<tsm:transition status="RESOLVED" />
</tsm:status>
</bpmn:extensionElements>
</bpmn:userTask>

Backoffice Review (External Task)

An External Task configured for backoffice review. The template sets the topic and maps the required input variables.

Bindings:

[
{
"type": "attribute",
"name": "implementationType",
"set": "external"
},
{
"type": "attribute",
"name": "implementationTopic",
"set": "backoffice.review"
},
{
"type": "inputParameter",
"name": "ticketId",
"set": "#{ticketId}"
},
{
"type": "inputParameter",
"name": "priority",
"set": "#{priority}"
},
{
"type": "inputParameter",
"name": "assigneeGroup",
"set": "#{assigneeGroup}"
}
]

Resulting BPMN XML:

<bpmn:serviceTask id="Activity_backoffice" name="Backoffice Review"
camunda:type="external"
camunda:topic="backoffice.review"
tsm:templateCode="task.external.backoffice-review/1.0.0">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="ticketId">#{ticketId}</camunda:inputParameter>
<camunda:inputParameter name="priority">#{priority}</camunda:inputParameter>
<camunda:inputParameter name="assigneeGroup">#{assigneeGroup}</camunda:inputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
</bpmn:serviceTask>

Kafka Integration (Kafka Task)

A Receive Task configured as a Kafka Task. The template sets the topics, payload, and the Kafka listener delegate.

Bindings:

[
{
"type": "executionListener",
"event": "start",
"delegateExpression": "#{tsmKafkaTaskExecutor}",
"fields": [
{
"name": "requestTopic",
"value": {
"set": "eligibility-requests"
}
},
{
"name": "responseTopic",
"value": {
"set": "tsm-process-response"
}
},
{
"name": "key",
"value": {
"set": "#{execution.processBusinessKey}"
}
},
{
"name": "payload",
"value": {
"set": "#{execution.getVariable('eligibilityPayload')}"
}
}
]
}
]

Resulting BPMN XML:

<bpmn:receiveTask id="Activity_kafka" name="Request Eligibility Check"
tsm:templateCode="task.kafka.eligibility-check/1.0.0">
<bpmn:extensionElements>
<camunda:executionListener event="start"
delegateExpression="#{tsmKafkaTaskExecutor}">
<camunda:field name="requestTopic" stringValue="eligibility-requests"/>
<camunda:field name="responseTopic" stringValue="tsm-process-response"/>
<camunda:field name="key" stringValue="#{execution.processBusinessKey}"/>
<camunda:field name="payload" stringValue="#{execution.getVariable('eligibilityPayload')}"/>
</camunda:executionListener>
</bpmn:extensionElements>
</bpmn:receiveTask>

Summary

Task Templates provide a consistent, guided way to configure BPMN tasks:

  • Templates are applied to existing BPMN elements and extend the properties panel with a configuration form.
  • Favorite templates can be accessed directly from the palette.
  • Templates use two-way bindings — configured values can be read back and edited at any time.
  • The element is bound to the template via tsm:templateCode for traceability and round-trip editing.
  • Template updates do not propagate automatically to existing processes — re-application is an explicit action.
  • The resulting BPMN remains standard, editable, and deployable.