elsa-workflows/elsa-core · error · BpmnBindingException
BPMN element ' ' of process ' ' is a ' ': the document says…
Error message
BPMN element '{unbound.ElementId}' of process '{unbound.ProcessId}' is a '{unbound.TaskType}': the document says what it is for, not how to perform it, and nothing binds it to an Elsa activity. Declare one with an <{BpmnActivityBindingFormat.NamespacePrefix}:{BpmnActivityBindingFormat.BindingElementName}> element inside the element's <extensionElements>. What it means
A BPMN task element is described as an 'unbound task' — the document says what it is for but not how to perform it — and no <elsa:binding> declaration was found in its <extensionElements>. The binder cannot construct any activity for it, so import fails with guidance on how to declare one.
Solutions
- Add an <elsa:binding> element (with activityType and <elsa:input> children) inside the task's <bpmn:extensionElements>.
- Verify the binding is attached to the element whose id matches the one named in the error, inside that element's <extensionElements>.
- If the task should not run in Elsa, change the BPMN so it is not an unbound task (e.g. map it to a supported BPMN construct).
Example fix
// before
<bpmn:serviceTask id="Task1" name="Send email" />
// after
<bpmn:serviceTask id="Task1" name="Send email">
<bpmn:extensionElements>
<elsa:binding activityType="SendEmail">
<elsa:input name="Subject">"Hello"</elsa:input>
</elsa:binding>
</bpmn:extensionElements>
</bpmn:serviceTask> Defensive patterns
Strategy: validation
Validate before calling
foreach (var task in doc.Descendants().Where(e => e.Name.LocalName.EndsWith("Task")))
{
var hasBinding = task.Element("extensionElements")?.Elements().Any(x => x.Name.LocalName == "binding") == true;
if (!hasBinding) Console.WriteLine($"Task {task.Attribute("id")?.Value} has no elsa:binding and will fail import if unbound.");
} Type guard
static bool HasElsaBinding(XElement element) =>
element.Element("extensionElements")?.Elements()
.Any(x => x.Name.LocalName == "binding" && x.Name.Namespace.NamespaceName.StartsWith("urn:elsa")) == true; Try / catch
try { await importer.ImportAsync(bpmn); }
catch (BpmnBindingException ex) when (ex.Message.Contains("nothing binds it to an Elsa activity"))
{
logger.LogError(ex, "Declare an <elsa:binding> in the task's <extensionElements>.");
} Prevention
- Adopt a checklist: every authored task in a .bpmn must carry an <elsa:binding>
- Use a modeler template or snippet that includes the extensionElements block
- Verify element ids match between the task and its binding
When it happens
Trigger: BpmnWorkBinder.ReadDeclaredActivity (via CreateActivity for an UnboundTask binding) finds BpmnActivityBindingFormat.Find(element.Extensions) returns null for the element with the given ElementId.
Common situations: Authoring a .bpmn by hand or in a generic BPMN tool and forgetting the Elsa extension binding; the binding element placed outside <extensionElements>; element ID mismatch after renaming IDs in the modeler.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- An < : > element declares no ' ', so there is nothing to…
- An < : > element of the ' ' binding declares no ' '.
- BPMN element ' ' is a call activity that names no…
- BPMN element ' ' ( ) of process ' ' carries an < : >…
- BPMN element ' ' binds process activity ' ' as the work of…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/3aa7620081428ebb.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Bpmn.Interchange/Binding/BpmnWorkBinder.cs:144
BpmnWorkBinding.CallProcess call => new DispatchWorkflow
{
WorkflowDefinitionId = new(CalledElementOf(call)),
WaitForCompletion = new(call.WaitForCompletion)
},
BpmnWorkBinding.NestedProcess nested => BindScope(nested.Definition, bindings),
BpmnWorkBinding.UnboundTask unbound => ReadDeclaredActivity(definition, unbound, consumed),
// The binding hierarchy is closed, so this is reachable only from a library version that added a kind this
// binder has never heard of. Skipping it would produce a scope whose interpreter starts work nothing maps.
_ => throw new BpmnBindingException($"The BPMN work binding kind '{binding.GetType().Name}' declared by element '{binding.ElementId}' is not supported by this binder.")
};
private IActivity ReadDeclaredActivity(BpmnProcessDefinition definition, BpmnWorkBinding.UnboundTask unbound, ISet<string> consumed)
{
var element = definition.Elements.FirstOrDefault(element => string.Equals(element.ElementId, unbound.ElementId, StringComparison.Ordinal));
if (BpmnActivityBindingFormat.Find(element?.Extensions) is not { } declaration)
{
throw new BpmnBindingException(
$"BPMN element '{unbound.ElementId}' of process '{unbound.ProcessId}' is a '{unbound.TaskType}': the document says what it is for, not how to perform it, and nothing binds it to an Elsa activity. "
+ $"Declare one with an <{BpmnActivityBindingFormat.NamespacePrefix}:{BpmnActivityBindingFormat.BindingElementName}> element inside the element's <extensionElements>.");
}
consumed.Add(unbound.ElementId);
return format.Read(declaration);
}
/// <summary>
/// Refuses an <c>elsa:activityBinding</c> on an element that has no unbound task to bind.
/// </summary>
/// <remarks>
/// Six of the seven kinds bind on their own and never consult a declaration, so one written on a timer, a
/// subprocess or a gateway configures nothing. Ignoring it is the quiet answer: the author sees their expression in
/// the file, the process runs, and the activity they configured never executes. Refusing says so.
/// </remarks>
private static void RefuseUnusedDeclarations(BpmnProcessDefinition definition, ISet<string> consumed)View on GitHub (pinned to fe9217bdfa)