elsa-workflows/elsa-core · error · InvalidOperationException
Output converter registration
Error message
Output converter registration '{id}' must use the Converter ID as its service key. What it means
Thrown by OutputConverterRegistry.ValidateRegistrations at startup when an output converter is registered with a service key that differs from its descriptor's Converter ID. The registry resolves converters by keyed services, so the DI service key must exactly (ordinal) match the descriptor Id.
Solutions
- Make the DI service key identical to descriptor.Id (use the same constant in both places).
- Derive the service key from the descriptor when registering: AddKeyedSingleton(descriptor.Id, implementation).
- If using Elsa's registration helpers, pass the descriptor itself so key and Id stay in sync.
Example fix
// before
services.AddKeyedSingleton<IOutputConverter, IntToStringConverter>("int-to-str");
descriptor.Id = "int-to-string";
// after
const string ConverterId = "int-to-string";
descriptor.Id = ConverterId;
services.AddKeyedSingleton<IOutputConverter, IntToStringConverter>(ConverterId); Defensive patterns
Strategy: validation
Validate before calling
if (!string.Equals(descriptor.Id, serviceKey, StringComparison.Ordinal))
throw new InvalidOperationException($"Service key '{serviceKey}' must equal converter Id '{descriptor.Id}'."); Try / catch
try
{
services.AddOutputConverters(...); // triggers ValidateRegistrations
}
catch (InvalidOperationException ex) when (ex.Message.Contains("must use the Converter ID as its service key"))
{
logger.LogCritical(ex, "Output converter registration key mismatch");
throw; // fail fast: misconfiguration cannot be recovered at runtime
} Prevention
- Use a single shared constant for both descriptor.Id and the DI service key.
- Register converters through Elsa's helper APIs that take the descriptor, not hand-rolled AddKeyed calls.
- Add a startup smoke test that builds the host to surface registration errors in CI.
When it happens
Trigger: Registering an OutputConverterDescriptor via DI with a key such as AddKeyedSingleton(key, ...) where key != descriptor.Id; renaming the descriptor Id without updating the registration key; convention-based registration helpers producing mismatched keys.
Common situations: Copy/pasted converter registrations where the descriptor Id was changed, plugin/module registrations assembled dynamically, and refactoring after renaming a converter class.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Output converter ID ' ' is registered more than once or…
- Output converter IDs cannot be empty.
- A workflow resumer is required to complete a User Task.
- Build() must be called before accessing services
- External Authentication handle-hashing settings are…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/527e0f13682372d4.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Workflows.Core/Services/OutputConverterRegistry.cs:53
internal static bool IsAssignableToDestination(Type valueType, Type destinationType)
{
if (destinationType.IsAssignableFrom(valueType))
return true;
var underlyingDestinationType = Nullable.GetUnderlyingType(destinationType);
return underlyingDestinationType?.IsAssignableFrom(valueType) == true;
}
private static void ValidateRegistrations(IReadOnlyCollection<OutputConverterRegistration> registrations)
{
foreach (var registration in registrations)
{
ValidateDescriptor(registration.Descriptor);
var id = registration.Descriptor.Id;
if (!string.Equals(id, registration.ServiceKey, StringComparison.Ordinal))
throw new InvalidOperationException($"Output converter registration '{id}' must use the Converter ID as its service key.");
}
var duplicate = registrations
.GroupBy(x => x.Descriptor.Id, StringComparer.OrdinalIgnoreCase)
.FirstOrDefault(x => x.Count() > 1);
if (duplicate != null)
throw new InvalidOperationException($"Output converter ID '{duplicate.Key}' is registered more than once or differs from another registration only by case.");
}
internal static void ValidateDescriptor(OutputConverterDescriptor descriptor)
{
if (string.IsNullOrWhiteSpace(descriptor.Id))
throw new InvalidOperationException("Output converter IDs cannot be empty.");
if (descriptor.SourceType.ContainsGenericParameters || descriptor.ResultType.ContainsGenericParameters)
throw new InvalidOperationException($"Output converter '{descriptor.Id}' cannot use open-generic source or result types.");
}View on GitHub (pinned to fe9217bdfa)