Upgrading from 1.x to 2.0

Elsa 2 contains many breaking changes compared with Elsa 1. This document tries to capture the most important changes. If you encounter anything that is missing here, please submit an issue or submit a PR. Thanks!

For an overview of what's new in Elsa 2, checkout this blog post.


When upgrading from v1 to v2, it is recommended to create a new project instead of updating the packages. Workflows and its related models in v2 are vastly different and are not compatible with what you may have in your v1 database.

By setting up a new project, you will be able to manually recreate the workflows.

API Changes

As you recreate your workflows, you will need to copy your custom activities into the new project. When you do, you will have to update each activity's code so that it compiles again. Here are the most important changes when writing activities in v2.


In v1, you decorate activity classes with ActivityDefinition. This has changed in v2 to ActivityAttribute or one of its derivatives such as ActionAttribute, TriggerAttribute and JobAttribute.

For activities that perform an action, use the ActionAtribute or ActivityAttribute and setting its Traits property to ActivityTraits.Action.

For blocking activities that represent a trigger, use the TriggerAtribute or ActivityAttribute and setting its Traits property to ActivityTraits.Trigger.

For activities that represent a job that perform an action and then awaits the result (causing the workflow to get suspended), use the JobAtribute or ActivityAttribute and setting its Traits property to ActivityTraits.Job.

Activity Icon

In Elsa 2, you no longer specify an icon anymore. This should now be done in the client app by implementing a designer plugin (not yet documented).

Activity Runtime Description

As is the case with the icon property, the RuntimeDescription property is gone as well and should instead be controlled from the client app by implementing a designer plugin (not yet documented).

Activity Properties

In v1, you decorate activity properties with the ActivityPropertyAttribute. In v2, this has changed to ActivityInputAttribute and has a different set of properties to control various aspects when displayed in the Activity Editor UI.

In v1, you use the Type property to control what input UI would be displayed. In v2, use the UIHint property and any value provided in the ActivityInputUIHints static class.

In v1, you had to use GetState and SetState calls when implementing property setters and getters. In v2, you no longer need to do this (except when implementing Composite Activities).

For example, the following is a valid activity property:

[ActivityInput(Hint = "The text to write.", UIHint = ActivityInputUIHints.SignleLine)]
public string? Text { get; set; }

Activity Output

In v1, an activity provides output using the Output object.

In v2, an activity provides output by exposing a public property that is marked with the ActivityOutput attribute.

Furthermore, if that property is named Output, it is automatically sent as input into the next activity to execute.


In V2, context.CurrentScope.LastResult and the lastResult() JS function have been removed. Instead, you now use the input JS variable to access the output of the last executed activity.

Workflow Execution

In v1, the service to execute a workflow is IWorkflowInvoker.

In v2, the service to execute workflow is IWorkflowRunner and works similarly.

However, IWorkflowRunner does not have any trigger methods. These have been moved into a newer, high-level service called the IWorkflowLaunchpad.

The workflow launchpad service takes care of selecting the right workflows to execute. It allows you to collect workflows to execute as well as to execute them directly. Besides executing workflows directly, they can als be dispatched using IWorkflowDispatcher.

When you dispatch a workflow for execution, it gets executed asynchronously in the background. The default implementation sends a message to a queue, which gets processed in the background using Rebus.


In v1, you would type your activity properties as e.g. IWorkflowExpression<string> in order to allow the user to provide an expression that, when evaluated at runtime, would return the actual value to use.

This has changed in v2, where you directly specify the final type of the property (e.g. string) and no longer evaluate this yourself. This is now a concern of the workflow engine itself. This means that when your activity executes, any expression associated with your activity property will already have been evaluated.

You can control what expressions are supported using ActivityInputAttribute by specifying the DefaultSyntax and SupportedSyntaxes properties.

For example:

[ActivityInput(Hint = "The text to write.", DefaultSyntax = SyntaxNames.Literal, SupportedSyntaxes = new[]{ SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? Text { get; set; }

With that in place, the user will be able to enter a literal string value in some text field, or switch to code and start typing JavaScript or Liquid expressions.


Please let us know!