elsa-workflows/elsa-core · error · BpmnExportUnavailableException
BpmnErrorCodes.ExportNotImported
BpmnErrorCodes.ExportNotImported
Error message
Workflow definition '{definition.DefinitionId}' does not currently carry BPMN source, so it cannot be exported as BPMN 2.0 XML. Either it was never imported from a BPMN document, or a later save replaced its custom properties wholesale and removed the '{SourceXmlCustomPropertyKey}' entry as a side effect of editing something else. What it means
ResolveSourceXml refuses export with BpmnExportUnavailableException (reason NotImported) when the definition's CustomProperties contain no non-empty SourceXmlCustomPropertyKey. Export requires the original BPMN XML recorded at import time; without it the definition cannot be rendered back to BPMN 2.0 XML. The message distinguishes 'never imported' from a later wholesale custom-properties save that removed the key.
Solutions
- Import the workflow from a BPMN document via the BPMN interchange import endpoint so SourceXmlCustomPropertyKey is recorded
- If a save dropped the key, re-save the definition through the BPMN import path or restore the custom property containing the source XML
- For natively-created definitions, use the appropriate (non-BPMN) export mechanism instead
Example fix
// before
var xml = bpmnService.Export(definitionId); // throws if never imported
// after
var def = await definitions.FindAsync(handle);
if (!def.CustomProperties.ContainsKey(BpmnErrorCodes.SourceXmlCustomPropertyKey))
await bpmnService.ImportAsync(def.DefinitionId, bpmnDocument);
var xml = bpmnService.Export(def.DefinitionId); Defensive patterns
Strategy: try-catch
Validate before calling
var imported = definition.CustomProperties.TryGetValue<string>(SourceXmlCustomPropertyKey, out var xml) && !string.IsNullOrEmpty(xml);
if (!imported) throw new InvalidOperationException("Definition has no BPMN source; import first"); Try / catch
try { var xml = service.Export(id); }
catch (BpmnExportUnavailableException ex) when (ex.Reason == BpmnExportUnavailableReason.NotImported) { /* use non-BPMN export or import first */ } Prevention
- Only use BPMN export for definitions actually imported from BPMN
- Avoid wholesale CustomProperties overwrites; merge dictionaries instead
- Track which definitions are BPMN-managed in your tooling
When it happens
Trigger: Calling Export on a definition that (a) was created natively and never imported from a BPMN document, or (b) had its CustomProperties overwritten by a save that replaced them wholesale, dropping SourceXmlCustomPropertyKey.
Common situations: Editing the workflow via a generic designer/save API that rewrites custom properties, copying definitions between environments without custom properties, or exporting a definition created from scratch instead of imported.
Related errors
- BpmnErrorCodes.ExportSourceVersionUnknown
- BpmnErrorCodes.ExportSourceStale
- An < : > element declares no ' ', so there is nothing to…
- An < : > element of the ' ' binding declares no ' '.
- The ' ' binding declares the input ' ' more than once. Each…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/a1baf93e8ca1e399.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Bpmn.Interchange/Services/BpmnInterchangeDocumentService.cs:790
/// <summary>The BPMN <c>calledElement</c> a call activity element carries, kept by the reader for round-trip.</summary>
private static string? CalledElementOf(BpmnElement element) =>
element.Properties.TryGetValue(BpmnXmlReader.CalledElementPropertyKey, out var calledElement) ? calledElement : null;
/// <summary>
/// The BPMN source a workflow definition was imported from, refusing rather than guessing when it is missing or
/// no longer trustworthy. See this type's remarks for what "missing" and "stale" mean and why each gets its own
/// message.
/// </summary>
/// <exception cref="BpmnExportUnavailableException">
/// The definition does not currently carry BPMN source, or it does but the definition has changed since the
/// source was recorded.
/// </exception>
private static string ResolveSourceXml(WorkflowDefinition definition)
{
if (!definition.CustomProperties.TryGetValue<string>(SourceXmlCustomPropertyKey, out var xml) || string.IsNullOrEmpty(xml))
{
throw new BpmnExportUnavailableException(
$"Workflow definition '{definition.DefinitionId}' does not currently carry BPMN source, so it cannot be exported as BPMN 2.0 XML. "
+ "Either it was never imported from a BPMN document, or a later save replaced its custom properties wholesale and removed the "
+ $"'{SourceXmlCustomPropertyKey}' entry as a side effect of editing something else.",
BpmnExportUnavailableReason.NotImported);
}
if (!definition.CustomProperties.TryGetValue<int>(SourceVersionCustomPropertyKey, out var sourceVersion))
{
// Distinct from both other refusals: this is not "never imported" (the source text is right there) and
// not "stale" (there is no version to compare against yet). ImportAsync writes SourceXmlCustomPropertyKey
// and SourceVersionCustomPropertyKey together, in the single save described in its remarks, so this path
// is not reachable through import itself; it is kept as a defence against the same combination arising
// some other way — e.g. custom properties edited or migrated directly, outside ImportAsync — where
// "whether the source still matches" cannot be verified without a version to compare against.
throw new BpmnExportUnavailableException(
$"Workflow definition '{definition.DefinitionId}' carries BPMN source, but not the definition version it was recorded against, so "
+ "whether that source still matches this definition cannot be verified. It does not mean this definition was never imported from "
+ "BPMN, and it does not mean the source is stale — there is simply no version recorded to compare against. Re-import the document to "View on GitHub (pinned to fe9217bdfa)