# Elsa Workflows vs Workflow Engine NEO

Source: https://workflowengine.io/blog/elsa-vs-workflow-engine/
Site index for agents: https://workflowengine.io/llms.txt (products, licensing, documentation hosts, package names).
Author: Rylee Soll, Senior backend developer and Workflow Engine NEO product team lead. Elsa Workflows 3 as of August 2026 · Workflow Engine NEO v22.
Published: 2026-08-17.

Compare Elsa Workflows 3.7 with Workflow Engine NEO across process modeling, human tasks, multitenancy, operations, and cost, read against Elsa 3.7 and NEO v22.

## Key takeaways

Workflow Engine NEO fits products centered on business state, participant decisions, forms, and tenant routing. Elsa fits when technical orchestration drives most changes: events, integrations, background work, and tool calls. Both support durable execution, definition versioning, visual and code-first authoring, child workflows, and multitenancy. For the approval SaaS examined here, NEO supplies Commands, permission checks, Inbox, Forms Plugin, tenant routing, and built-in locks. Elsa needs an external task-and-forms layer plus configured distributed runtime, locking, and scheduling across nodes. Test the same production process in both, then map next year's backlog. Record what the team must write, configure, deploy, operate, and learn.

I began this comparison with a practical question: if I were choosing a workflow engine for a product that my team
would operate for years, which responsibilities would each engine take over, what would we still have to build, and
which model would be easier to live with? My short answer is that [Workflow Engine NEO][wfe-neo-edition]
usually fits products centered on business state, human decisions, forms, and tenant isolation, while
[Elsa Workflows 3.7][elsa-architecture-overview-high-level-architecture] usually fits products centered
on technical orchestration, events, integrations, and code-first activity graphs.

A production workflow waits for users and external systems, survives restarts, fires timers, resumes in the right place,
and continues as definitions change. When people participate, the product also needs assignments, permissions, forms,
deadlines, and a history that explains each decision.

Engine selection itself is an architecture decision. The engine determines where execution state lives,
how work resumes after a wait, who owns timers, what can run safely on several nodes, and how much responsibility remains
in application code. SaaS makes this boundary especially visible: the same workflow may run for thousands of objects
across tenants, while identity, data, jobs, forms, and audit must stay isolated together.

Once adopted, a workflow engine normally remains part of the product for years. I consider replacing it after release a
last resort: live processes, authorization rules, background work, decision history, and integrations already depend on
it, and upgrades must happen while the system continues to serve users. The cost and migration risk can make that
replacement impractical for a mature product.

The comparison follows responsibilities rather than feature catalogs. I use the same question throughout the article:
what does the engine provide, what must my team design, implement, and operate around it, and which model can we maintain
safely for years? The remaining sections build the full answer from the process model, application code, storage, and
operational behavior.

I compare Workflow Engine NEO with Elsa 3.7. I do not anchor the Workflow Engine side to one release:
its release history and
[public compatibility policy][wfe-backward-compatibility]
extend back to 2014. I use human-centric SaaS as a demanding test case because it brings multitenancy, forms,
participant permissions, and updates to live processes into the same system. Those concerns affect the weight I give
each criterion, but they do not limit the comparison to SaaS.

For the concrete example, I use a document approval process. An employee submits a document, and a manager can approve
or reject it. If the manager misses the deadline, the process escalates to a senior manager. I intentionally leave the
senior manager's next actions out of the example so they do not obscure the comparison. I also look separately at
multitenancy, form integration, and AI agents in two roles: an agent can make a decision as a process participant, or the
workflow can invoke and coordinate agents as part of its work.

## What each product includes

The diagram below separates the components supplied by each workflow product from the responsibilities that remain
with the host application.

![The components of Workflow Engine NEO and Elsa Workflows, with application-owned responsibilities shown separately](https://workflowengine.io/images/blog/elsa-vs-workflow-engine/workflow-engine-vs-elsa-product-components.svg)

**Workflow Engine NEO**

Workflow Engine NEO combines an embeddable
[Workflow Runtime][wfe-workflow-runtime-concept] with
[persistence][wfe-persistence-persistence-and-workflow-runtime]. Its separately adoptable product features include
[Workflow Designer][wfe-workflow-designer-what-it-is], the
[Workflow Engine HTTP API][wfe-http-api-what-it-is],
[Inbox][wfe-plugins-and-extensions-inbox], and the
[Forms Plugin][wfe-attachable-forms-what-it-is]. The application keeps its identity model, domain services, and
business data. [Actions][wfe-action-how-an-action-works] call those services, while
[Rules][wfe-rule-rule-and-actor] check identities for command permissions.

**Elsa Workflows**

Elsa combines an
[activity-graph runtime][elsa-execution-model-start-run-pause-resume] with
[Management and Runtime stores][elsa-persistence-layer],
dispatching, and scheduling. Elsa Server and Elsa Studio can be added for API hosting, visual authoring, and
management screens. A minimal code-first application does not need every optional component in the
[hosting model][elsa-hosting-model]. The host application or an existing task platform still owns human-task
assignment, permissions, Inbox, forms, and business audit. Elsa
[custom activities][elsa-custom-activities-resolving-services] connect the workflow to domain services.

## Short answer

The practical choice depends on what drives most of the product's complexity and changes most often: business state
and decision authority, or technical orchestration.

### Overall recommendation

**Workflow Engine NEO:**

Workflow Engine NEO treats a process as the lifecycle of a business object. [Workflow Runtime][wfe-workflow-runtime-concept]
stores the document's [current Activity and State][wfe-activity-activity-properties] and returns the
[Commands][wfe-command-how-commands-connect-to-transitions-in-a-scheme] available at that point. To check permissions,
the application represents each user or agent with an Identity; Workflow Runtime evaluates that Identity against
[Actors, Rules, and Restrictions][wfe-rule-how-a-rule-works]. In products built around forms and approvals, this allows
the team to model the workflow with business concepts such as States, Commands, and participant permissions. The same
tenant context scopes HTTP API requests, workflow instances, timers, history, Inbox items, and Forms Plugin data.

**Elsa:**

Elsa Workflows treats a workflow as a graph of executable activities: what to run, which event to wait for, and which
path to schedule next. Its [execution model][elsa-execution-model-the-four-execution-paths] covers direct execution,
persistent runtime execution, background dispatch, and resumption through triggers and bookmarks. This model fits
integration-heavy processes well, particularly when a team wants typed code-first workflows and already owns its task,
forms, identity, and tenant platform.

**Verdict:**

When approvals and documents dominate the product, Workflow Engine NEO keeps Commands, participant permissions, Inbox,
Forms Plugin, tenant routing, timers, and process locking inside the workflow product. The product team therefore writes
less human-task and tenant-routing code.

When workflows mainly coordinate events, integrations, background work, model calls, and tools, Elsa Workflows provides
the activity graph, typed code-first authoring, and detailed technical diagnostics. For a human-centric product, your
team or an existing task platform must supply user tasks, assignment, forms, and business audit. Multi-node execution
also requires explicit choices for distributed runtime, locking, and scheduling.

Choose the engine whose native model matches the part of the product that changes most often and consumes most of the
engineering effort. The table below summarizes how that distinction appears in each criterion and links to the detailed
sections.

### Criteria at a glance

Table: Elsa Workflows 3.7 and Workflow Engine NEO decision summary

| Area | Workflow Engine NEO | Elsa Workflows |
|---|---|---|
| [How the workflow models differ][section-workflow-model] | Workflow Engine NEO models a business object's lifecycle through Activities, States, and outgoing Command or timer Transitions. | Elsa Workflows models execution as an activity graph with branches, waits, and bookmark-based resumption. |
| [One approval process in both engines][section-approval-process] | Workflow Engine NEO covers process execution, available decisions, command authorization, Inbox, Forms Plugin, and approval history. | Elsa Workflows covers graph execution and durable waits; an external task layer owns assignment, permissions, Inbox, forms, and business audit. |
| [AI agents in workflows][section-ai-agents] | Identity, Actors, Rules, and Restrictions control which Commands people and AI agents can execute. | Elsa Agents optionally adds Activities for model and tool calls. |
| [Visual workflow authoring and editor embedding][section-visual-designer] | Workflow Designer embeds in JavaScript, React, Angular, and Blazor with full-node control. | Elsa Studio loads Blazor WebAssembly outside Blazor, making those pages heavier. |
| [Code-first workflow authoring][section-code-first] | Code and Workflow Designer edit stored Schemes; rebuilds must preserve visual changes. | WorkflowBase and Elsa Studio drafts remain separate; teams must choose the authoritative source. |
| [Composition and child workflows][section-process-composition] | Inline Activities reuse Schemes; Subprocesses form a managed process tree. | Definitions provide inline reuse; child workflows run directly, in the background, or in bulk. |
| [Long-running waits][section-durable-waits] | Idled instances need no thread or per-process memory; TimerManager polls due Timers in batches. | Stored bookmarks outlive the execution context; multi-node scheduling adds Quartz.NET or Hangfire with shared storage. |
| [Deadline control, reminders, and escalation][section-deadline-escalation] | Timer-triggered Transitions model reminders and deadlines; target Actions send notices. | An SLA sequence adds time events; external task code closes, expires, or reassigns tasks. |
| [Concurrent events and process locking][section-concurrency-locking] | Built-in persistence locks serialize Process Instance execution and due Timers across nodes. | Multi-node resumes need Distributed Runtime and shared locking; external task updates must be atomic. |
| [Scheme versions and live process updates][section-scheme-versioning] | Running instances keep stored Scheme versions; updates are explicit or lazy at marked Activities. | Publishing sets the default for new starts; alteration plans migrate selected running instances. |
| [Administrative changes to running processes][section-running-process-operations] | Workflow Runtime offers direct State, Activity, Parameter, and resume operations for individual Process Instances. | Elsa Alteration plans apply activity and variable changes across filtered instance sets. |
| [Multitenancy architecture][section-multitenancy] | Tenant routing covers four placement modes and scopes the HTTP API, workflow data, Timers, Inbox, Forms Plugin, approvals, and permissions. | Management and Runtime stores, Elsa Identity, and configured scheduler jobs are tenant-aware; the team isolates external tasks and forms. |
| [Onboarding complexity][section-onboarding] | NEO uses fewer concepts for approvals. | Elsa Workflows requires more execution and infrastructure concepts: resumption, dispatch, scheduling, and persistence. |
| [Operational diagnostics][section-diagnostics] | Transition History explains State changes by route, trigger, actor, executor, and time. | Elsa's workflow journal records the technical flow; the application supplies the business decision audit. |
| [Embedding and architectural impact][section-embedding-architecture] | Workflow Runtime embeds in existing services; its UI and API integrations remain optional. | Durable background execution and visual operations add stores, scheduling, dispatch, APIs, and Studio. |
| [Licensing and total cost][section-licensing] | NEO's price covers Forms Plugin, Inbox, tenant routing, Timers, and multi-server coordination. | Elsa Core has no runtime fee; total cost includes task/forms code, multitenancy, distributed operations, and optional paid support. |

## How the workflow models differ

**Why it matters:**

The engine model determines how a team reads and changes a process. Each notation exposes a different set of concepts
and connections for the same business route.

The example route starts when an employee submits a document. The manager chooses `Approve` or `Reject`; a missed
deadline escalates the process to a senior manager. The senior manager's later actions are omitted. The top of the
diagram shows the common business route. The lower panels express it in each engine's notation.

![The same approval process modeled in Workflow Engine NEO and Elsa Workflows](https://workflowengine.io/images/blog/elsa-vs-workflow-engine/workflow-engine-vs-elsa-approval-model.svg)

**Workflow Engine NEO:**

The process contains [Activities][wfe-activity-activity-properties] named `DraftActivity`, `ManagerReview`, `Approved`,
`Rejected`, and `Escalated`. Each Activity can have a business-facing State; for example,
`ManagerReview` maps to `Under review`. A [Command][wfe-command-how-commands-connect-to-transitions-in-a-scheme] named `Submit` moves the process from
`DraftActivity` to `ManagerReview`. The `Approve` and `Reject` Command-triggered Transitions leave `ManagerReview`.

Both decisions are restricted to the manager. Each restriction on those Transitions refers to the `Manager` Actor. Workflow Runtime
resolves its [Rule][wfe-rule-rule-and-actor], which checks the user's Identity. A
[Timer][wfe-timer-how-timers-work-in-a-scheme] triggers an outgoing Transition at the approval deadline. Two restricted
Command-triggered Transitions and one Timer-triggered Transition together leave `ManagerReview`.

**Elsa:**

After `SubmitDocument` completes, a `Fork` configured with `WaitAny` schedules two branches. In one branch, `RunTask`
[creates a bookmark and waits for the manager's result][elsa-runtask]. `FlowDecision` evaluates that result and routes
execution to application activities that record approval or rejection. In the other branch, a `Timer` or `Delay` waits
for the deadline; after completion, the graph schedules the escalation activity. When one branch completes, Elsa
Workflows completes `Fork(WaitAny)`, continues execution, and cancels the still-running branch in its
[parallel execution model][elsa-timeout-pattern].

The product team records the document's status in domain data or through explicit activities; the control-flow activities
represent execution rather than domain status.

**Verdict:**

Workflow Engine NEO models the route as an Activity with restricted Command Transitions and a Timer-triggered Transition.
Elsa Workflows models it as executable activities, competing branches, and bookmark-based resumption. The primary model
itself describes either business positions and decisions or execution steps and control flow.

## One approval process in both engines

**Why it matters:**

The diagram shows the workflow, but leaves the integration work off the page. The product must list available decisions,
verify the manager's permission, provide the task and form, accept the answer, and handle the deadline. Responsibilities
outside the engine become code or infrastructure for the product team.

![Application integration for the approval process in both engines](https://workflowengine.io/images/blog/elsa-vs-workflow-engine/workflow-engine-vs-elsa-approval-integration.svg)

**Workflow Engine NEO:**

The employee and manager use the product's existing UI. Through
[Workflow Runtime][wfe-workflowruntime-query-commands-and-actors] or the
[Workflow Engine HTTP API][wfe-http-api-what-it-is], the application lists available Commands and sends `Submit`,
`Approve`, or `Reject`. Workflow Runtime evaluates Restrictions with the [Rule Provider][wfe-rule-provider] to decide
which Commands the manager sees. When requested, it repeats the check as the manager submits a decision. By default,
Workflow Runtime persists Activity, State, changed persistent parameters, Timer registrations, and Transition History.

Workflow Engine NEO provides
Inbox,
[Forms Plugin][wfe-attachable-forms-what-it-is], and
[approval history][wfe-approval-plugin-track-approval-history] for the product UI. Approval logic connects through
an [Action Provider][wfe-action-how-an-action-works], which lets the workflow call product services, and a Rule Provider,
which evaluates whether an Identity matches the Rules referenced by
[Transition Restrictions][wfe-transition-properties].

The document remains in the application's database; persistence stores process state. When the deadline Timer
becomes due, [TimerManager evaluates the escalation transition][wfe-timer-timer-execution]. When Conditions pass, the
transition changes State and its Action sends the notification. A multi-node deployment keeps TimerManager and persistence
unchanged; no other component is added or operated in production.

**Elsa:**

The application starts the workflow through Elsa Workflows' Runtime API. The
[external application interaction guide][elsa-runtask]
documents the division: `RunTask` creates a Task ID and bookmark, sends a request to an application handler, and suspends
until completion. A parallel `Timer` creates a deadline bookmark; the scheduler registers its resume time.

On one node, Elsa Workflows' `LocalScheduler` keeps timer work in memory and rebuilds schedules from persisted bookmarks
after restart. A multi-node deployment requires
[Distributed Runtime][elsa-distributed-runtime] and distributed
[locking][elsa-distributed-locking]. Scheduled work can use clustered
[Quartz.NET][elsa-quartz-clustered-mode] or [Hangfire][elsa-hangfire-store] backed by durable shared storage.
Your team operates and monitors the scheduler and its shared storage. The
[Management store][elsa-management-store-source] holds definitions, instances,
[workflow state][elsa-workflow-instance-state-source], and [incidents][elsa-workflow-state-incidents-source]; the
[Runtime store][elsa-runtime-store-source] holds bookmarks, triggers, and execution records.

The product team must build manager assignment, task permissions, Inbox, forms, and business decision history in its
task/forms layer or connect an existing task platform. After validating the manager, that layer sends the Task ID and
result to Elsa Workflows' task-completion endpoint, resuming `RunTask`. If the deadline wins, the task layer closes or
expires the task while a custom activity writes the escalation to domain data.

**Verdict:**

Workflow Engine NEO covers process execution and much of the participant-facing workflow: available decisions, command
authorization, Inbox, forms, and approval history. Elsa Workflows covers graph execution and durable waits, while your
application or an existing task platform owns user tasks, their permissions, forms, and business audit. For this approval
process, Workflow Engine NEO includes more ready-made approval functionality and needs less configuration.

## AI agents in workflows

**Why it matters:**

AI agents now enter business processes in two distinct roles. An agent may make a decision alongside people, where
authority and audit matter most. A workflow may also invoke and coordinate one or more agents, where model and tool
integration matter more.

**Workflow Engine NEO:**

The host represents a decision-making agent with an Identity. An adapter calls the model and maps its response to a
Command returned by Workflow Runtime for that Identity. Workflow Runtime evaluates Actors, Rules, and Restrictions;
the [Rule Provider][wfe-rule-provider] supplies the Rule implementation. A Scheme can expose the `ProposeApproval`
Command to the agent and reserve the `Approve` Command for managers.

Model calls can run as an [Action][wfe-action-how-an-action-works] or
[custom Activity][wfe-custom-activity-execution]. The Scheme coordinates them through Transitions, Timers, and
Conditions; your team supplies model and tool integrations.

**Elsa:**

An external agent can return a result for a [`RunTask`][elsa-runtask] through the task layer. Elsa Workflows matches its
Task ID to a stored bookmark and resumes the workflow. The task layer authenticates the agent, limits its decisions,
applies human-confirmation policy, and writes the business audit.

[Elsa Agents][elsa-agents-extension] is an optional extension that turns configured agents into activities and selected
skills into model-callable functions. Its activities compose with other steps and bookmark-based waits but do not
persist internal chat history.

**For both engines:**

Execution journals are not complete AI decision records. Store model version, prompt or instructions, context, tool
calls, and result in the product's business audit.

**Verdict:**

Workflow Engine NEO fits agent decisions beside people through Commands, Actors, Rules, and Restrictions. Elsa
Workflows supplies more of the model-invocation path through Elsa Agents.

## Visual workflow authoring and editor embedding

**Why it matters:**

Embedding determines how an editor fits the frontend, authentication, design system, page weight, and product-specific
blocks.

**Workflow Engine NEO:**

The [Workflow Designer][wfe-workflow-designer-what-it-is] edits the [Scheme][wfe-scheme-concept] that
[Workflow Runtime][wfe-workflow-runtime-concept] executes. It is a standalone JavaScript component for
plain JavaScript, with
React and
Angular wrappers, plus
Blazor interop.

Its API validates Schemes and supports
read-only mode, localization, and autocomplete. A
Custom Activity Type supplies
[execution code][wfe-custom-activity-execution], parameters, a configuration form, and a complete SVG node template.
The Designer lists Actions from the Action Provider and permission Rules from the Rule Provider.

**Elsa:**

[Elsa Studio is a modular Blazor application][elsa-architecture-overview-studio]. Its
[custom-elements host][elsa-custom-elements-which-surface-to-embed] embeds the editor, definition and instance lists,
and instance viewer; its [React wrapper][elsa-custom-elements-react-wrapper] uses those elements, which
load the Blazor WebAssembly runtime.

Custom activities use typed inputs and outputs. Studio supports
[custom input components][elsa-studio-ui-customization] and
[custom icons][elsa-custom-icons-how-the-icon-path-works], including SVG; its documented extensions do not
include complete per-activity canvas-node replacement.

**Verdict:**

For JavaScript, React, and Angular products, Workflow Designer fits the host stack more directly and supports full-node
control. Elsa Studio adds definition and instance-management surfaces, but its Blazor WebAssembly runtime makes the page
heavier in those products. In Blazor, Elsa Studio integrates more natively.

## Code-first workflow authoring

**Why it matters:**

Code-first and visually authored definitions can be versioned and tested. Developers should be able to read and change
them without reconstructing runtime behavior in their heads. Code and visual editing may share one executable
definition or create separate sources of truth.

### Workflow Engine NEO example

**Workflow Engine NEO:**

[ProcessDefinitionBuilder][wfe-builder-definition]
defines Activities and their States, Commands, Actors, timers, and Transitions through its fluent API. `.Ref(out ...)`
captures typed Activity and Command objects so Transitions can reuse the references. The
`ProcessDefinition` is the model used by Workflow Runtime and Workflow Designer.

Names handled by the registered [Action Provider][wfe-action-how-an-action-works] and [Rule
Provider][wfe-rule-provider] remain string contracts. The compiler cannot detect a mismatched Scheme reference, so
teams should validate those names in integration tests.

The approval Scheme:

```csharp filename="DocumentApprovalScheme.cs"
using System;
using OptimaJet.Workflow.Core.Model;
using OptimaJet.Workflow.Core.Model.Builder;

var scheme = ProcessDefinitionBuilder.Create("DocumentApproval")
    .CreateActor("Employee", "IsEmployee").Ref(out var employee)
    .CreateActor("Manager", "IsManager").Ref(out var manager)
    .CreateCommand("Submit").Ref(out var submit)
    .CreateCommand("Approve").Ref(out var approve)
    .CreateCommand("Reject").Ref(out var reject)
    .CreateTimer("Deadline")
        .Interval(TimeSpan.FromDays(3)).Ref(out var deadline)
    .CreateActivity("Draft").Initial().State("Draft").Ref(out var draft)
    .CreateActivity("ManagerReview")
        .State("UnderReview").Ref(out var review)
    .CreateActivity("Approved").Final()
        .State("Approved").Ref(out var approved)
    .CreateActivity("Rejected").Final()
        .State("Rejected").Ref(out var rejected)
    .CreateActivity("Escalated").Final()
        .State("Escalated").Ref(out var escalated)
    .CreateTransition("Submit", draft, review)
        .TriggeredByCommand(submit)
        .CreateRestriction(employee, RestrictionType.Allow)
    .CreateTransition("Approve", review, approved)
        .TriggeredByCommand(approve)
        .CreateRestriction(manager, RestrictionType.Allow)
    .CreateTransition("Reject", review, rejected)
        .TriggeredByCommand(reject)
        .CreateRestriction(manager, RestrictionType.Allow)
    .CreateTransition("Escalate", review, escalated)
        .TriggeredByTimer(deadline)
    .ProcessDefinition;
```

The example assumes a registered Rule Provider supplies `IsEmployee` and `IsManager`. Actions, forms, and the senior
manager's route are omitted.

The resulting `ProcessDefinition` can be saved as a Scheme. Workflow Designer can reopen that Scheme, and code can load
it as a `ProcessDefinition`. Workflow Designer updates the stored Scheme, not fluent C# calls. Teams using both paths
should modify the saved Scheme or account for visual changes when rebuilding it from code; otherwise, the rebuild will
overwrite them.

### Elsa code-first example

**Elsa:**

An Elsa [code-first workflow][elsa-workflow-providers-code-first-workflows] inherits from `WorkflowBase` and composes
activities. `Input<T>`, `Output<T>`, and `Variable<T>` [retain declared .NET types][elsa-activity-data]. It combines
`RunTask`, review and deadline branches, and decision routing.

```csharp filename="DocumentApproval.cs"
using Elsa.Scheduling.Activities;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Activities;
using WorkflowTimer = Elsa.Scheduling.Activities.Timer;

public sealed class DocumentApproval : WorkflowBase
{
    protected override void Build(IWorkflowBuilder workflow)
    {
        var decision = workflow.WithVariable<object>("Decision");

        var review = new Sequence
        {
            Activities =
            {
                new RunTask("ManagerReview")
                {
                    Result = new Output<object>(decision)
                },
                new If(context =>
                    Equals(decision.Get(context), "Approve"))
                {
                    Then = new RecordApproval(),
                    Else = new RecordRejection()
                }
            }
        };

        var sla = new Sequence
        {
            Activities =
            {
                new WorkflowTimer(TimeSpan.FromDays(3)),
                new EscalateToSeniorManager()
            }
        };

        workflow.Root = new Sequence
        {
            Activities =
            {
                new SubmitDocument(),
                new Fork
                {
                    JoinMode = ForkJoinMode.WaitAny,
                    Branches = { review, sla }
                }
            }
        };
    }
}
```

Application activities represent domain updates, decision history, and escalation. Teams supply an external task/forms
layer. It assigns the manager, presents Inbox and forms, checks permission, reports the `RunTask` result, and closes or
replaces user tasks.

Studio can display a workflow registered in an Elsa Server host. It saves edits as a [persisted draft][elsa-provider-studio]
without changing `WorkflowBase`; a provider reload can overwrite that draft. Teams must choose C# or Studio as the source
of truth.

### Authoring tradeoffs

**For both engines:**

The snippets contain the workflow definitions. Completing the Workflow Engine NEO example requires its Rule Provider
implementation, Actions, and forms; completing the Elsa example requires an external task/forms layer and four
application-activity implementations.

**Verdict:**

For the approval process examined in this article, Workflow Engine NEO uses Activities with business States, Commands,
permissions, and Transitions. This Elsa implementation connects `Sequence`, `Fork`, `RunTask`, `If`, `Timer`, a result
variable, and application activities. Workflow Engine NEO's definition is more compact for human approval; Elsa's
activity graph may be clearer for integration orchestration.

Elsa retains declared .NET types for inputs, outputs, and variables. Mixed code and visual editing needs an authoritative
artifact. Workflow Designer edits the stored Workflow Engine NEO Scheme, and code can load its `ProcessDefinition` and
modify it. Elsa Studio persists edits as a draft without changing `WorkflowBase`; a provider reload can overwrite it.
With Elsa, teams must choose C# or the Studio draft as authoritative.

## Process composition and child workflows

**Why it matters:**

Composition keeps workflows understandable. Distinguish inline workflow reuse from child workflows with distinct state,
history, and version.

**Workflow Engine NEO:**

An [Inline Activity][wfe-scheme-inlining-what-scheme-inlining-is] references a Scheme. At build time, the
pipeline expands it into the parent Scheme with parameter mappings.

[Subprocesses][wfe-subprocess-what-a-subprocess-is] are separate Process Instances in a managed tree. Fork Transitions
govern lifecycle, parameter transfer, and parent-state control. Runtime APIs retrieve the tree and include child Commands
in a root-process query.

For fan-out (one Subprocess per item), the Loops Plugin iterates Parameter values. A
ForkStart Transition creates each child in the loop; the parent waits until a Condition confirms all have finished.

An Action can [start an ordinary independent process][wfe-basic-plugin-process-control-example] through
WorkflowRuntime, read its state, [list its commands][wfe-workflowruntime-query-commands-and-actors],
and execute them. The application stores the other `ProcessId` and defines the relationship, waiting behavior, and joint
lifecycle.

**Elsa:**

A published definition marked Usable as activity is reusable inside the parent
instance with inputs, outputs, and outcomes. [ExecuteWorkflow][elsa-activity-reference-composition] and
[DispatchWorkflow][elsa-dispatch-workflow-activity-using-code] create a child directly or by background dispatch, pass
input, and can await its result. [BulkDispatchWorkflows][elsa-bulk-dispatch-workflows-what-it-does] supports fan-out with
one child per collection item.

Independent workflows can start through the dispatcher or Runtime API. The application records their relationship and
queries status by instance or correlation ID; Elsa manages triggers and bookmarks. Its instance API does not list
available named business decisions and execute one as a Command.

**Verdict:**

Both support graph composition: Workflow Engine NEO uses Inline Activities; Elsa makes definitions reusable as
activities. Both support child workflows, fan-out, and independent launches. Workflow Engine NEO's Subprocess tree has
parent-state control, parameter transfer, and child Commands in root-process queries. Elsa uses activities for direct,
background, and bulk child execution. Independent relationships remain application-owned.

## Long-running waits

**Why it matters:**

A process may wait for months. Supporting thousands requires releasing worker threads and in-memory contexts, surviving
restarts, and resuming instances with their state and deadlines intact.

**Workflow Engine NEO:**

Workflow Engine NEO stores a [durable execution][wfe-durable-execution] checkpoint:
current Activity and State, Process Status, persistent Parameters, recorded Transition History, and Timer registrations.
An [`Idled` instance][wfe-process-instance-lifecycle] uses no execution thread or per-process application memory.
[Workflow Runtime][wfe-workflow-runtime-concept] reloads it when a Command, Timer, or API operation starts the next
Transition.

TimerManager polls due Timers in batches rather than keeping an in-memory job per wait. In
[multi-server mode][wfe-clustering-what-it-is], runtimes take turns processing Timer batches
from the shared database. If one node fails, another runtime continues Timer processing. Timers need no recovery; only
Process Instances left `Running` enter recovery. No external scheduler or coordination service is required.

**Elsa:**

With persistent runtime storage, Elsa [persists workflow state and bookmarks][elsa-long-waits]. Between bursts, its
`WorkflowExecutionContext` [is absent from memory][elsa-execution-context]; a stimulus reloads state and resumes the
instance. [Memory stores lose this state on restart][elsa-long-running-workflows-common-mistakes] and cannot support
durable waits.

The [default local scheduler][elsa-local-scheduler] creates [one in-memory task per inline `Timer` or `Delay`
bookmark][elsa-local-scheduler-source], with the [workflow ID, bookmark ID, and absolute resume
time][elsa-bookmark-scheduler-source]. Memory use grows with scheduled waits. After restart, Elsa [recreates
tasks][elsa-local-schedule-restore] for those stored times. Multi-node deployments require [Distributed
Runtime][elsa-distributed-runtime], [distributed locking][elsa-distributed-locking], and [clustered
Quartz.NET][elsa-quartz-clustered-mode] or [Hangfire][elsa-hangfire-store] with durable shared storage.

**Verdict:**

Both persist waiting state and release worker threads and execution contexts. For many Timer waits, Workflow Engine NEO
avoids per-wait scheduled objects and extra multi-node scheduling infrastructure. Elsa requires the team to operate
Quartz.NET or Hangfire and shared storage across nodes.

## Deadline control, reminders, and escalation

**Why it matters:**

Approval policies can combine recurring reminders, a final deadline, and escalation. They change workflow
structure and application code differently in each product.

![Recurring reminders and deadline escalation in both engines](https://workflowengine.io/images/blog/elsa-vs-workflow-engine/workflow-engine-vs-elsa-reminders-escalation.svg)

**Workflow Engine NEO:**

In this Scheme, each reminder or deadline is a [Timer-triggered
Transition][wfe-timer-how-timers-work-in-a-scheme] from `ManagerReview`. The reminder returns to `ManagerReview`; an
Action there can read the executed Transition and send the notification. The deadline moves the process to the
`Escalated` Activity, where an Action sends the escalation notice. Marking that Timer as [not
overridable][wfe-timer-not-override-if-exists] preserves its original execution time after the reminder loop. The
application supplies these Actions and a Rule Provider for Identity checks during Command execution.

**Elsa:**

`Fork(WaitAny)` runs a `RunTask` manager-decision branch beside a `Timer`-based SLA branch. [The Fork continues when
either completes][elsa-timeout-pattern] and cancels the unfinished branch. Recurring
reminders make the SLA branch wait, send a notification, and wait again until the deadline; a sibling branch would
finish `WaitAny` on the first reminder.

Elsa controls waits and cancellations, not the application-owned human-task record. The external task layer
creates and assigns the manager task. On SLA completion, that layer closes or expires the task and creates or
reassigns a senior-manager task under application policy.

**Verdict:**

Both handle one deadline directly. With more events, Workflow Engine NEO adds Timer-triggered Transitions from
`ManagerReview`; Elsa extends the SLA sequence and coordinates task-layer updates. For the approval process examined
in this article, Workflow Engine NEO keeps the diagram and surrounding code simpler.

## Concurrent events and process locking

**Why it matters:**

A manager decision and deadline can arrive together. Handlers must not advance the same Process Instance
along both routes.

**Workflow Engine NEO:**

Before a Transition runs, its Command or due Timer [locks the Process Instance
through persistence][wfe-process-lock] and moves its status from `Idled` to `Running`. Only one execution for that
Process Instance succeeds across nodes sharing a database; any retry is evaluated against the new Activity. If the
deadline wins, `Approve` is invalid. If approval wins, entering `Approved` clears the deadline Timer registration.
Process Instance locking resolves this race; [clustering][wfe-clustering-what-it-is] separately locks each due Timer
record so only one node executes it.

**Elsa:**

When one branch completes, `Fork(WaitAny)` cancels the other. `WaitAny` does not serialize hosts that
loaded the same instance concurrently. Multi-node deployment needs a [distributed workflow
runtime][elsa-distributed-runtime] and [shared distributed lock][elsa-distributed-locking]. Cache invalidation and
clustered scheduling remain separate components.

The distributed runtime serializes instance execution. The winner completes the Fork, removing the other
bookmark before [a later request can resume that branch][elsa-execution-model-how-resume-matching-works]. Canceling
`RunTask` does not update the application-owned task. The external task layer must atomically complete or expire it and
reject a late response.

**Verdict:**

Workflow Engine NEO is simpler out of the box: Process Instance and Timer-record locking are built in. A multi-node
Elsa deployment requires a distributed runtime and shared locking, while its external task layer must make updates
atomic.

## Scheme versions and live process updates

**Why it matters:**

A process can outlive several product releases. A new definition should affect running instances only when the team
explicitly migrates them.

**Workflow Engine NEO:**

Workflow Engine NEO binds each running Process Instance to a stored Scheme version; new instances receive the latest
published definition. Its [process versioning model][wfe-process-versioning-what-it-is] lets application code migrate an
instance explicitly or lazily at an Activity marked `IsAutoSchemeUpdate`. This limits lazy migration to named Activities
in the business process.

**Elsa:**

Elsa tracks [latest and published definition versions][elsa-definition-lifecycle] separately. Publishing changes the
default version for new REST starts, not existing workflow instances. A [`Migrate` Alteration][elsa-alterations-alteration-types]
moves selected instances to a specific version of the same definition; filtered alteration plans provide an operator
workflow for bulk changes.

**Verdict:**

Both protect running work from implicit route changes. With Workflow Engine NEO, the Scheme marks Activities where lazy
migration is allowed. Elsa lets operators select instances and track migration through alteration plans. In either
product, the team must keep application code and data compatible with versions still running.

## Administrative changes to running processes

**Why it matters:**

Support teams sometimes reposition stuck processes, correct data, or resume execution. Editing workflow data directly
is an anti-pattern: it bypasses runtime validation and auditing.

**Workflow Engine NEO:**

Workflow Engine NEO provides [direct state
control][wfe-direct-state-control-what-it-is].
It can [change a Process Instance's State][wfe-set-state], [move it to a chosen Activity and run or skip its
Actions][wfe-set-activity], or [resume from the current or selected
Activity][wfe-resume] without executing it again. Operators can correct a persistent
Parameter. Because direct State changes bypass Command restrictions,
the product team controls access and adds auditing when required.

Applications can invoke these operations in code through [Workflow Runtime][wfe-workflow-runtime-concept] or through
the [HTTP API][wfe-http-api-what-it-is].

**Elsa:**

The [Alterations module][elsa-alterations-alteration-types] can cancel or schedule an activity, modify a variable, or
migrate an instance. `IAlterationRunner` applies immediate changes; its caller then dispatches affected instances. An
[alteration plan][elsa-alteration-plan-execution] selects instances by ID or filter, creates one job per instance, and
records its status and log.

Elsa has no built-in operation that maps a business State to an Activity; repositioning uses activity IDs. Elsa
defaults to in-memory plan stores, job stores, and dispatch. Durable plans require persistent stores and a durable
dispatcher.

**Verdict:**

Workflow Engine NEO is simpler when support needs to repair one Process Instance: it exposes direct State, Activity,
Parameter, and resume operations. Elsa packages interventions as Alterations and adds filtered plans with status and
results for each selected instance.

## Multitenancy architecture

**Why it matters:**

In a SaaS product, API calls, workflow data, timers, background jobs, Inbox, forms, history, and authorization must use
the same tenant context. Shared tables need tenant-aware filters. Dedicated stores need request
routing. Scheduled jobs must restore the correct tenant before resuming a workflow. Storage topology affects
operations: a separate schema creates a physical boundary inside one database, while a separate database supports
independent backup, restore, and data-residency policies. Hybrid routing keeps some tenants in shared storage and places
others in dedicated stores.

![Tenant storage models and isolation boundaries in both engines](https://workflowengine.io/images/blog/elsa-vs-workflow-engine/workflow-engine-vs-elsa-multitenancy.svg)

**Workflow Engine NEO:**

Workflow Engine NEO supports [Logical Tenancy in a shared database and
schema][wfe-multitenancy-logical-tenancy-core-runtime-and-http-api], [Physical Tenancy with a dedicated database or
schema][wfe-multitenancy-physical-tenancy-http-api], and [Hybrid Tenancy that combines
them][wfe-multitenancy-hybrid-tenancy-http-api]. The [multitenancy
feature][wfe-multitenancy-what-it-is] reads the `Workflow-Api-Tenant-ID` header or a configured default; its registry
selects the matching Workflow Runtime and Database Provider. Several logical tenants can share a runtime. A physical
tenant can use its own schema or database and a different supported database type.

Tenant scope covers API operations, Schemes and Forms, Process Instances and Parameters, Timers and Transition History,
Inbox and Approval History, and permissions. The [ASP.NET Core integration][wfe-api-glossary-surfaces] applies the
request's tenant to these operations and records. Direct Workflow Runtime calls must select the runtime for Physical
Tenancy and pass or filter by `TenantId` for Logical Tenancy.

**Elsa:**

Elsa's [multitenancy model][elsa-introduction-multitenancy-levels] supports shared tables filtered by `TenantId` and
separate databases selected through a connection-string factory. The same factory can send some tenants to shared
storage and others to dedicated databases. Elsa has no built-in per-tenant SQL-schema router: EF Core `SchemaName` is
configured for a persistence module when the host registers it.

Tenant context scopes Management and Runtime records, plus users and roles in Elsa Identity. The
Quartz.NET and [Hangfire][elsa-hangfire-tenant] integrations add the tenant ID to scheduled jobs
and restore the context before workflow execution. The team configures
[durable scheduler storage][elsa-hangfire-store], monitors its workers, and includes the job store in backup and
recovery.

For the approval process considered in this article, the task/forms layer is external to Elsa. Your application stores
`TenantId` with tasks, forms, assignments, and decision history; filters reads and writes; checks the user's tenant and
permission at completion; and restores tenant context before resuming the workflow. Elsa's [setup
guide][elsa-setup-configuration] configures tenants, tenant resolution, and Elsa Identity. The external task service
must enforce its own isolation.

**Verdict:**

For the approval SaaS considered here, Workflow Engine NEO applies tenant routing to workflow data, the HTTP API, Inbox,
Forms, approvals, and permissions. Elsa scopes Management, Runtime, Identity, and configured scheduler jobs by tenant.
Its external task/forms service must enforce the same isolation, and the team operates durable scheduler storage.
Workflow Engine NEO therefore needs less application code in this scenario.

## Onboarding complexity

**Why it matters:**

AI tools generate integration code; developers maintain and troubleshoot long-running processes. They must know
what the runtime loads and persists, how waits resume, and what happens after restarts, duplicate events, or definition
updates.

**Workflow Engine NEO:**

Workflow Engine NEO's [main terms][wfe-workflow-engine-concepts] are Scheme, Process Instance, Activity, State,
Transition, Command, Action, Actor, Rule, and Timer. Developers must distinguish Activity from State, Command from
Action, Actor from Rule, and relate each Process Instance to its stored Scheme version.

For Command or Timer execution, Workflow Runtime loads and locks the Process Instance through persistence. The selected
Transition moves it to the target Activity, whose Actions run. A [persisted
checkpoint][wfe-durable-execution-what-it-is] can contain Activity, State, Process Status, persistent Parameters,
optional Transition History, and Timer registrations. An `Idled` instance uses no execution thread or per-process
application memory; Workflow Runtime reloads it for new work.

**Elsa:**

Elsa's [execution model][elsa-execution-model-triggers-bookmarks-and-stimuli] has a broader vocabulary: workflow graph;
Workflow and Activity Execution Contexts; inputs, outputs, variables, and outcomes; bookmarks, stimuli, and triggers;
runner, runtime, dispatcher, and scheduler; Management and Runtime stores; incidents; and Alterations.

With runtime persistence, Elsa stores the workflow state and bookmark when an activity pauses for input. A matching
stimulus resumes the workflow through that bookmark. Scheduled waits use the scheduler; background workflow requests
use the dispatcher.

**Verdict:**

For teams new to both products, Workflow Engine NEO has fewer concepts to learn for approvals. Elsa requires developers
to learn more execution and infrastructure concepts: runtime execution, bookmark resumption, background dispatch,
scheduled waits, and persistence.

## Operational diagnostics

**Why it matters:**

When a process stalls or produces the wrong result, support must reconstruct its business route and internal
execution.

**Workflow Engine NEO:**

Workflow Engine NEO persists [Transition History][wfe-process-logs-what-each-log-entry-contains] with route, trigger,
actor, executor, and time. Optional [Process Logs][wfe-process-logs-what-process-logs-are] trace execution and errors;
they are in memory by default. [Runtime Logs][wfe-process-logs-runtime-logging-with-ilogger] report engine diagnostics.
[Runtime Events][wfe-process-logs-custom-logging-with-runtime-events] notify subscribers of state changes and errors but
store no history. Durable Process Logs need a [custom provider][wfe-process-logs-where-process-logs-are-stored].

**Elsa:**

Elsa's [workflow journal][elsa-monitoring-observability-workflow-journal], [activity execution
records][elsa-monitoring-observability-activity-execution-records], and
[incidents][elsa-monitoring-observability-incidents] show its event timeline, Activity state, and faults. Configured
[OpenTelemetry][elsa-monitoring-observability-export-traces-and-metrics-with-opentelemetry] exports traces and metrics;
Log Persistence controls retained inputs, outputs, and internal state.

**Verdict:**

Workflow Engine NEO answers which Transition moved a process, what triggered it, who acted, and when. Elsa exposes more
of the internal execution path, but its technical records do not by themselves constitute an approval audit that records
the participant, decision, and evidence.

## Embedding and architectural impact

**Why it matters:**

Embedding decides which hosting, storage, authorization, API, and UI boundaries must change and which components the
team must secure, update, and monitor.

**Workflow Engine NEO:**

Workflow Engine NEO embeds [Workflow Runtime][wfe-workflow-runtime-concept] in a .NET host with a
[persistence provider][wfe-persistence-persistence-and-workflow-runtime].
[Action Providers][wfe-action-how-an-action-works] call application services;
[Rule Providers][wfe-rule-provider] supply application-specific Rule logic evaluated for an Identity. Domain data,
identity, API, and UI can remain application-owned. The backend uses Workflow Runtime to create Process Instances and
list or execute [Commands][wfe-command-how-commands-connect-to-transitions-in-a-scheme]; it selects and executes their
Transitions.

[Workflow Engine Web API][wfe-api-glossary-surfaces] can publish the
[Workflow Engine HTTP API][wfe-http-api-what-it-is] from the same ASP.NET Core host or a separate service.
[Workflow Designer][wfe-workflow-designer-what-it-is],
[Approval Plugin Inbox][wfe-plugins-and-extensions-inbox], and
[Forms Plugin][wfe-attachable-forms-what-it-is] are independent integrations.

**Elsa:**

Elsa is added to an [existing ASP.NET Core host][elsa-existing-host] through dependency injection. Code-first workflows
can run there without Elsa Server or Elsa Studio.

Durable waits require persistent Management and Runtime stores; background execution uses a dispatcher; scheduled waits
add job scheduling. Visual authoring and instance management use the workflow API and Elsa Studio. These parts can share
a host. The team still configures and operates the selected stores, workers, scheduler, protected API, and Studio
deployment. The [hosting guide][elsa-hosting-model] covers standalone and modular boundaries.

If the product keeps its own API and UI, application code connects Elsa to its identity and domain services, while the
product team supplies the external task and forms layer.

**Verdict:**

Both can run in-process or behind a service. Workflow Engine NEO lets the product keep its identity system, application
services, API, and UI in place while adopting optional integrations separately. Elsa's durable background execution and
visual operations bring persistent stores, scheduling, dispatch, workflow APIs, and Studio into the product
architecture. A mature product preserving its architecture will normally require fewer changes with Workflow Engine
NEO. Either can underpin a dedicated workflow platform; Elsa generally needs more integration code when the product
retains its API, UI, and task layer.

## Licensing and total cost

**Why it matters:**

Total cost of ownership includes licensing, integration, infrastructure, upgrades, and support.

**Workflow Engine NEO:**

The editions and prices of Workflow Engine NEO are on the [pricing page][wfe-pricing]. Every edition of Workflow Engine NEO includes licensed Forms integration
through [Forms Plugin][wfe-attachable-forms-what-it-is]. For the approval SaaS considered here, NEO supplies Commands, Rules,
Approval Plugin Inbox, tenant routing, Timers, and multi-server coordination. The team configures these capabilities
instead of building and operating equivalent application services.

**Elsa:**

[Elsa Core][elsa-source-elsa-core-license] is MIT and has no runtime license fee. [Elsa+][elsa-plus-support] lists paid
support and professional services from independent providers. Elsa optionally uses MassTransit for
[broker-backed messaging][elsa-masstransit-activities] and workflow dispatch; its
[distributed-hosting guide][elsa-distributed-caching] uses it for cross-node cache invalidation. The current
[Elsa integration][elsa-masstransit-license-status] pins [MassTransit 8.5.7][masstransit-v8-license] under Apache 2.0.
[MassTransit v9][masstransit-v9-license] requires a commercial subscription scoped by product line or organization. As of
September 2026, Elsa has no committed v9 integration or replacement. A MassTransit design must budget for that
subscription or the cost of retaining or replacing 8.x.

**Verdict:**

For the approval SaaS considered here, NEO's license covers capabilities Elsa Core leaves to the product team. Compare NEO's price with
Elsa's task and forms code, multitenant integration, distributed operations, and paid support. When task, forms, and
operations capabilities already exist, Elsa may cost less; otherwise NEO reduces implementation scope and operational
ownership.

## When to choose Workflow Engine NEO

Choose Workflow Engine NEO when business-object lifecycles are expressed through
[States][wfe-activity-activity-properties], [Commands][wfe-command-how-commands-connect-to-transitions-in-a-scheme], participant
permissions, forms, or deadlines.

- **[Approvals involving people and AI agents.][section-ai-agents]** Commands represent decisions.
  [Workflow Runtime][wfe-workflow-runtime-concept] checks each participant's Identity against
  [Transition Restrictions whose Actors reference Rules][wfe-rule-how-a-rule-works].
  [Approval Plugin Inbox][wfe-plugins-and-extensions-inbox] lists work,
  [Forms Plugin][wfe-attachable-forms-what-it-is] manages forms, and
  [approval history][wfe-approval-plugin-track-approval-history] records outcomes.

- **[Multitenant products with forms and approvals.][section-multitenancy]** The
  [HTTP API routes requests by `TenantId`][wfe-multitenancy-what-it-is]. Tenant-aware operations cover workflow data,
  Forms Plugin, Timers, Transition History, approval history, Inbox, and permissions across
  [shared, schema-per-tenant, dedicated, or hybrid storage][wfe-multitenancy-hybrid-tenancy-http-api].

- **[Embedding in an existing application or a private workflow platform.][section-embedding-architecture]** Domain data,
  identity, API, and UI remain application-owned. [Action Providers][wfe-action-how-an-action-works] call services;
  [Rule Providers][wfe-rule-provider] supply permission logic.
  [Workflow Designer][wfe-workflow-designer-what-it-is], the HTTP API, Approval Plugin Inbox, and Forms Plugin are
  optional integrations.

- **[Combined visual and programmatic authoring.][section-code-first]** Workflow Designer embeds in JavaScript, React, or
  Angular products and supports [custom activity types][wfe-customization-custom-activity-types].
  [`ProcessDefinitionBuilder` uses the same `ProcessDefinition` format][wfe-builder-definition]; code can load a Scheme,
  but visual edits do not regenerate C#.

- **[Long-lived processes with reminders and controlled updates.][section-scheme-versioning]** Workflow Runtime
  [persists Timer registrations and evaluates them without an external scheduler][wfe-timer-timer-execution]. Persistence
  locks each Process Instance during Command execution and
  [coordinates due Timers across nodes in multi-server mode][wfe-clustering-what-it-is]. Instances remain on stored Scheme
  versions until migration; Workflow Runtime and HTTP API support
  [direct repair][wfe-direct-state-control-what-it-is].

## When to choose Elsa

Choose Elsa when workflows coordinate events, integrations, background work, parallel branches, or model and tool calls.
Approval assignment, permissions, forms, tenant isolation, and audit are outside Elsa, in host code or an
[external task-and-forms platform][section-approval-process].

- **[Integration- and event-driven pipelines.][section-workflow-model]**
  [Triggers and bookmarks][elsa-execution-model-triggers-bookmarks-and-stimuli], HTTP and
  [MassTransit activities][elsa-masstransit-activities], dispatch,
  [child workflows][elsa-dispatch-workflow-activity-using-code], and parallel branches support event-driven orchestration.
  Authority for business decisions and their audit remain application responsibilities.

- **[Code-first workflows owned by developers.][section-code-first]**
  [`WorkflowBase` defines typed inputs, outputs, variables, and custom activities in C#][elsa-workflow-providers-code-first-workflows].
  Studio can display them, but its edits do not change `WorkflowBase`;
  [the team chooses C# or the Studio definition as authoritative][elsa-provider-studio].

- **[A technical orchestration platform.][section-embedding-architecture]**
  [Elsa Server][elsa-architecture-overview-high-level-architecture] exposes APIs and execution records;
  [Studio][elsa-architecture-overview-studio] presents visual authoring, instance management, and Alteration plans. A
  [multi-node deployment][elsa-distributed-hosting] requires Distributed Runtime, shared locking, and, for scheduled work,
  [Quartz.NET or Hangfire with durable shared storage][elsa-durable-scheduler].

- **[Open source is a hard requirement.][section-licensing]** [Elsa Core][elsa-source-elsa-core-license],
  [Studio][elsa-source-elsa-studio-license], and [Extensions][elsa-source-elsa-extensions-license] use the MIT license, so
  teams may modify and redistribute them under its terms without buying a commercial runtime license.
  [Paid support and services][elsa-plus-support] are separate; teams maintain private modifications.

## Other products to evaluate

**[Hangfire][hangfire].** Hangfire runs background and recurring .NET jobs while the application owns business state and
transition rules. Its job queue, retries, and execution dashboard may be enough without a full workflow model.

**[Quartz.NET][quartz-net].** Quartz.NET schedules code by time, calendar, or trigger while the application owns business
state. Participant permissions, forms, process versions, and decision history remain outside the scheduler.

**[Wexflow][wexflow].** Wexflow's visual workflows automate files, scripts, ETL, back-office processes, and operational
jobs. Its server, task catalog, and administration UI suit products where system automation dominates human workflow.

**[Workflow Core][workflow-core].** Workflow Core is an embeddable open-source .NET runtime with fluent, JSON, or YAML
definitions and pluggable persistence and concurrency providers. Choose it when a code-first runtime matters more than
Elsa's Studio and platform services.

**[Temporal][temporal-docs].** Temporal coordinates distributed code and workers in several languages through replay-based
durable execution. Its Event History replay, deterministic Workflow code, and Worker Versioning differ from both engines.

**[Durable Functions][durable-functions].** Durable Functions provides stateful serverless orchestration on Azure through
orchestrator and activity functions, durable timers, and external events. Replay and determinism constraints apply; the
choice remains tied to Azure Functions and a configured storage backend.

**BPMN platforms.** Workflow Engine NEO [imports supported BPMN 2.0 elements][wfe-bpmn-support-what-it-is] into an editable
Scheme and executes that Scheme. Elsa Workflows 3.7 does not support BPMN import or execution. Dedicated BPMN-native
platforms such as [Camunda][camunda-processes] and [Flowable][flowable-bpmn] use BPMN as the executable workflow
definition. Include them in the shortlist when the BPMN model itself must remain the source of truth.

## Test both on your own process

**Implement the same real process.** Build the approval flow in both products using the permissions, integrations, and
support operations the production system requires. Include recurring reminders, a final deadline and escalation, and
two tenants. Then restart a host during a durable wait, submit a duplicate event, fail an external service call, race a
participant decision against the deadline on two nodes, and update the definition while an older instance remains
active.

Include the surrounding application work: create a process, show and submit the available decisions, render the form,
inspect approval history, and correct workflow state or data. Record the code, configuration, and deployed components
required for authorization, tasks and forms, tenant routing, persistence, scheduling, locking, audit, and diagnostics.

Compare recovery, logs, manual repair, and what the team must monitor, back up, and upgrade. Ask a second developer to
change one rule and diagnose one failure without help; record the time and concepts they needed.

**Map next year's backlog.** List changes planned or likely during the next twelve months and map each one to both
products. For each product, mark whether the change fits its native concepts or requires a new application component;
record infrastructure or operational work separately. Estimate implementation, testing, migration, and support effort
for the whole list. The first test measures the current process; the backlog map shows where work will accumulate as the
product changes.

## Final recommendation

For the long-running, multitenant approval SaaS considered in this article, Workflow Engine NEO is the more practical
choice. Elsa fits when technical orchestration drives most changes and the product already owns the human-task
capabilities around it. The two tests above show whether that conclusion holds for your product.

[wfe-neo-edition]: https://docs.workflowengine.io/evaluate/workflow-engine-editions#workflow-engine-neo
[elsa-architecture-overview-high-level-architecture]: https://docs.elsaworkflows.io/getting-started/architecture-overview#high-level-architecture
[wfe-backward-compatibility]: https://docs.workflowengine.io/evaluate/evaluate-workflow-engine#tested-and-stable-10-years-of-backward-compatible-releases
[wfe-workflow-runtime-concept]: https://docs.workflowengine.io/get-started/concepts/workflow-runtime/
[wfe-workflow-engine-concepts]: https://docs.workflowengine.io/get-started/concepts/workflow-engine-concepts/
[wfe-scheme-concept]: https://docs.workflowengine.io/get-started/concepts/scheme/
[wfe-persistence-persistence-and-workflow-runtime]: https://docs.workflowengine.io/get-started/concepts/persistence#persistence-and-workflow-runtime
[wfe-workflow-designer-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/workflow-designer#what-it-is
[wfe-http-api-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/http-api#what-it-is
[wfe-plugins-and-extensions-inbox]: https://docs.workflowengine.io/glossary/plugins-and-extensions#plugin-system-terms
[wfe-attachable-forms-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/attachable-forms#what-it-is
[wfe-action-how-an-action-works]: https://docs.workflowengine.io/get-started/concepts/action#how-an-action-works
[wfe-rule-rule-and-actor]: https://docs.workflowengine.io/get-started/concepts/rule#rule-and-actor
[elsa-execution-model-start-run-pause-resume]: https://docs.elsaworkflows.io/guides/architecture/execution-model#start-run-pause-resume
[elsa-persistence-layer]: https://docs.elsaworkflows.io/guides/persistence#persistence-stores-overview
[elsa-hosting-model]: https://docs.elsaworkflows.io/guides/architecture/standalone-and-modular-hosting#choosing-a-model
[elsa-custom-activities-resolving-services]: https://docs.elsaworkflows.io/extensibility/custom-activities#resolving-services
[wfe-activity-activity-properties]: https://docs.workflowengine.io/get-started/concepts/activity#activity-properties
[wfe-workflowruntime-query-commands-and-actors]: https://docs.workflowengine.io/get-started/concepts/workflow-runtime#use-the-process-api
[wfe-rule-how-a-rule-works]: https://docs.workflowengine.io/get-started/concepts/rule#how-a-rule-works
[elsa-execution-model-the-four-execution-paths]: https://docs.elsaworkflows.io/guides/architecture/execution-model#the-four-execution-paths
[section-workflow-model]: #workflow-model
[section-approval-process]: #approval-process
[section-ai-agents]: #ai-agents
[section-visual-designer]: #visual-designer
[section-code-first]: #code-first
[section-process-composition]: #process-composition
[section-durable-waits]: #durable-waits
[section-deadline-escalation]: #deadline-escalation
[section-concurrency-locking]: #concurrency-locking
[section-scheme-versioning]: #scheme-versioning
[section-running-process-operations]: #running-process-operations
[section-multitenancy]: #multitenancy
[section-onboarding]: #onboarding
[section-diagnostics]: #diagnostics
[section-embedding-architecture]: #embedding-architecture
[wfe-api-glossary-surfaces]: https://docs.workflowengine.io/glossary/apis-and-integration#workflow-engine-api-surfaces
[elsa-existing-host]: https://docs.elsaworkflows.io/guides/onboarding/hosting-elsa-in-existing-app#basic-workflow-runtime
[section-licensing]: #licensing
[wfe-command-how-commands-connect-to-transitions-in-a-scheme]: https://docs.workflowengine.io/get-started/concepts/command#how-commands-connect-to-transitions-in-a-scheme
[wfe-timer-how-timers-work-in-a-scheme]: https://docs.workflowengine.io/get-started/concepts/timer#how-timers-work-in-a-scheme
[elsa-runtask]: https://docs.elsaworkflows.io/guides/running-workflows/long-running-workflows#pattern-4-wait-for-background-work
[elsa-timeout-pattern]: https://docs.elsaworkflows.io/guides/patterns#timeout-escalation
[wfe-rule-provider]: https://docs.workflowengine.io/get-started/concepts/rule#implementing-rules-through-iworkflowruleprovider
[wfe-transition-properties]: https://docs.workflowengine.io/get-started/concepts/transition#transition-properties
[wfe-approval-plugin-track-approval-history]: https://docs.workflowengine.io/glossary/plugins-and-extensions#plugin-system-terms
[wfe-timer-timer-execution]: https://docs.workflowengine.io/get-started/concepts/timer#timer-execution
[elsa-distributed-runtime]: https://docs.elsaworkflows.io/hosting/distributed-hosting#id-1.-distributed-runtime
[elsa-distributed-locking]: https://docs.elsaworkflows.io/hosting/distributed-hosting#id-2.-distributed-locking
[elsa-quartz-clustered-mode]: https://docs.elsaworkflows.io/hosting/distributed-hosting#id-4.-quartz.net-clustered-mode
[elsa-distributed-hosting]: https://docs.elsaworkflows.io/hosting/distributed-hosting
[elsa-management-store-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Persistence.EFCore/Modules/Management/DbContext.cs#L17-L29
[elsa-workflow-instance-state-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Workflows.Management/Entities/WorkflowInstance.cs#L7-L35
[elsa-workflow-state-incidents-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Workflows.Core/State/WorkflowState.cs#L64-L72
[elsa-runtime-store-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Persistence.EFCore/Modules/Runtime/DbContext.cs#L20-L67
[wfe-custom-activity-execution]: https://api.workflowengine.io/core/OptimaJet.Workflow.Core/FormBase/#method-executionasync-workflowruntime-processinstance-dictionary-string-string-cancellationtoken
[elsa-agents-extension]: https://docs.elsaworkflows.io/guides/ai-agents
[wfe-customization-custom-activity-types]: https://docs.workflowengine.io/evaluate/workflow-engine-features/customization#what-it-is
[elsa-architecture-overview-studio]: https://docs.elsaworkflows.io/getting-started/architecture-overview#id-2.-elsa-studio
[elsa-custom-elements-which-surface-to-embed]: https://docs.elsaworkflows.io/guides/studio/integration/custom-elements#which-surface-to-embed
[elsa-custom-elements-react-wrapper]: https://docs.elsaworkflows.io/guides/studio/integration/custom-elements#react-wrapper
[elsa-studio-ui-customization]: https://docs.elsaworkflows.io/guides/studio/custom-ui-components#the-customization-model
[elsa-custom-icons-how-the-icon-path-works]: https://docs.elsaworkflows.io/guides/studio/custom-icons#how-the-icon-path-works
[wfe-builder-definition]: https://docs.workflowengine.io/evaluate/workflow-engine-features/workflow-as-code#what-it-is
[elsa-activity-data]: https://docs.elsaworkflows.io/extensibility/custom-activities#inputs-and-studio-metadata
[elsa-workflow-providers-code-first-workflows]: https://docs.elsaworkflows.io/extensibility/workflow-providers#code-first-workflows
[elsa-provider-studio]: https://docs.elsaworkflows.io/extensibility/workflow-providers#how-studio-users-experience-provider-backed-workflows
[wfe-scheme-inlining-what-scheme-inlining-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/workflow-templates#what-it-is
[wfe-subprocess-what-a-subprocess-is]: https://docs.workflowengine.io/get-started/concepts/subprocess#what-a-subprocess-is
[wfe-basic-plugin-process-control-example]: https://docs.workflowengine.io/plugins/basic-plugin/process-control/#real-world-scenario-multi-stage-deployment
[elsa-activity-reference-composition]: https://docs.elsaworkflows.io/activities/activity-reference#start-with-the-job-to-do
[elsa-dispatch-workflow-activity-using-code]: https://docs.elsaworkflows.io/guides/running-workflows/dispatch-workflow-activity#using-code
[elsa-bulk-dispatch-workflows-what-it-does]: https://docs.elsaworkflows.io/guides/running-workflows/bulk-dispatch-workflows#what-it-does
[wfe-durable-execution]: https://docs.workflowengine.io/evaluate/workflow-engine-features/durable-execution
[wfe-process-instance-lifecycle]: https://docs.workflowengine.io/get-started/concepts/process-instance
[elsa-long-waits]: https://docs.elsaworkflows.io/guides/running-workflows/long-running-workflows#how-elsa-pauses-and-resumes
[elsa-execution-context]: https://docs.elsaworkflows.io/getting-started/concepts/workflow-context#activity-execution-context
[elsa-long-running-workflows-common-mistakes]: https://docs.elsaworkflows.io/guides/running-workflows/long-running-workflows#common-mistakes
[elsa-local-scheduler]: https://docs.elsaworkflows.io/guides/running-workflows/timer-and-scheduled-workflows#how-scheduling-works-in-elsa-3.8.2
[elsa-durable-scheduler]: https://docs.elsaworkflows.io/guides/running-workflows/timer-and-scheduled-workflows#durable-scheduler-options
[elsa-local-scheduler-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Scheduling/Services/LocalScheduler.cs#L11-L88
[elsa-bookmark-scheduler-source]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Scheduling/Services/DefaultBookmarkScheduler.cs#L25-L143
[elsa-local-schedule-restore]: https://github.com/elsa-workflows/elsa-core/blob/749b6491fc4f32d94baaa34410c1e9b96577f62f/src/modules/Elsa.Scheduling/StartupTasks/CreateSchedulesStartupTask.cs#L10-L48
[wfe-timer-not-override-if-exists]: https://docs.workflowengine.io/get-started/concepts/timer#timer-registration
[wfe-process-lock]: https://docs.workflowengine.io/evaluate/workflow-engine-features/multi-database-support/#when-to-use-it
[wfe-clustering-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/clustering#what-it-is
[elsa-execution-model-how-resume-matching-works]: https://docs.elsaworkflows.io/guides/architecture/execution-model#how-resume-matching-works
[wfe-process-versioning-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/process-versioning#what-it-is
[elsa-definition-lifecycle]: https://docs.elsaworkflows.io/guides/running-workflows/workflow-definition-lifecycle#drafts-latest-versions-and-publishing
[elsa-alterations-alteration-types]: https://docs.elsaworkflows.io/features/alterations#alteration-types
[wfe-direct-state-control-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/direct-state-control#what-it-is
[wfe-set-state]: https://docs.workflowengine.io/get-started/concepts/activity#setstate-method
[wfe-set-activity]: https://docs.workflowengine.io/get-started/concepts/activity#setactivity-method
[wfe-resume]: https://docs.workflowengine.io/get-started/concepts/activity#resume-process
[elsa-alteration-plan-execution]: https://docs.elsaworkflows.io/features/alterations/alteration-plans#what-happens-when-you-submit-a-plan
[wfe-multitenancy-logical-tenancy-core-runtime-and-http-api]: https://docs.workflowengine.io/evaluate/workflow-engine-features/multitenancy#logical-tenancy-core-runtime-and-http-api
[wfe-multitenancy-physical-tenancy-http-api]: https://docs.workflowengine.io/evaluate/workflow-engine-features/multitenancy#physical-tenancy-storage
[wfe-multitenancy-hybrid-tenancy-http-api]: https://docs.workflowengine.io/evaluate/workflow-engine-features/multitenancy#hybrid-tenancy-http-api
[wfe-multitenancy-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/multitenancy#what-it-is
[elsa-introduction-multitenancy-levels]: https://docs.elsaworkflows.io/multitenancy/introduction#multitenancy-levels
[elsa-hangfire-tenant]: https://docs.elsaworkflows.io/guides/running-workflows/hangfire-integration#what-the-integration-schedules
[elsa-hangfire-store]: https://docs.elsaworkflows.io/guides/running-workflows/hangfire-integration#choose-and-operate-storage-deliberately
[elsa-setup-configuration]: https://docs.elsaworkflows.io/multitenancy/setup#configuration
[wfe-durable-execution-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/durable-execution#what-it-is
[elsa-execution-model-triggers-bookmarks-and-stimuli]: https://docs.elsaworkflows.io/guides/architecture/execution-model#triggers-bookmarks-and-stimuli
[wfe-process-logs-what-each-log-entry-contains]: https://docs.workflowengine.io/evaluate/workflow-engine-features/process-logs#what-each-log-entry-contains
[wfe-process-logs-what-process-logs-are]: https://docs.workflowengine.io/get-started/concepts/process-logs#what-process-logs-are
[wfe-process-logs-runtime-logging-with-ilogger]: https://docs.workflowengine.io/get-started/concepts/process-logs#runtime-logging-with-ilogger
[wfe-process-logs-custom-logging-with-runtime-events]: https://docs.workflowengine.io/get-started/concepts/process-logs#custom-logging-with-runtime-events
[wfe-process-logs-where-process-logs-are-stored]: https://docs.workflowengine.io/get-started/concepts/process-logs#where-process-logs-are-stored
[elsa-monitoring-observability-workflow-journal]: https://docs.elsaworkflows.io/operate/monitoring-observability#workflow-journal
[elsa-monitoring-observability-activity-execution-records]: https://docs.elsaworkflows.io/operate/monitoring-observability#activity-execution-records
[elsa-monitoring-observability-incidents]: https://docs.elsaworkflows.io/operate/monitoring-observability#incidents
[elsa-monitoring-observability-export-traces-and-metrics-with-opentelemetry]: https://docs.elsaworkflows.io/operate/monitoring-observability#export-workflow-telemetry-with-opentelemetry
[wfe-pricing]: /pricing/#pricing
[elsa-source-elsa-core-license]: https://github.com/elsa-workflows/elsa-core/blob/main/LICENSE
[elsa-source-elsa-studio-license]: https://github.com/elsa-workflows/elsa-studio/blob/main/LICENSE
[elsa-source-elsa-extensions-license]: https://github.com/elsa-workflows/elsa-extensions/blob/main/LICENSE
[elsa-plus-support]: https://www.elsaworkflows.io/elsa-plus
[elsa-masstransit-activities]: https://docs.elsaworkflows.io/activities/masstransit
[elsa-distributed-caching]: https://docs.elsaworkflows.io/hosting/distributed-hosting#id-3.-distributed-caching
[elsa-masstransit-license-status]: https://github.com/elsa-workflows/elsa-core/discussions/6583
[masstransit-v8-license]: https://github.com/MassTransit/MassTransit/blob/v8.5.7/LICENSE
[masstransit-v9-license]: https://massient.com/license
[hangfire]: https://www.hangfire.io/
[quartz-net]: https://www.quartz-scheduler.net/
[wexflow]: https://wexflow.github.io/
[workflow-core]: https://github.com/danielgerlag/workflow-core
[temporal-docs]: https://docs.temporal.io/
[durable-functions]: https://learn.microsoft.com/azure/durable-task/durable-functions/durable-functions-overview
[camunda-processes]: https://docs.camunda.io/docs/components/concepts/processes/
[flowable-bpmn]: https://www.flowable.com/open-source/docs/bpmn/ch02-GettingStarted/
[wfe-bpmn-support-what-it-is]: https://docs.workflowengine.io/evaluate/workflow-engine-features/bpmn-support#what-it-is
