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

  1. Add an <elsa:binding> element (with activityType and <elsa:input> children) inside the task's <bpmn:extensionElements>.
  2. Verify the binding is attached to the element whose id matches the one named in the error, inside that element's <extensionElements>.
  3. 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

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


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)