<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Optimajet Limited</title>
    <description>The latest articles on DEV Community by Optimajet Limited (@optimajet).</description>
    <link>https://dev.to/optimajet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1475516%2F326deb07-08af-408b-a5a3-dfa595a8ebb3.png</url>
      <title>DEV Community: Optimajet Limited</title>
      <link>https://dev.to/optimajet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/optimajet"/>
    <language>en</language>
    <item>
      <title>What is workflow orchestration, and where should a .NET app run it?</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:24:35 +0000</pubDate>
      <link>https://dev.to/optimajet/what-is-workflow-orchestration-and-where-should-a-net-app-run-it-2fee</link>
      <guid>https://dev.to/optimajet/what-is-workflow-orchestration-and-where-should-a-net-app-run-it-2fee</guid>
      <description>&lt;p&gt;Workflow orchestration means that one component owns the sequence of steps, the state and the failures of a multi-step process. That component is the orchestrator. For a business process inside one .NET product, run the orchestrator as a library in your app. Keep its state in your own database. Data teams use the same term for a second job. For them, the term means running data pipelines on tools such as Apache Airflow.&lt;/p&gt;

&lt;p&gt;Mike Lukinov, co-founder of Optimajet, wrote &lt;a href="https://workflowengine.io/blog/what-is-workflow-orchestration/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=what-is-workflow-orchestration" rel="noopener noreferrer"&gt;What Is Workflow Orchestration&lt;/a&gt;. The post gives a rule for where to run the orchestrator. If the process belongs to one product and its data should stay in that product's database, put the orchestrator inside the product. The post also says when a separate server or a cloud service is the better place.&lt;/p&gt;

&lt;p&gt;Workflow Engine by Optimajet is a .NET workflow engine that runs as a library inside your app. The post compares Workflow Engine with Temporal, Camunda 8, AWS Step Functions and the data pipeline tools. It also lists the vendor pages used for these comparisons, with the date each page was read.&lt;/p&gt;

&lt;p&gt;Workflow Engine Free needs no license key. Its license covers personal use and internal commercial use, with attribution. It runs up to 10 workflow definitions (schemes) and 4 execution threads. A public web app or a SaaS product needs the Subscription, SaaS or Enterprise Edition of Workflow Engine NEO. Workflow Engine NEO is a separate product based on Workflow Engine. It adds an HTTP API and multitenancy. The facts about Workflow Engine were checked against version 23.0.0 and its documentation on 27 October 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an orchestrator does
&lt;/h2&gt;

&lt;p&gt;For a business process inside an application, the orchestrator is software called a workflow engine. Workflow Engine by Optimajet is one product of that kind. Take a purchase request. Someone submits it, and a manager approves it within a week. Then the system places the order through a supplier's API. Each request is a case. A case is one business item that moves through the process. Without an orchestrator, each service calls the next one. The state of the request is spread across several tables. A scheduled job searches for requests that got stuck. With an orchestrator, each request has one record. There is one place to ask where the request is.&lt;/p&gt;

&lt;p&gt;An orchestrator has five duties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequence.&lt;/strong&gt; It decides which step runs next, and which steps can run in parallel. In Workflow Engine, the sequence lives in the process definition, which is called a scheme. A scheme is an XML file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State.&lt;/strong&gt; It saves the current step and the data of each case between steps. In Workflow Engine, the state is a row in the application's own database, so the case survives a restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waiting.&lt;/strong&gt; It pauses a case until a person acts, a timer fires or another system answers. In Workflow Engine, a waiting case holds no execution thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure handling.&lt;/strong&gt; It retries a step, times it out, moves the case to another path, or hands the case to a person. In Workflow Engine, the exception handlers of a step can retry a failed call and then move the case to manual review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History.&lt;/strong&gt; It records each move, the time of the move, and who or what caused it. In Workflow Engine, &lt;code&gt;GetProcessHistoryAsync&lt;/code&gt; returns that history.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two jobs share the name
&lt;/h2&gt;

&lt;p&gt;Data pipeline orchestration moves datasets on a schedule. Application workflow orchestration runs business cases that wait for people. It is the job of a workflow engine, such as Workflow Engine. The two jobs need different tools.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Data pipeline orchestration&lt;/th&gt;
&lt;th&gt;Application workflow orchestration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What moves&lt;/td&gt;
&lt;td&gt;Datasets, from source to warehouse&lt;/td&gt;
&lt;td&gt;One business case, such as an order, a request or a loan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical shape&lt;/td&gt;
&lt;td&gt;A graph of tasks with no loops (a DAG). It runs on a schedule, and each run takes minutes to hours.&lt;/td&gt;
&lt;td&gt;A process instance that can wait days for a person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who acts&lt;/td&gt;
&lt;td&gt;Mostly machines&lt;/td&gt;
&lt;td&gt;Machines and people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When a step fails&lt;/td&gt;
&lt;td&gt;Rerun the task, or backfill (start the runs that were missed)&lt;/td&gt;
&lt;td&gt;Retry, time out, take another path, or route the case to a person. The case is kept.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical tools&lt;/td&gt;
&lt;td&gt;Apache Airflow, Dagster, Prefect&lt;/td&gt;
&lt;td&gt;Workflow engines such as Temporal, Camunda, AWS Step Functions and Workflow Engine by Optimajet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a nightly ETL chain (extract, transform, load), pick a data tool. Apache Airflow, Dagster and Prefect define pipelines in Python and are built for that job. Kubernetes uses the word &lt;em&gt;orchestration&lt;/em&gt; for a third thing. Container orchestration keeps containers running in the state you declare. It knows nothing about approvals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an embedded engine does with waits, deadlines and failures
&lt;/h2&gt;

&lt;p&gt;An embedded engine is a workflow engine that runs as a library inside your application. In Workflow Engine, the engine's object inside the application is &lt;code&gt;WorkflowRuntime&lt;/code&gt;. It runs on the application's own database. The purchase request goes from the Draft step to the Review step, where a manager approves or rejects it. Then it goes to the Ordering step, which calls the supplier's API, and then to Ordered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A restart.&lt;/strong&gt; If you stop the app while the request waits for approval, the request is still in Review when the app starts again. A waiting request has the status Idled. It holds no execution thread. All it holds is its row in the database. The restarted application is a new operating-system process. It loads the request with the same step, the same deadline and the same two commands, Approve and Reject. A command is what a person executes to move a request to another step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A deadline.&lt;/strong&gt; A timer on the Review step can take its value from a process parameter. A process parameter is a value stored with the request. So each request carries its own review window. When the timer fires, a transition moves the request from Review to Escalated. By default, the engine checks timers about once a second. It does this only while &lt;code&gt;WorkflowRuntime&lt;/code&gt; is started. If a timer's deadline passes while the app is down, the timer fires after the app starts again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A failure.&lt;/strong&gt; A step runs code called an action. The step also declares what happens when its action fails. When the manager executes Approve, the request moves into Ordering. So the supplier call runs inside the Approve command. If that call throws an &lt;code&gt;HttpRequestException&lt;/code&gt;, the exception handlers of the Ordering step can retry it twice. After that, they move the request to a step for manual review. Without handlers, the Approve command fails with a &lt;code&gt;ProcessExecutionException&lt;/code&gt;. The request stays in Review with the status Error. Nothing is lost, but someone has to go and find the request.&lt;/p&gt;

&lt;p&gt;An embedded engine still leaves four things for you to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A crash in the middle of a step.&lt;/strong&gt; By default, the engine does not rerun an interrupted step. The process gets the status Error or Terminated. Your recovery policy decides what happens next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effects.&lt;/strong&gt; A real order needs an idempotency key. This key is a value that makes a repeated call harmless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The next step for a person.&lt;/strong&gt; Escalation and manual-review steps need commands for the manager. Without them, a case stops there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More than one server.&lt;/strong&gt; To run several servers on one database, you need either Workflow Engine Complete or Workflow Engine NEO. Workflow Engine Complete is a paid Edition of Workflow Engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When you need an orchestrator, and when you do not
&lt;/h2&gt;

&lt;p&gt;An orchestrator is one more component to learn and to keep up to date. For small jobs, it costs more than it saves. One job on a schedule needs only a cron job or a hosted service. One unit of work with a retry policy needs only a queue and a background worker. Two services that work together with no waits and no people need only a direct call with a retry policy.&lt;/p&gt;

&lt;p&gt;Mike Lukinov's rule of thumb is that you need an orchestrator when one of these is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A step waits for a person, or a process takes days.&lt;/li&gt;
&lt;li&gt;One process touches three or more systems.&lt;/li&gt;
&lt;li&gt;Someone keeps asking where a case is stuck, and nobody can answer quickly.&lt;/li&gt;
&lt;li&gt;The process changes more often than you want to release code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the orchestrator runs
&lt;/h2&gt;

&lt;p&gt;For an application workflow, the orchestrator runs in one of three places. The first is a separate server, such as Temporal. The second is a cloud provider's service, such as AWS Step Functions. The third is a library in your app, such as Workflow Engine by Optimajet. The place you choose decides where the process state lives and what you operate. It also decides what it costs to move away later.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;A separate server&lt;/th&gt;
&lt;th&gt;A cloud provider's service&lt;/th&gt;
&lt;th&gt;A library in your app&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;Temporal, Camunda 8, Orkes Conductor&lt;/td&gt;
&lt;td&gt;AWS Step Functions, Azure Durable Functions, Azure Logic Apps&lt;/td&gt;
&lt;td&gt;Workflow Engine by Optimajet, Elsa Workflows, Workflow Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where state lives&lt;/td&gt;
&lt;td&gt;In the server's own storage&lt;/td&gt;
&lt;td&gt;In the provider's store. With Durable Functions, in a backend you choose.&lt;/td&gt;
&lt;td&gt;In your application's database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you operate&lt;/td&gt;
&lt;td&gt;A cluster, its storage and its upgrades, unless you buy the vendor's cloud plan&lt;/td&gt;
&lt;td&gt;Nothing extra, within the provider's limits&lt;/td&gt;
&lt;td&gt;Your application and its database. The database also holds the engine's tables. They get migrations on major upgrades. Workflow Engine Free runs on one server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How your code talks to it&lt;/td&gt;
&lt;td&gt;Through an SDK. Your workers poll the server for tasks.&lt;/td&gt;
&lt;td&gt;Through the provider's SDK or API&lt;/td&gt;
&lt;td&gt;Through method calls in the same process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Temporal (MIT) and Conductor OSS (Apache 2.0) are free to self-host. Camunda 8 Self-Managed needs a license in production. Each vendor also sells a cloud plan.&lt;/td&gt;
&lt;td&gt;You pay for what you use. Step Functions Standard charges per state transition. Logic Apps Consumption charges per action.&lt;/td&gt;
&lt;td&gt;Elsa Workflows and Workflow Core use the MIT license. Workflow Engine Free covers personal and internal commercial use, with attribution. A public web app or a SaaS product needs an Edition of Workflow Engine NEO. There is no fee per execution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moving away&lt;/td&gt;
&lt;td&gt;You rewrite the definitions. Temporal workflows are code written against its SDK. Camunda 8 models are BPMN (a standard process notation) with Camunda's extensions. Conductor definitions use Conductor's own JSON format.&lt;/td&gt;
&lt;td&gt;You rewrite the definitions. Step Functions uses the Amazon States Language. Durable Functions workflows are code written against its SDK.&lt;/td&gt;
&lt;td&gt;You rewrite the schemes, which are in the engine's own XML format. The process data is already in your database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fits best&lt;/td&gt;
&lt;td&gt;Many services in several languages, or one process that starts thousands of parallel tasks&lt;/td&gt;
&lt;td&gt;An application that already lives on one cloud&lt;/td&gt;
&lt;td&gt;One product that keeps its own processes and data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Mike Lukinov's view, a separate server is worth its operating cost when services in several languages share the same processes, or when one process starts thousands of parallel tasks. That is where Temporal is strong. A cloud service is the fastest way to start when the application already lives on that cloud. Its cost grows with every execution. For one product whose data stays in its own database, the library is the simplest place. The reason is that you add the library to an application you already run.&lt;/p&gt;

&lt;p&gt;The post takes its facts about these products from the following pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temporal: the &lt;a href="https://docs.temporal.io/develop/dotnet" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; (Temporalio 1.20.0), &lt;a href="https://docs.temporal.io/workers" rel="noopener noreferrer"&gt;workers&lt;/a&gt; and &lt;a href="https://github.com/temporalio/temporal" rel="noopener noreferrer"&gt;license&lt;/a&gt;, read on 14 and 27 October 2026.&lt;/li&gt;
&lt;li&gt;Camunda: the &lt;a href="https://docs.camunda.io/docs/reference/licenses/" rel="noopener noreferrer"&gt;licenses&lt;/a&gt;, read on 14 October.&lt;/li&gt;
&lt;li&gt;Orkes Conductor: the &lt;a href="https://orkes.io/content/sdks/csharp" rel="noopener noreferrer"&gt;C# SDK&lt;/a&gt; (conductor-csharp 3.0.0), read on 14 October.&lt;/li&gt;
&lt;li&gt;Azure Durable Functions: the &lt;a href="https://learn.microsoft.com/en-us/azure/durable-task/common/durable-task-storage-providers" rel="noopener noreferrer"&gt;storage providers&lt;/a&gt;, read on 11 October.&lt;/li&gt;
&lt;li&gt;AWS Step Functions and Azure Logic Apps: the &lt;a href="https://aws.amazon.com/step-functions/pricing/" rel="noopener noreferrer"&gt;Step Functions pricing&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-pricing" rel="noopener noreferrer"&gt;Logic Apps pricing&lt;/a&gt; pages, read on 27 October.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/elsa-workflows/elsa-core/releases" rel="noopener noreferrer"&gt;Elsa Workflows&lt;/a&gt; 3.9.0, read on 11 October, and &lt;a href="https://github.com/danielgerlag/workflow-core" rel="noopener noreferrer"&gt;Workflow Core&lt;/a&gt; 3.21.0, read on 1 October.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full post also compares orchestration with automation, job scheduling and choreography. It sorts the tools into seven categories, from data pipeline orchestrators to robotic process automation. The categories are in &lt;a href="https://workflowengine.io/blog/what-is-workflow-orchestration/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=what-is-workflow-orchestration#workflow-orchestration-tools-by-category" rel="noopener noreferrer"&gt;the tools section of What Is Workflow Orchestration&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>workflow</category>
      <category>orchestration</category>
    </item>
    <item>
      <title>Should approval thresholds live in your .NET workflow or in a rule engine?</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Wed, 28 Oct 2026 06:28:37 +0000</pubDate>
      <link>https://dev.to/optimajet/should-approval-thresholds-live-in-your-net-workflow-or-in-a-rule-engine-39fi</link>
      <guid>https://dev.to/optimajet/should-approval-thresholds-live-in-your-net-workflow-or-in-a-rule-engine-39fi</guid>
      <description>&lt;p&gt;Keep approval thresholds in your .NET workflow while developers own them, the thresholds change about once a year, there are two or three of them, and one service uses them. Move them to a rule engine when a business owner wants to edit them without a developer. Do the same when the thresholds change quarterly or more often, when the list grows into a table, or when several services need the same decision. After the move, a workflow action calls the rule engine and stores the decision with its rule version.&lt;/p&gt;

&lt;p&gt;Mike Lukinov, co-founder of Optimajet, sets out that rule in &lt;a href="https://workflowengine.io/blog/rule-engine-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rule-engine-vs-workflow-engine" rel="noopener noreferrer"&gt;Rule Engine vs Workflow Engine&lt;/a&gt;. Mike writes from the workflow side. Workflow Engine by Optimajet is a .NET workflow engine with no rule engine of its own. The post compares Workflow Engine with the rule engines it would call: NRules, Microsoft RulesEngine and DMN (Decision Model and Notation) engines. Workflow Engine Free runs the workflow side of this pattern. It needs no license key. Its license covers personal use and internal commercial use, with attribution.&lt;/p&gt;

&lt;p&gt;The product facts were checked on Workflow Engine 23.0.0 on 27 October 2026. The facts about NRules, Microsoft RulesEngine and DMN were checked on their own pages the same day. The thresholds in the examples are illustrative.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a rule engine does and what a workflow engine does
&lt;/h2&gt;

&lt;p&gt;A rule engine evaluates facts against explicit rules and returns a decision, usually without keeping state between calls. A workflow engine stores every running process and moves it from step to step. Take a purchase request for 60,000 from a preferred supplier. The rule engine answers that the request needs CFO approval. Workflow Engine then holds the request in its database, perhaps for days, until the CFO acts. The same supplier's request for 12,000 gets Auto-approve and finishes in the same call. When a request only needs an answer, such as a live checkout price, the rule engine alone is enough.&lt;/p&gt;

&lt;p&gt;One name can mislead .NET readers. In Workflow Engine, a Rule is a permission check that decides which user may execute a command. It is not a business rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three places a threshold can live
&lt;/h2&gt;

&lt;p&gt;In Workflow Engine, a scheme describes a process. It defines the activities (steps), the transitions between them and the commands that people execute. A threshold has two homes inside the workflow and one outside it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An Expression condition in the scheme.&lt;/strong&gt; By default, it is a C# expression that reads a process parameter. It sits on a transition. A change ships as a new scheme version. Running instances keep the version they started with until you migrate them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An Action condition in your code.&lt;/strong&gt; Your &lt;code&gt;IWorkflowActionProvider&lt;/code&gt; implements a named condition that can read the threshold from configuration or a table. A change needs no new scheme version, but a developer still owns the threshold. Nothing records which value routed each request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rule engine.&lt;/strong&gt; It sits outside the workflow. It takes the thresholds that business owners edit, that change quarterly or more often, that grow into tables, or that several services share.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Which rule engine for which rules
&lt;/h2&gt;

&lt;p&gt;Workflow Engine's own Decision Table activity is not a rule engine. Each row holds a transition condition and a target activity. The rows are stored in the scheme. The activity has no hit policies and no repository for business users. A hit policy is the rule that picks the result when several rows match.&lt;/p&gt;

&lt;p&gt;Decision tables that analysts edit belong in a DMN engine or a business rules management system (BRMS). Both store rules as data and version them. DMN, the decision standard of the Object Management Group, reached &lt;a href="https://www.omg.org/spec/DMN/1.6" rel="noopener noreferrer"&gt;version 1.6&lt;/a&gt; in September 2026. For rules that developers own, two MIT-licensed .NET libraries are common choices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://nrules.net/" rel="noopener noreferrer"&gt;NRules&lt;/a&gt;&lt;/strong&gt; (1.0.4, published 3 February 2026) runs rules that you write in C# with an internal DSL. So a rule change is normally a code change. The project also offers JSON serialization and a text rule language called Rule#. NRules can fire several matching rules and infer new facts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/RulesEngine" rel="noopener noreferrer"&gt;Microsoft RulesEngine&lt;/a&gt;&lt;/strong&gt; (6.0.1, published 3 July 2026) keeps rules as JSON outside the code. It suits rules that change as data but need no full rule repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Boundary tests catch row-order and threshold mistakes
&lt;/h2&gt;

&lt;p&gt;Once a threshold moves from Workflow Engine to a rule engine's table, the order of the rows is part of the logic. Under the DMN First hit policy, the engine checks the rows in order and returns the first match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purchase approval routing, First hit policy (from the full post)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule order&lt;/th&gt;
&lt;th&gt;Supplier status&lt;/th&gt;
&lt;th&gt;Amount (USD)&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Supplier is on the block list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;≥ 50,000&lt;/td&gt;
&lt;td&gt;CFO approval&lt;/td&gt;
&lt;td&gt;Above executive threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;≥ 5,000&lt;/td&gt;
&lt;td&gt;Procurement review&lt;/td&gt;
&lt;td&gt;New supplier above review threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Preferred&lt;/td&gt;
&lt;td&gt;&amp;lt; 50,000&lt;/td&gt;
&lt;td&gt;Auto-approve&lt;/td&gt;
&lt;td&gt;Preferred supplier under the limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Manager approval&lt;/td&gt;
&lt;td&gt;Default route&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you move row 1 below row 2, a blocked supplier's request for 60,000 goes to the CFO instead of being rejected. If you raise row 2 to 75,000 and leave row 4 at 50,000, two other routes change by accident. A preferred supplier's request for 60,000 goes to the manager. A new supplier's request for 60,000 goes to procurement review instead of the CFO. Boundary tests at 49,999.99, 50,000, 74,999.99 and 75,000 catch both mistakes before release. When rows should never overlap, the DMN Unique hit policy turns an overlap into an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a workflow action calls the rule engine
&lt;/h2&gt;

&lt;p&gt;With Workflow Engine, the two engines connect through a contract. It says which facts go in, which decision and rule version come out, and what happens when the call is late or fails. A workflow action is the code that a step of the scheme runs. In this pattern, the action does four things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It reads the facts from the request's process parameters, such as the supplier status and the amount.&lt;/li&gt;
&lt;li&gt;It calls the rule service and gives the call its own deadline, for example 500 ms.&lt;/li&gt;
&lt;li&gt;It stores the route and the rule version as persistent parameters, which are saved with the request.&lt;/li&gt;
&lt;li&gt;When the call is late or fails, the action stores the decision NeedsReview and the reason instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Transitions with Expression conditions then route the request by the stored decision. Workflow Engine checks an Otherwise transition last. This transition sends all other requests to a step where a person reviews them. Requests with the decision NeedsReview go there too. When the call to the rule service is late or fails, even a request that the rules would approve waits for a person. The reason is stored as well.&lt;/p&gt;

&lt;p&gt;Three things need care.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The deadline needs a call that can be canceled.&lt;/strong&gt; The action passes a cancellation token to the call. An HTTP call to a rule service stops when the token fires. A rule engine inside the same application stops only if its API accepts the token. NRules checks the token between rule firings. Give any engine that ignores the token its own time limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A call that changes something can run twice.&lt;/strong&gt; After a restart, the step can run again. Such a call needs an idempotency key or an outbox. An idempotency key is a value that makes a repeated call harmless. An outbox holds messages that are saved in the same database transaction and sent after the transaction commits. A call that only computes can simply run again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A second engine is a second system.&lt;/strong&gt; It adds a network call, its own ways to fail and its own release cycle for rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stored rule version records which rules made the decision. It does not pin the request to those rules. Suppose a request is submitted before a threshold changes and is evaluated after the change. That request takes the new route. A threshold in an Expression condition is pinned for free, because each request keeps its scheme version. With a rule engine, the contract has to carry a version or a creation time. The rule service also has to keep old versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it takes to run the contract on .NET
&lt;/h2&gt;

&lt;p&gt;Workflow Engine Free includes actions, persistent parameters and Expression conditions, which is all the contract needs. It runs with up to 10 workflow definitions (schemes) and 4 execution threads. A public web application or a SaaS product needs the Subscription, SaaS or Enterprise Edition of Workflow Engine NEO. Workflow Engine NEO is the HTTP API product of the Workflow Engine Family.&lt;/p&gt;

&lt;p&gt;So keep each threshold where its owner can change it. When a threshold moves to a rule engine, keep the route in the workflow and store the rule version with every decision. The full post adds a side-by-side table of the two engines. It also answers whether rules engines are outdated now that AI models score requests. Both are in &lt;a href="https://workflowengine.io/blog/rule-engine-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rule-engine-vs-workflow-engine" rel="noopener noreferrer"&gt;Rule Engine vs Workflow Engine, and Where a Business Rules Engine Fits&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>workflow</category>
      <category>dmn</category>
    </item>
    <item>
      <title>Should a .NET approval run inside your app or in a Camunda 8 cluster?</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Tue, 27 Oct 2026 16:32:55 +0000</pubDate>
      <link>https://dev.to/optimajet/should-a-net-approval-run-inside-your-app-or-in-a-camunda-8-cluster-4aa4</link>
      <guid>https://dev.to/optimajet/should-a-net-approval-run-inside-your-app-or-in-a-camunda-8-cluster-4aa4</guid>
      <description>&lt;p&gt;When managers review documents in a .NET product's own UI, the approval belongs inside your app, in an embedded engine such as Workflow Engine NEO, a commercial product by Optimajet. A Camunda 8 cluster beside the app pays off when several teams share one executable BPMN process and will use its Tasklist and Operate screens.&lt;/p&gt;

&lt;p&gt;Jevgenije Betin, lead developer of Workflow Engine NEO, reached that verdict in a &lt;a href="https://workflowengine.io/blog/camunda-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine" rel="noopener noreferrer"&gt;full comparison&lt;/a&gt;. Jevgenije modeled one approval in Camunda 8.9 Self-Managed and in Workflow Engine NEO v22. The comparison reviews both products' documentation as of 24 September 2026. No runtime benchmark was run.&lt;/p&gt;

&lt;p&gt;This post is the short version. The full comparison goes through 16 criteria, with diagrams of both models and links to both products' documentation. It compares Camunda Self-Managed because in that setup your team deploys and upgrades both products. With Camunda SaaS, Camunda runs the platform, so running the cluster stops being your team's job.&lt;/p&gt;

&lt;p&gt;Both products have had new releases since then. Camunda 8.10 came out on 13 October and Workflow Engine NEO 23.0.0 on 27 October. The comparison has not been rerun on either.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same approval, modeled twice
&lt;/h2&gt;

&lt;p&gt;In Workflow Engine NEO and in Camunda 8, the route is the same. An employee submits a document, a manager approves or rejects it, and a missed deadline escalates the review to a senior manager.&lt;/p&gt;

&lt;p&gt;In Workflow Engine NEO, the route is a Scheme in the product's own &lt;a href="https://docs.workflowengine.io/evaluate/workflow-engine-features/simple-process-notation/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine" rel="noopener noreferrer"&gt;Simple Process Notation&lt;/a&gt;, not BPMN. The &lt;code&gt;Submit&lt;/code&gt; Command moves the process to the &lt;code&gt;ManagerReview&lt;/code&gt; Activity. Three Transitions leave it. &lt;code&gt;Approve&lt;/code&gt; and &lt;code&gt;Reject&lt;/code&gt; are restricted to the &lt;code&gt;Manager&lt;/code&gt; Actor, and a Timer moves the process to &lt;code&gt;Escalated&lt;/code&gt;. The flow drawn in Workflow Designer is the flow the runtime executes. The notation has no modeling-only flow symbols. The Scheme is also where lock-in sits. Workflow Engine NEO's BPMN support is import-only, so a Scheme does not leave as BPMN. Moving off Workflow Engine NEO means rewriting the Scheme.&lt;/p&gt;

&lt;p&gt;In Camunda 8, the route is BPMN. &lt;code&gt;Submit document&lt;/code&gt; and &lt;code&gt;Manager review&lt;/code&gt; are user tasks. The manager completes the review with a &lt;code&gt;decision&lt;/code&gt; value, and an exclusive gateway routes on it. At the deadline, an interrupting boundary timer ends the review and starts the senior manager's route. All of these elements execute in Camunda 8.9. Some others that Modeler can draw, such as complex gateways, do not.&lt;/p&gt;

&lt;p&gt;For this approval, Jevgenije would model the route in Workflow Engine NEO. There, &lt;code&gt;Approve&lt;/code&gt;, &lt;code&gt;Reject&lt;/code&gt; and the Timer all leave &lt;code&gt;ManagerReview&lt;/code&gt;, so the decisions and the escalation are visible together. If several service teams need one executable BPMN process, Camunda's model serves them better. Both diagrams are in the &lt;a href="https://workflowengine.io/blog/camunda-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine#workflow-model" rel="noopener noreferrer"&gt;workflow model section&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs where, and what stays with your team
&lt;/h2&gt;

&lt;p&gt;With either Workflow Engine NEO or Camunda 8, the document stays in your .NET product. Workflow Engine NEO runs inside it; Camunda 8 runs beside it as a cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9k9ub45oq1zafgqeegy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9k9ub45oq1zafgqeegy2.png" alt="Diagram, Workflow Engine NEO. Your existing product keeps identity, documents and the product UI. You integrate the Action and Rule Providers, for business services and access checks. Workflow Engine NEO brings Workflow Runtime, Workflow Designer with the HTTP API, Inbox and Forms Plugin, and a persistence provider." width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow Engine NEO&lt;/strong&gt; runs inside the .NET service. Workflow Runtime executes the Scheme and stores process state through a persistence provider in a supported database. Workflow Designer can be embedded in the product's UI. Approval Plugin supplies Inbox entries and approval history, and Forms Plugin connects form data to the process. An HTTP API is there if you would rather run workflow as a separate service. What stays with your team is a Rule Provider that maps signed-in users to workflow Identities, the Actions that update the document or send notifications, and the task and support screens. Your team also keeps the workflow database available and recoverable. Workflow Engine NEO ships no task portal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7sfq8j0ji0iis7q64c8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7sfq8j0ji0iis7q64c8e.png" alt="Diagram, Camunda 8.9 Self-Managed. Your existing product keeps identity, documents and a custom UI. You integrate business services and job workers when the process calls external work. The Self-Managed platform brings Zeebe for execution, Tasklist and Operate, and secondary storage deployed alongside the Orchestration Cluster." width="800" height="858"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camunda 8 Self-Managed&lt;/strong&gt; runs as a separate platform. Its Orchestration Cluster holds Zeebe for execution, Tasklist for user tasks, Operate for inspecting and repairing instances, and Admin. Zeebe keeps its log and state in primary storage, and Tasklist and Operate read from a secondary storage layer. What stays with your team is running the cluster and both storage layers with their backups, and connecting the document service. If managers are to preview the document in Tasklist, your team also serves it through a protected endpoint. In return, if managers can work in Tasklist and support can work in Operate, nobody builds those screens.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://workflowengine.io/blog/camunda-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine#architecture" rel="noopener noreferrer"&gt;architecture section&lt;/a&gt; of the full comparison lists who operates what in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four questions the comparison answers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can a .NET service work with Camunda 8?
&lt;/h3&gt;

&lt;p&gt;Yes. A .NET service talks to Camunda 8 through its APIs and external job workers, and the service can stay in C#. In 8.9, the version compared, the official C# SDK was in Technical Preview. Camunda's &lt;a href="https://docs.camunda.io/docs/apis-tools/csharp-sdk/support-status/" rel="noopener noreferrer"&gt;support status page&lt;/a&gt; lists it as fully supported from 8.10. Either way, Camunda 8 is not an engine you embed in a .NET process. The C# code talks to a cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Camunda 8 Self-Managed need Kubernetes and Elasticsearch?
&lt;/h3&gt;

&lt;p&gt;No. Camunda &lt;a href="https://docs.camunda.io/docs/self-managed/setup/overview/" rel="noopener noreferrer"&gt;recommends Helm on Kubernetes&lt;/a&gt; and also supports Docker on Linux and a manual Java deployment. The Docker Compose quickstart is for development. In 8.9, the secondary storage can be a relational database as well as Elasticsearch or OpenSearch. The relational database does not replace Zeebe's primary storage. Optimize, Camunda's reporting tool, still needs Elasticsearch or OpenSearch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a Camunda process move to Workflow Engine NEO unchanged?
&lt;/h3&gt;

&lt;p&gt;Do not assume it can. Workflow Engine NEO imports BPMN by converting the supported elements into a Scheme. The runtime then executes the Scheme, not the BPMN XML. Task assignments, forms, Camunda extensions, decision models and worker behavior need their own migration review. The &lt;a href="https://docs.workflowengine.io/evaluate/workflow-engine-features/bpmn-support/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine" rel="noopener noreferrer"&gt;BPMN support page&lt;/a&gt; lists what converts, and the &lt;a href="https://workflowengine.io/products/compare/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine" rel="noopener noreferrer"&gt;product comparison&lt;/a&gt; shows which Editions include BPMN import.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is each one licensed?
&lt;/h3&gt;

&lt;p&gt;Three of the four Editions of Workflow Engine NEO have published prices on the &lt;a href="https://workflowengine.io/pricing/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;. The Enterprise Edition is priced by quote. The SaaS Edition licenses your own public application. Optimajet does not host Workflow Engine NEO. &lt;a href="https://camunda.com/pricing/" rel="noopener noreferrer"&gt;Camunda's pricing&lt;/a&gt; allows free local development and non-production use, and Self-Managed in production needs an enterprise license and a quote. For embedding in a product you sell, both offer negotiated terms, through the Enterprise Edition of Workflow Engine NEO and through Camunda's OEM program.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to pick Workflow Engine NEO, and when Camunda 8
&lt;/h2&gt;

&lt;p&gt;For this approval inside a .NET document product, Jevgenije's comparison picks Workflow Engine NEO. Workflow Runtime can run beside the document service, and the manager's form can live in the existing UI. The team builds the task and support screens around them. Form Engine Designer, which styles that form, is licensed separately except in the Enterprise Edition of Workflow Engine NEO.&lt;/p&gt;

&lt;p&gt;Camunda 8 Self-Managed is the better pick when several teams need one executable BPMN process across their services and will use Tasklist and Operate as their task and incident screens. The trade-off is a separate cluster to run and an integration with the document service.&lt;/p&gt;

&lt;p&gt;For a .NET team new to both, the learning differs too. With Workflow Engine NEO, the team learns how Triggers, Conditions, Rules and Actions shape a Scheme. It adds Workflow Runtime and a persistence provider to its .NET host and runs the migrations for a SQL provider. With Camunda, the team learns which BPMN symbols Camunda executes and FEEL expressions for conditions. It also deploys the cluster with its storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you decide
&lt;/h2&gt;

&lt;p&gt;The full comparison of Camunda 8 and Workflow Engine NEO lists six tests to run on your own process before choosing. Here are three of them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Revoke access.&lt;/strong&gt; Remove the manager's permission after the task exists, then try the UI and a direct workflow call. Try both again as a user of another tenant (another customer in a multi-tenant product).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interrupt execution.&lt;/strong&gt; Restart the host or the cluster during the wait, then interrupt the work after the document update succeeds but before the workflow step completes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repair an incident.&lt;/strong&gt; Diagnose a failed instance, make an authorized repair and restore a backup. With Camunda, support does it in Operate; with Workflow Engine NEO, in the support screen your team built.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then count what each product left to your team. For Workflow Engine NEO, that is the task and support screens. For Camunda, it is the document integration, the cluster and any service-task workers.&lt;/p&gt;

&lt;p&gt;The deadlines, concurrency, versioning and multitenancy sections, with the other three tests, are in the &lt;a href="https://workflowengine.io/blog/camunda-vs-workflow-engine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=camunda-vs-workflow-engine#deadlines" rel="noopener noreferrer"&gt;full comparison of Camunda 8 and Workflow Engine NEO&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>camunda</category>
      <category>bpmn</category>
      <category>architecture</category>
    </item>
    <item>
      <title>What Elsa Workflows and Workflow Engine NEO leave in your code</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:43:48 +0000</pubDate>
      <link>https://dev.to/optimajet/what-elsa-workflows-and-workflow-engine-neo-leave-in-your-code-5hm3</link>
      <guid>https://dev.to/optimajet/what-elsa-workflows-and-workflow-engine-neo-leave-in-your-code-5hm3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short answer.&lt;/strong&gt; Workflow Engine NEO by Optimajet fits .NET products built around business state, human decisions, forms and tenant isolation. Elsa Workflows fits products built around technical orchestration, events, integrations and background work. In a multi-tenant approval product, Workflow Engine NEO supplies the Commands, permission checks, Inbox, Forms Plugin, tenant routing and process locks. With Elsa, the team adds an external task and forms layer and configures a distributed runtime, locking and scheduling for several nodes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am &lt;a href="https://github.com/rylee-soll" rel="noopener noreferrer"&gt;Rylee Soll&lt;/a&gt;. I lead the product team of &lt;a href="https://workflowengine.io/products/workflow-engine-neo/" rel="noopener noreferrer"&gt;Workflow Engine NEO&lt;/a&gt; at Optimajet, and I run our demos. Most weeks someone on a demo call asks why they should pay for a license when &lt;a href="https://www.elsaworkflows.io/" rel="noopener noreferrer"&gt;Elsa Workflows&lt;/a&gt; is MIT. A feature list cannot answer that. So I built one approval process in both engines and wrote down what each engine takes over and what stays in the application.&lt;/p&gt;

&lt;p&gt;This post is the short version. The &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/" rel="noopener noreferrer"&gt;full comparison of Elsa Workflows and Workflow Engine NEO&lt;/a&gt; has the diagrams and both workflow definitions in C#. Every claim about Elsa in it links to Elsa's own documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval process I built twice
&lt;/h2&gt;

&lt;p&gt;The test case is a document approval. An employee submits a document, and a manager approves or rejects it. If the manager misses a three-day deadline, the process escalates to a senior manager.&lt;/p&gt;

&lt;p&gt;The two engines model it differently. Workflow Engine NEO treats it as the lifecycle of the document. An Activity called &lt;code&gt;ManagerReview&lt;/code&gt; carries the State "Under review". Two Commands, &lt;code&gt;Approve&lt;/code&gt; and &lt;code&gt;Reject&lt;/code&gt;, trigger the Transitions out of it, and only the manager may run them. A Timer moves the process to &lt;code&gt;Escalated&lt;/code&gt; when the deadline passes. Elsa treats the same process as a graph of executable activities. A &lt;code&gt;Fork&lt;/code&gt; in &lt;code&gt;WaitAny&lt;/code&gt; mode runs two branches. A &lt;code&gt;RunTask&lt;/code&gt; waits for the manager, and a &lt;code&gt;Timer&lt;/code&gt; waits for the deadline. The first branch to finish cancels the other. &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#workflow-model" rel="noopener noreferrer"&gt;How the workflow models differ&lt;/a&gt; shows both on one diagram.&lt;/p&gt;

&lt;p&gt;The happy path says little about a workflow engine. I followed the process further, to where the state lives, what survives a restart, how two tenants stay apart, and what somebody has to operate at three in the morning. I compared Elsa 3.7, as its documentation stood in August 2026, with Workflow Engine NEO v22.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who owns what
&lt;/h2&gt;

&lt;p&gt;In both products, your application keeps its identity, its services and its business data. The difference is the block marked "You provide". With Workflow Engine NEO, it holds an Action Provider, which calls your services, and a Rule Provider, which checks identities. With Elsa, it holds custom activities and the whole human-task layer, including assignment, the Inbox, forms and the business audit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkul75g6wu0nqv9ffuqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkul75g6wu0nqv9ffuqs.png" alt="Diagram of what Workflow Engine NEO and Elsa Workflows bring to an application. Workflow Engine NEO brings Workflow Runtime, persistence, Workflow Designer, the HTTP API, Inbox and Forms Plugin, and the application provides an Action Provider and a Rule Provider. Elsa Workflows brings an activity runtime, hosting and tools, and stores, and the application provides custom activities and the human-task layer." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the approval process, the split looks like this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of the approval&lt;/th&gt;
&lt;th&gt;Workflow Engine NEO v22&lt;/th&gt;
&lt;th&gt;Elsa Workflows 3.7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decisions a user can take now&lt;/td&gt;
&lt;td&gt;Workflow Runtime returns the Commands available to that user at the current Activity&lt;/td&gt;
&lt;td&gt;Your task layer decides. Elsa's instance API does not list named business decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who may approve&lt;/td&gt;
&lt;td&gt;Restrictions reference Actors, and your Rule Provider checks the user's Identity&lt;/td&gt;
&lt;td&gt;Your task layer checks the manager before it completes the task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbox and forms&lt;/td&gt;
&lt;td&gt;Approval Plugin Inbox and Forms Plugin&lt;/td&gt;
&lt;td&gt;Your team builds them or connects an existing task platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadline and escalation&lt;/td&gt;
&lt;td&gt;A Timer-triggered Transition moves the process to &lt;code&gt;Escalated&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The timer branch of &lt;code&gt;Fork(WaitAny)&lt;/code&gt; wins, and your task layer closes or expires the task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timers on several nodes&lt;/td&gt;
&lt;td&gt;TimerManager polls due timers in batches from the shared database&lt;/td&gt;
&lt;td&gt;Distributed Runtime, distributed locking, and clustered Quartz.NET or Hangfire with shared storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A decision and the deadline at the same moment&lt;/td&gt;
&lt;td&gt;A lock on the process instance lets only one of them run&lt;/td&gt;
&lt;td&gt;The distributed runtime serializes execution, and your task layer must update the task atomically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenants&lt;/td&gt;
&lt;td&gt;Logical, physical or hybrid tenancy. Through the HTTP API, the tenant scope covers the Inbox, forms, timers, history and permissions&lt;/td&gt;
&lt;td&gt;Shared tables, a database per tenant, or both. Your task and forms layer enforces its own isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision history&lt;/td&gt;
&lt;td&gt;Transition History records the route, trigger, actor, executor and time&lt;/td&gt;
&lt;td&gt;The workflow journal records the technical flow, and your application writes the business audit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code you still write&lt;/td&gt;
&lt;td&gt;A Rule Provider, Actions and forms&lt;/td&gt;
&lt;td&gt;A task and forms layer, plus application activities (four in this example)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both engines persist long waits, version their definitions, support visual and code-first authoring, run child workflows and support multitenancy. What differs is how much of the human-task layer comes with the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the difference shows up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Human tasks and permissions
&lt;/h3&gt;

&lt;p&gt;In Workflow Engine NEO, a decision is a Command on a Transition. Workflow Runtime lists the Commands that a user may run at the current Activity. When you ask it to, it checks the Rules again as the manager submits the decision.&lt;/p&gt;

&lt;p&gt;In Elsa, &lt;code&gt;RunTask&lt;/code&gt; creates a task ID and a bookmark, sends a request to your handler, and waits. Your task layer validates the manager and sends the task ID and the result to Elsa's task-completion endpoint, which resumes the workflow. The article draws &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#approval-process" rel="noopener noreferrer"&gt;the application integration for both engines&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A decision and a deadline at the same moment
&lt;/h3&gt;

&lt;p&gt;Say the manager clicks Approve in the same second the deadline fires. In Workflow Engine NEO, the Command or the due timer locks the process instance through persistence, so only one of them runs, even across nodes that share the database. If the deadline wins, Approve is no longer valid.&lt;/p&gt;

&lt;p&gt;In Elsa, &lt;code&gt;Fork(WaitAny)&lt;/code&gt; cancels the losing branch, but it does not serialize two hosts that loaded the same instance. That takes the distributed runtime and a shared lock. Your task layer must still complete or expire the task atomically and reject a late answer. &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#concurrency-locking" rel="noopener noreferrer"&gt;Concurrent events and process locking&lt;/a&gt; walks through the race in both engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timers on more than one node
&lt;/h3&gt;

&lt;p&gt;Workflow Engine NEO keeps timers in its database. TimerManager polls the due ones in batches, and in multi-server mode the runtimes take turns, so no external scheduler is involved.&lt;/p&gt;

&lt;p&gt;Elsa's default local scheduler keeps one in-memory task per timer and rebuilds them from stored bookmarks after a restart. On several nodes, Elsa needs Distributed Runtime, distributed locking, and clustered Quartz.NET or Hangfire with durable shared storage. Your team runs and monitors that scheduler. The section on &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#durable-waits" rel="noopener noreferrer"&gt;long-running waits in both engines&lt;/a&gt; links the Elsa documentation and scheduler source behind these points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tenants
&lt;/h3&gt;

&lt;p&gt;Workflow Engine NEO supports logical tenancy in shared tables, physical tenancy in a dedicated database or schema, and hybrid tenancy that mixes the two. The HTTP API reads the tenant from the &lt;code&gt;Workflow-Api-Tenant-ID&lt;/code&gt; header and routes the request to that tenant's runtime and database. Its tenant scope covers the Inbox, forms, timers, history and permissions.&lt;/p&gt;

&lt;p&gt;Elsa supports shared tables and a database per tenant, and one connection-string factory can mix them. It scopes its own stores, Elsa Identity and the configured scheduler jobs by tenant. It has no built-in router for a schema per tenant, and your task and forms service has to enforce the same isolation on its own data. &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#multitenancy" rel="noopener noreferrer"&gt;Multitenancy architecture in both engines&lt;/a&gt; compares the storage models in one diagram.&lt;/p&gt;

&lt;h3&gt;
  
  
  The editor in your frontend
&lt;/h3&gt;

&lt;p&gt;Workflow Designer is a JavaScript component with React and Angular wrappers and Blazor interop. A custom activity type can supply a complete SVG node template. Elsa Studio is a Blazor application. Its custom elements and its React wrapper load the Blazor WebAssembly runtime, which makes the page heavier in JavaScript, React and Angular products. In a Blazor product, Elsa Studio is the more native fit. The article has the details on &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#visual-designer" rel="noopener noreferrer"&gt;visual authoring and editor embedding&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would choose Elsa
&lt;/h2&gt;

&lt;p&gt;If most of your complexity is events, integrations and background work rather than people and business state, choose Elsa. It is also the better fit in these cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event and integration pipelines.&lt;/strong&gt; Triggers and bookmarks, HTTP and MassTransit activities, dispatch, child workflows and parallel branches are Elsa's native model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code-first workflows owned by developers.&lt;/strong&gt; &lt;code&gt;WorkflowBase&lt;/code&gt; keeps typed inputs, outputs and variables in C#. Elsa Studio saves its edits as a separate draft, so the team picks C# or Studio as the source of truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calls to models and tools.&lt;/strong&gt; The optional Elsa Agents extension turns configured agents into activities and selected skills into functions a model can call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source as a hard requirement.&lt;/strong&gt; Elsa Core, Elsa Studio and Elsa's extensions use the MIT license, so you can modify and redistribute them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Elsa can also cost less when your product already has the task, forms and operations pieces around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the license pays for
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/elsa-workflows/elsa-core" rel="noopener noreferrer"&gt;Elsa Core&lt;/a&gt; is MIT and has no runtime license fee. Paid support and services come from independent providers listed on &lt;a href="https://www.elsaworkflows.io/elsa-plus" rel="noopener noreferrer"&gt;Elsa+&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The editions and prices of Workflow Engine NEO are on the &lt;a href="https://workflowengine.io/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;. Every edition of Workflow Engine NEO includes Forms integration through the Forms Plugin, and every edition runs on several servers. For the approval product in this test, Workflow Engine NEO supplies the Commands, Rules, the Approval Plugin Inbox, tenant routing, timers and multi-server coordination. Your team configures them instead of building and running its own services for the same jobs.&lt;/p&gt;

&lt;p&gt;So compare the price of Workflow Engine NEO with what you would build, run and support around Elsa. That means the task and forms code, the multi-tenant integration, the distributed operations and paid support.&lt;/p&gt;

&lt;p&gt;If your Elsa design uses MassTransit, add one more line to the budget. Elsa can use MassTransit for broker-backed messaging, and its distributed-hosting guide uses it for cache invalidation across nodes. The current integration pins MassTransit 8.5.7, under Apache 2.0. MassTransit v9 requires a commercial subscription, and as of September 2026 Elsa has no committed v9 integration. The sources are in &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/#licensing" rel="noopener noreferrer"&gt;licensing and total cost&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test both before you choose
&lt;/h2&gt;

&lt;p&gt;A comparison ends where a proof of concept begins. Build the same approval in both engines, with a reminder, a deadline, an escalation and two tenants. Restart a host during a wait, race a decision against the deadline on two nodes, and update the definition while an older instance is still running. Then count the code and the deployed components that live outside the definition. Ask a second developer to change one rule without help, and write down how long it took.&lt;/p&gt;

&lt;p&gt;For the Workflow Engine NEO side, request a &lt;a href="https://trial.workflowengine.io/" rel="noopener noreferrer"&gt;30-day trial key&lt;/a&gt;. If you compare engines with an AI assistant, give it the &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/index.md" rel="noopener noreferrer"&gt;Markdown version of the full comparison&lt;/a&gt;, so it works from the same text you read.&lt;/p&gt;

&lt;p&gt;If I read Elsa's documentation wrong anywhere, say so in the comments or in &lt;a href="https://github.com/optimajet/WorkflowEngine.NET/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;, and I will change the row in the full comparison. If you would rather see this against your own process, &lt;a href="https://workflowengine.io/book-a-demo/" rel="noopener noreferrer"&gt;book a call&lt;/a&gt;. I am the person on the other side of it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>architecture</category>
      <category>saas</category>
    </item>
    <item>
      <title>Workflow Engine by Optimajet is now a family of four products</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:52:18 +0000</pubDate>
      <link>https://dev.to/optimajet/workflow-engine-by-optimajet-is-now-a-family-of-four-products-3611</link>
      <guid>https://dev.to/optimajet/workflow-engine-by-optimajet-is-now-a-family-of-four-products-3611</guid>
      <description>&lt;p&gt;One workflow engine for every .NET scenario, as an embeddable library, an HTTP API or a standalone server, now in four products with a new website.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Workflow Engine Family by Optimajet is one .NET workflow engine in four products: Workflow Engine Free and Workflow Engine as an embeddable library, Workflow Engine NEO as an HTTP API, and Workflow Server as a standalone server. A scheme drawn in Workflow Designer or built in C# runs in all four.&lt;/li&gt;
&lt;li&gt;Workflow Engine NEO is the flagship, for teams whose workflows are part of the product they sell. Workflow Engine NEO adds the Data API and RPC API groups of Workflow Engine HTTP API and ready multitenancy. A 30-day trial key from trial.workflowengine.io lets a team try Workflow Engine NEO on its own process before buying.&lt;/li&gt;
&lt;li&gt;Every edition of Workflow Engine NEO includes Workflow Forms for human tasks. The Forms Plugin behind Workflow Forms integrates Form Engine, Optimajet's separate form product family, and shows each person only the forms whose commands they may run.&lt;/li&gt;
&lt;li&gt;Workflow Engine Free, formerly Community Edition, is for personal use and internal commercial use with attribution, needs no license key and allows 10 schemes and 4 execution threads.&lt;/li&gt;
&lt;li&gt;Workflow Engine, Workflow Engine NEO and Workflow Server are source-available under a commercial EULA, and Workflow Engine Free is not. Engine source access is a one-year window, included with NEO Enterprise and an add-on for Workflow Engine Complete, NEO Business and NEO SaaS. Workflow Server source comes with Business Ultimate and Custom.&lt;/li&gt;
&lt;li&gt;The engine has shipped inside commercial .NET products since 2014. The current release is 22.1.0, from August 2026, and its core library targets .NET Standard 2.0, supported on .NET 8, .NET 10 and .NET Framework 4.6.2.&lt;/li&gt;
&lt;li&gt;The new workflowengine.io went live on September 22, 2026, with a comparison of the four products at workflowengine.io/products/compare/ and new documentation at docs.workflowengine.io. For existing users, the NuGet packages and their names, license keys and schemes stay the same.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Optimajet team and I are proud of the work behind this launch. Since 2014, .NET teams have built Workflow Engine into CRM, ERP and HR systems, and into software that manages documents. Over those years, four products grew around the engine, and it was not always easy to see which one you needed. This launch fixes that. For .NET developers and teams, the product line is now more useful and easier to work with. The engine your applications run has not changed.&lt;/p&gt;

&lt;p&gt;Thank you to every team that uses our products and tells us which features matter most to them and what they need next. We are especially grateful to Acer, AMCS Group, Avery Dennison, Conduent, Consolsys, DigiNation, Elm Company, EY (formerly Ernst &amp;amp; Young), Fiserv, Ideagen EHS (formerly ProcessMAP), Indra, KARL STORZ, Leah (formerly ContractPodAi), Ncontracts and Novartis. Their feedback shapes the features we build, and it makes each proof of concept with a new customer better. We could not have improved these products without them.&lt;/p&gt;

&lt;p&gt;The four products share &lt;a href="https://workflowengine.io/" rel="noopener noreferrer"&gt;one workflow engine&lt;/a&gt;, and the schemes that describe your processes carry over when you switch between them. A new website went live on September 22, 2026, together with &lt;a href="https://docs.workflowengine.io/" rel="noopener noreferrer"&gt;new documentation&lt;/a&gt;, and both are built to help you choose. Workflow Engine NEO is the flagship, built for teams whose workflows are part of the product they sell.&lt;/p&gt;

&lt;h2&gt;
  
  
  One workflow engine for every .NET scenario
&lt;/h2&gt;

&lt;p&gt;The four products run the same engine in three ways. They differ in where it runs, how your application calls it and what the license includes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;As an embeddable library.&lt;/strong&gt; &lt;a href="https://workflowengine.io/products/community-edition/" rel="noopener noreferrer"&gt;Workflow Engine Free&lt;/a&gt; and &lt;a href="https://workflowengine.io/products/dotnet-workflow-engine/" rel="noopener noreferrer"&gt;Workflow Engine&lt;/a&gt; run inside your .NET application, and you call them from C#. They use the same NuGet packages. Workflow Engine Free is for personal use and internal commercial use with attribution, and it needs no license key. It allows 10 schemes and 4 execution threads, the number of processes the engine advances at the same time. Workflow Engine is licensed for internal commercial applications, in two editions, Team and Complete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As an HTTP API.&lt;/strong&gt; &lt;a href="https://workflowengine.io/products/workflow-engine-neo/" rel="noopener noreferrer"&gt;Workflow Engine NEO&lt;/a&gt;, the flagship, is a separate product based on Workflow Engine. It adds the Data API and RPC API groups of Workflow Engine HTTP API and ready multitenancy to your application. A web front end, a mobile app or another service can then drive your processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As a standalone server.&lt;/strong&gt; &lt;a href="https://workflowengine.io/products/workflow-server/" rel="noopener noreferrer"&gt;Workflow Server&lt;/a&gt; is a separate service you run in Docker. Any language can call it over HTTP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four run on your own infrastructure, and none of them is a hosted Optimajet service. The embeddable products keep process data in your database: SQL Server, PostgreSQL, MySQL, Oracle, MongoDB or SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow Engine NEO is the flagship
&lt;/h2&gt;

&lt;p&gt;Workflow Engine NEO is a separate product in the Workflow Engine Family. It is based on Workflow Engine, but it is not an edition of Workflow Engine. Its editions are Subscription, Business, SaaS and Enterprise. It is built for teams whose workflows are part of the product they sell. Typical products are a CRM, an ERP, an HR system or a document management platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data API and RPC API.&lt;/strong&gt; The &lt;a href="https://docs.workflowengine.io/evaluate/workflow-engine-features/http-api/" rel="noopener noreferrer"&gt;OptimaJet.Workflow.Api&lt;/a&gt; package adds Workflow Engine HTTP API to your ASP.NET Core application. Without a NEO key it serves the Designer API and the Health Check API. With one, it also serves the Data API and the RPC API. The Data API reads and changes schemes, process instances, parameters, timers and approvals, with search, filtering and paging. The RPC API runs the engine, for example to create a process instance, execute a command or list the commands available now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://workflowengine.io/features/multi-tenancy/" rel="noopener noreferrer"&gt;Multitenancy&lt;/a&gt;.&lt;/strong&gt; One deployment serves many customers. The HTTP API applies the tenant to every built-in operation. Each tenant can live in shared storage, its own database or its own database schema, and one deployment can mix all three. In Workflow Engine, your code builds that isolation around a tenant ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security.&lt;/strong&gt; The HTTP API uses your application's ASP.NET Core authentication, such as JWT bearer tokens or cookies. Each operation has its own &lt;code&gt;OperationId&lt;/code&gt;, and &lt;code&gt;IWorkflowApiPermissions&lt;/code&gt; decides who may call it. The tenant header only selects the tenant. Whether the caller may use that tenant is a separate permission check, which you enable with API security. Your own endpoints stay your responsibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; Every edition of Workflow Engine NEO runs on multiple servers. In Workflow Engine, multi-server needs the Complete edition, and Team runs on one server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://workflowengine.io/features/workflow-forms/" rel="noopener noreferrer"&gt;Workflow Forms&lt;/a&gt; for human tasks.&lt;/strong&gt; Every edition of Workflow Engine NEO includes the Forms Plugin, installed as the &lt;a href="https://www.nuget.org/packages/WorkflowEngine.NETCore-FormsPlugin/" rel="noopener noreferrer"&gt;WorkflowEngine.NETCore-FormsPlugin&lt;/a&gt; package. In Workflow Engine it is an add-on for Complete and is not available on Team. It binds a form to a workflow step and shows each person only the forms whose commands they may run. Submitting a form runs that command, and the workflow's restrictions are checked again. A tenant can have its own version of a form, with the shared one as the fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forms from &lt;a href="https://formengine.io/" rel="noopener noreferrer"&gt;Form Engine&lt;/a&gt;.&lt;/strong&gt; The Forms Plugin integrates Form Engine, Optimajet's separate form product family. Form Engine Core renders the forms under the &lt;a href="https://www.npmjs.com/package/@react-form-builder/core" rel="noopener noreferrer"&gt;MIT license&lt;/a&gt;, and Form Engine Designer lets an analyst lay out a form without a frontend ticket. Workflow Engine NEO Enterprise includes the Form Engine Enterprise license, which covers the Designer, with OEM rights. On the other editions, that license is priced separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The right to ship.&lt;/strong&gt; The Subscription and SaaS editions cover a public web app or SaaS, and the Enterprise edition adds OEM distribution. The &lt;a href="https://workflowengine.io/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; states the licensed use of every edition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our &lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/" rel="noopener noreferrer"&gt;comparison with Elsa Workflows&lt;/a&gt;, Workflow Engine NEO supplied the commands, permission checks, inbox and forms for one approval process. Elsa needed an external task-and-forms layer for the same process.&lt;/p&gt;

&lt;p&gt;To evaluate Workflow Engine NEO, request a &lt;a href="https://trial.workflowengine.io/" rel="noopener noreferrer"&gt;30-day trial key&lt;/a&gt;. The key enables its licensed capabilities. Your team can then run the Data API, the RPC API and multitenancy on a process of its own before you buy. A one-hour &lt;a href="https://workflowengine.io/book-a-demo/" rel="noopener noreferrer"&gt;demo&lt;/a&gt; walks through your case with us. For more depth, read &lt;a href="https://workflowengine.io/blog/data-api-vs-rpc-api-in-workflow-engine-neo/" rel="noopener noreferrer"&gt;Data API or RPC API, which one your caller needs&lt;/a&gt; and &lt;a href="https://workflowengine.io/blog/workflow-engine-neo-22-0-0-dynamic-tenant-registration/" rel="noopener noreferrer"&gt;dynamic tenant registration in NEO 22.0.0&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One scheme runs in all four products
&lt;/h2&gt;

&lt;p&gt;A scheme is the workflow definition you draw in Workflow Designer or build in C#. It runs in all four products, and schemes written for older versions still run on the current release. That is what makes the four products one family, and it makes the choice above safe to make early. Scheme compatibility is separate from upgrading the application. The runtime, the packages and the database follow the &lt;a href="https://workflowengine.io/documentation/release-notes/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt;, and 22.0.0, for example, required database migrations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From Workflow Engine Free to Workflow Engine, you register a license key. The packages stay the same, and the key lifts the limits.&lt;/li&gt;
&lt;li&gt;From Workflow Engine to Workflow Engine NEO, the switch itself is a license key. The existing application and its schemes keep running as they are, on the same version. Using the Data API, the RPC API and multitenancy is planned work. You add the packages, configure authentication and tenants, and write the code that calls them.&lt;/li&gt;
&lt;li&gt;Workflow Server runs as a separate service, so moving to it changes how you deploy. Your schemes still carry over.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source access and vendor lock-in
&lt;/h2&gt;

&lt;p&gt;The first objection to a commercial engine is lock-in, because your product depends on code you cannot see. Three of the four products answer it. Workflow Engine, Workflow Engine NEO and Workflow Server are source-available, under a commercial EULA, and Workflow Engine Free is the one product without source access.&lt;/p&gt;

&lt;p&gt;For Workflow Engine and Workflow Engine NEO, source access is a one-year window on GitHub to the full engine source, as is, to read and review. It is included with Workflow Engine NEO Enterprise and is an add-on for Workflow Engine Complete, NEO Business and NEO SaaS. Team and NEO Subscription do not include it. Workflow Server includes its full source for one year with the Business Ultimate and Custom editions.&lt;/p&gt;

&lt;p&gt;Source access is one of several guards against lock-in. Every edition of Workflow Engine and Workflow Engine NEO except NEO Subscription is a perpetual license, so the version you bought keeps running. A lapsed NEO Subscription falls back to the Workflow Engine Free limits. The engine runs on your infrastructure and keeps process data in your own database. Your schemes carry over between the four products, and there are no royalties or per-execution fees. The &lt;a href="https://workflowengine.io/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; shows the source access, license model and support of every edition.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed, and what did not
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is new
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One product family, the Workflow Engine Family, and one &lt;a href="https://workflowengine.io/products/compare/" rel="noopener noreferrer"&gt;comparison page&lt;/a&gt; for its four products.&lt;/li&gt;
&lt;li&gt;A new website at workflowengine.io, with a page for each product and public prices for Workflow Engine and Workflow Engine NEO.&lt;/li&gt;
&lt;li&gt;New documentation at docs.workflowengine.io, which starts with Evaluate and Get Started and defines every term in a Glossary.&lt;/li&gt;
&lt;li&gt;Clear lines between products and editions, including source-code access and what needs a paid edition or an add-on.&lt;/li&gt;
&lt;li&gt;A new name for the free product, Workflow Engine Free, formerly Community Edition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What stays the same
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The NuGet packages and their names.&lt;/li&gt;
&lt;li&gt;The license keys you already have.&lt;/li&gt;
&lt;li&gt;Your schemes, including those written for older versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compare the four products row by row
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://workflowengine.io/products/compare/" rel="noopener noreferrer"&gt;comparison of Workflow Engine products&lt;/a&gt; is the page to open before any other. It sets all four side by side, one capability per row, from durable execution and Workflow Designer to APIs, databases, multitenancy, scaling and licensing. Where the answer depends on the edition or an add-on, the cell names it. The Forms Plugin row, for example, reads "Not available" for the Team edition and "Add-on" for Complete. Every edition of Workflow Engine NEO includes it.&lt;/p&gt;

&lt;p&gt;Row names link to the pages that document each capability, and the common questions are answered below the table. The same matrix is published as Markdown at &lt;a href="https://workflowengine.io/products/compare/index.md" rel="noopener noreferrer"&gt;workflowengine.io/products/compare/index.md&lt;/a&gt;. When you ask an AI assistant which of our products fits your case, give it that link. It can then answer from the same table you would read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we rebuilt the site and the documentation
&lt;/h2&gt;

&lt;p&gt;As the family grew, the old site no longer matched the way customers evaluate our products. Workflow Engine NEO had no page of its own, and the prices were on optimajet.com, a different site. The free product's old name also read like an edition of the paid library.&lt;/p&gt;

&lt;p&gt;The new site starts from the choice. The comparison page answers what each product includes, the pricing page what it costs, and the documentation how it works.&lt;/p&gt;

&lt;p&gt;The documentation starts one step earlier too, with &lt;a href="https://docs.workflowengine.io/evaluate/" rel="noopener noreferrer"&gt;Evaluate&lt;/a&gt; for the choice and &lt;a href="https://docs.workflowengine.io/get-started/" rel="noopener noreferrer"&gt;Get Started&lt;/a&gt; for a first running process in about 15 minutes. The &lt;a href="https://docs.workflowengine.io/glossary/" rel="noopener noreferrer"&gt;Glossary&lt;/a&gt; defines each product, edition and term once.&lt;/p&gt;

&lt;p&gt;The new documentation is marked as a preview while its last pages are written, and both documentation hosts are official. The &lt;a href="https://workflowengine.io/documentation/" rel="noopener noreferrer"&gt;previous documentation&lt;/a&gt; still holds the release notes, the setup and security of the HTTP API, and the BPMN and database reference tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each answer lives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/pricing/" rel="noopener noreferrer"&gt;Workflow Engine pricing&lt;/a&gt; gives the price, license model, licensed use, limits, source-code access and support of every edition of Workflow Engine and Workflow Engine NEO. Every price is public except Workflow Engine NEO Enterprise, which we quote per project.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/enterprise/" rel="noopener noreferrer"&gt;Enterprise support and security&lt;/a&gt; covers self-hosting inside your perimeter, security questionnaires and support plans with a committed response time. Tickets go to the engineers who own the codebase.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/features/" rel="noopener noreferrer"&gt;Workflow Engine features&lt;/a&gt; lists every feature in the documentation's catalog, each with one sentence and a link. Full pages with code and limits cover &lt;a href="https://workflowengine.io/features/workflow-designer/" rel="noopener noreferrer"&gt;Workflow Designer&lt;/a&gt;, &lt;a href="https://workflowengine.io/features/bpmn/" rel="noopener noreferrer"&gt;BPMN support&lt;/a&gt;, &lt;a href="https://workflowengine.io/features/multi-tenancy/" rel="noopener noreferrer"&gt;multi-tenant workflow architecture&lt;/a&gt;, &lt;a href="https://workflowengine.io/features/workflow-forms/" rel="noopener noreferrer"&gt;Workflow Forms&lt;/a&gt;, &lt;a href="https://workflowengine.io/features/multi-database/" rel="noopener noreferrer"&gt;database providers&lt;/a&gt;, the &lt;a href="https://workflowengine.io/features/plugin-system/" rel="noopener noreferrer"&gt;plugin system&lt;/a&gt; and &lt;a href="https://workflowengine.io/features/pluggable-security/" rel="noopener noreferrer"&gt;LDAP and Active Directory authorization&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Workflow Designer also has pages for &lt;a href="https://workflowengine.io/features/blazor-workflow-designer/" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;, &lt;a href="https://workflowengine.io/features/javascript-workflow-designer/" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; and &lt;a href="https://workflowengine.io/features/react-workflow-designer/" rel="noopener noreferrer"&gt;React&lt;/a&gt;. The code on them ran on version 22.1.0. Each page has a prompt you can paste into Cursor, Claude Code or Copilot. The agent reads the page as Markdown before it changes your app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/blog/elsa-vs-workflow-engine/" rel="noopener noreferrer"&gt;Elsa Workflows vs Workflow Engine NEO&lt;/a&gt; builds the same approval process in both engines and says where each one fits.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/blog/workflow-engine-for-ehs-management-software/" rel="noopener noreferrer"&gt;How ProcessMAP runs EHS workflows on Workflow Engine&lt;/a&gt; shows the engine inside a commercial SaaS platform for environment, health and safety.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Written for AI assistants as well
&lt;/h2&gt;

&lt;p&gt;You may ask an AI assistant about a library before you open its website, so the site gives that assistant pages it can read. The &lt;a href="https://workflowengine.io/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; file is a short map of workflowengine.io for AI tools, and &lt;a href="https://workflowengine.io/llms-full.txt" rel="noopener noreferrer"&gt;llms-full.txt&lt;/a&gt; is the full product reference in one file. Both point an assistant to the Markdown copies of the comparison page, the pricing page, the Workflow Designer guides and the Elsa comparison. Every post in &lt;a href="https://workflowengine.io/blog/" rel="noopener noreferrer"&gt;our blog&lt;/a&gt; has a Markdown copy and a Copy page button. Each documentation page can be copied as Markdown or opened in ChatGPT or Claude.&lt;/p&gt;

&lt;p&gt;A coding agent can also request a 30-day trial key for you through &lt;a href="https://trial.workflowengine.io/llms.txt" rel="noopener noreferrer"&gt;trial.workflowengine.io/llms.txt&lt;/a&gt;. The request carries your business email and company name, so the agent should ask you first.&lt;/p&gt;

&lt;p&gt;If something you used on the old site is missing, tell us in &lt;a href="https://github.com/optimajet/WorkflowEngine.NET/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which Workflow Engine product do I need?
&lt;/h3&gt;

&lt;p&gt;It depends on how you run the engine. For a library you call from C# inside a .NET application, use Workflow Engine, or Workflow Engine Free for personal use and internal commercial use with attribution. For an HTTP API that a web front end, a mobile app or another service calls, use Workflow Engine NEO. It is also the choice when you serve many tenants. For a separate service in Docker that any language can call, use Workflow Server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Workflow Engine open source?
&lt;/h3&gt;

&lt;p&gt;No. Workflow Engine by Optimajet is commercial software with a free product, Workflow Engine Free, for personal use and internal commercial use with attribution. Workflow Engine, Workflow Engine NEO and Workflow Server are source-available, with full source access in selected editions under a commercial EULA. Workflow Engine Free is the one product without source access.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I evaluate Workflow Engine NEO?
&lt;/h3&gt;

&lt;p&gt;Request a 30-day trial key at trial.workflowengine.io. The key enables the licensed capabilities, including the Data API, the RPC API and multitenancy, so your team can test them on its own process. A coding agent can request the key for you through trial.workflowengine.io/llms.txt once you agree to share your business email and company name. A one-hour demo walks through your case with our team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Workflow Engine NEO include forms for human tasks?
&lt;/h3&gt;

&lt;p&gt;Yes. Every edition of Workflow Engine NEO includes the Forms Plugin, the component behind Workflow Forms. It binds a form to a workflow step and shows each person only the forms whose commands they may run. On submit it runs the command and checks the workflow's restrictions again. The Forms Plugin integrates Form Engine. Form Engine Core renders the forms under the MIT license, and Workflow Engine NEO Enterprise includes the Form Engine Enterprise license, which covers Form Engine Designer, with OEM rights.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed for existing Workflow Engine users?
&lt;/h3&gt;

&lt;p&gt;Nothing in the product changed with the launch. The engine, the NuGet packages, your license keys and your schemes work as before, and the current release is 22.1.0. What is new is the family of four products, the comparison page, the website and the documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Workflow Engine in software I sell to my customers?
&lt;/h3&gt;

&lt;p&gt;Yes, with the right edition. Workflow Engine Team and Complete cover internal commercial applications. A public web app or SaaS needs the Subscription, SaaS or Enterprise edition of Workflow Engine NEO, and OEM distribution needs Workflow Engine NEO Enterprise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Workflow Engine NEO a cloud service?
&lt;/h3&gt;

&lt;p&gt;No. Workflow Engine NEO is software you run on your own infrastructure, like the other three products. The name of its SaaS edition describes your application, not a service that Optimajet hosts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I compare Workflow Engine products?
&lt;/h3&gt;

&lt;p&gt;Use the comparison page at workflowengine.io/products/compare/. It sets Workflow Engine Free, Workflow Engine, Workflow Engine NEO and Workflow Server side by side, one capability per row. Where the answer depends on the edition or an add-on, the cell names it. A Markdown copy for AI assistants is at workflowengine.io/products/compare/index.md.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Workflow Engine Free an edition of Workflow Engine?
&lt;/h3&gt;

&lt;p&gt;No. Workflow Engine Free is a separate product for personal use and internal commercial use with attribution, and it needs no license key. It uses the same library as Workflow Engine, limited to 10 schemes and 4 execution threads. A Workflow Engine license key lifts those limits, and the packages stay the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I move an application from Workflow Engine to Workflow Engine NEO?
&lt;/h3&gt;

&lt;p&gt;Yes. Register a Workflow Engine NEO license key, and the application keeps running as before, on the same version. To use the Data API, the RPC API and multitenancy, you add the packages, configure authentication and tenants, and write the code that calls them. Your schemes carry over, as they do between all four products.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is the previous Workflow Engine documentation?
&lt;/h3&gt;

&lt;p&gt;It stays online at workflowengine.io/documentation/ while the new documentation at docs.workflowengine.io is in preview. Release notes, setup and security of the HTTP API, and the BPMN and database reference tables are still there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with Workflow Engine NEO
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://workflowengine.io/products/workflow-engine-neo/" rel="noopener noreferrer"&gt;Its page&lt;/a&gt; shows what the Data API, the RPC API, multitenancy and the Forms Plugin add. &lt;a href="https://workflowengine.io/products/compare/" rel="noopener noreferrer"&gt;Compare it with the other three products&lt;/a&gt;, and &lt;a href="https://workflowengine.io/book-a-demo/" rel="noopener noreferrer"&gt;book a demo&lt;/a&gt; when you want to walk through your case with us.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>workflow</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Your coding agent can now write a .NET workflow engine integration</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:34:17 +0000</pubDate>
      <link>https://dev.to/optimajet/your-coding-agent-can-now-write-a-net-workflow-engine-integration-2lkb</link>
      <guid>https://dev.to/optimajet/your-coding-agent-can-now-write-a-net-workflow-engine-integration-2lkb</guid>
      <description>&lt;p&gt;Ask Codex, Claude or Cursor to wire a workflow library into a .NET service and you usually get code that looks right and calls a method that doesn't exist. Say the agent gets past that. It then hits the line "obtain a license key from the vendor" and stops, because that step was built for a human with a browser and an email inbox.&lt;/p&gt;

&lt;p&gt;I work on Workflow Engine at Optimajet, and we've recently rebuilt our documentation. The new primary documentation is available at &lt;a href="https://docs.workflowengine.io/" rel="noopener noreferrer"&gt;docs.workflowengine.io&lt;/a&gt;. It is gradually replacing the legacy documentation at &lt;a href="https://workflowengine.io/documentation/" rel="noopener noreferrer"&gt;workflowengine.io/documentation&lt;/a&gt;. It publishes its full text as a single file so an agent reads the whole thing in one request instead of guessing, and a trial key can now be obtained by an agent, without a human clicking through a form.&lt;/p&gt;

&lt;p&gt;The rest is for humans: 57 pages, four sections, 24 feature pages, 17 pages that each define exactly one concept, plus the generated C# API reference on its own host, &lt;a href="https://api.workflowengine.io/" rel="noopener noreferrer"&gt;api.workflowengine.io&lt;/a&gt;, 1256 pages across 20 packages.&lt;/p&gt;

&lt;p&gt;If you've never touched the product: &lt;strong&gt;Workflow Engine by Optimajet&lt;/strong&gt; is an embeddable .NET library for workflow automation. You add a NuGet package to your app, point it at the database you already have, and it handles process state, transitions, timers, rules and actions in-process. Nothing extra to deploy, no separate workflow server to babysit. It works on .NET Framework 4.6.2 and up as well as modern .NET and ASP.NET Core, and it persists to SQL Server (2017+), PostgreSQL, MySQL, Oracle, SQLite or MongoDB, whichever provider package you install.&lt;/p&gt;

&lt;p&gt;A quick note before anyone asks: Workflow Engine is commercial software, and Workflow Engine Free costs nothing. It's not open source. Workflow Engine NEO Enterprise includes one year of access to the engine source, and Workflow Engine Complete, NEO Business and NEO SaaS can add it. We've been shipping it since 2014. The latest stable release is 22.0.0, released on July 16, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two text files and a key
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.workflowengine.io/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; is the index, every published page with a one-line description, about 11 KB. &lt;a href="https://docs.workflowengine.io/llms-full.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/a&gt; is the full text of every page with its source URL attached, about 480 KB. One fetch gives an agent the complete documentation corpus together with source URLs, making it easier to reference documented APIs instead of inferring them.&lt;/p&gt;

&lt;p&gt;The key is the other half. &lt;code&gt;trial.workflowengine.io/llms.txt&lt;/code&gt; carries instructions an agent can follow to obtain a trial license key by itself. Writing correct integration code and then stalling on paperwork is a stupid way to lose an evaluation, and it was the failure we kept watching happen.&lt;/p&gt;

&lt;p&gt;The API reference host publishes an &lt;code&gt;llms.txt&lt;/code&gt; too. Bookmark &lt;a href="https://api.workflowengine.io/" rel="noopener noreferrer"&gt;api.workflowengine.io&lt;/a&gt;: it's the generated C# reference for all 20 packages (Core, six database providers, five plugins, the Migrator, and the Web API with its six provider packages). The main docs don't link to it yet, so browsing won't get you there. That one's on us, it's in the queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually on the site
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/evaluate/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Deciding. 24 feature pages, the edition list, licensing and trial keys.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/get-started/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Running it. Install, first workflow, designer integration, 17 concept pages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/roadmap/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;What's next.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/video-tutorials/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;For people who'd rather watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The feature pages are one page per capability, named the way the product names them: Visual Designer, Simple Process Notation, Process Versioning, Parallel Processes, Timers &amp;amp; Scheduling, Direct State Control, HTTP API, BPMN Support, Plugin System, Pluggable Security, Clustering, Multitenancy, Workflow Forms, Customization, Multi-Database Support, Bulk API, Work Calendar, Pre-Execution (Simulation), Process Logs, Workflow as Code, Database Versioning, Offline API, Interactive Designer, Workflow Templates.&lt;/p&gt;

&lt;p&gt;The concept pages were the ones I most wanted to exist. Seventeen terms, one page each, no page trying to explain four things at once: activity, transition, command, scheme, process instance, workflow runtime, persistence, action, parameter, condition, rule, timer, annotation, process logs, localization, subprocess, work calendar. Workflow tools all use the same handful of words to mean slightly different things, and a shared vocabulary saves a lot of arguing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fifteen minutes, roughly
&lt;/h2&gt;

&lt;p&gt;Get Started is three short pages: install, define a scheme, start an instance. Here's the shape of it, with SQLite because it doesn't need a server running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapi &lt;span class="nt"&gt;-n&lt;/span&gt; WorkflowApi &lt;span class="nt"&gt;--framework&lt;/span&gt; net10.0
&lt;span class="nb"&gt;cd &lt;/span&gt;WorkflowApi
dotnet add package OptimaJet.Workflow.Api
dotnet add package OptimaJet.Workflow.Api.Sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the services and map the endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddJsonOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConfigureJson&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWorkflowApiSqlite&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWorkflowApiCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SetupWorkflowApiCore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWorkflowRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SetupWorkflowRuntime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapWorkflowApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can write the scheme as XML, or draw it in the designer without installing the designer at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @optimajet/workflow-designer http://localhost:5274/workflow-api/designer Approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens the full designer in a browser tab, wired to your local Web API. Save from there and the scheme goes into your database. Then start a process instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5274/workflow-api/rpc/create-instance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "schemeCode": "Approval", "processId": "a3f5b2c1-4d6e-7f8a-9b0c-1d2e3f4a5b6c" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need the .NET SDK 10.0, a database (SQLite counts, it's just a file) and, for the &lt;code&gt;rpc/create-instance&lt;/code&gt; call, a Workflow Engine NEO trial key set in &lt;code&gt;options.LicenseKey&lt;/code&gt;. The RPC API is a Workflow Engine NEO capability, so without that key &lt;code&gt;MapWorkflowApi()&lt;/code&gt; maps only the designer and health check endpoints. Console apps and worker services have their own page, &lt;a href="https://docs.workflowengine.io/get-started/framework-agnostic-install/" rel="noopener noreferrer"&gt;Framework-Agnostic Install&lt;/a&gt;. We didn't want ASP.NET Core to be a precondition for trying the library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editions
&lt;/h2&gt;

&lt;p&gt;The license key you pass to &lt;code&gt;WorkflowRuntime.RegisterLicense()&lt;/code&gt; lifts the Workflow Engine Free limits, so moving from Free to Team or Complete is a key swap on the same packages. Workflow Engine NEO is a separate product based on Workflow Engine: its key keeps your existing application running as it is, and its Data API, RPC API, multitenancy and forms need the matching packages and the code that calls them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Edition&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Limits and additions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine Free&lt;/td&gt;
&lt;td&gt;Perpetual, personal and internal commercial use, with attribution&lt;/td&gt;
&lt;td&gt;10 schemas, 4 execution threads, unlimited activities, transitions and commands, Optimajet branding in the designer, no key needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine Team&lt;/td&gt;
&lt;td&gt;Perpetual, internal commercial use&lt;/td&gt;
&lt;td&gt;No schema limit, 8 execution threads, up to 100 activities, 500 transitions and 100 commands per schema, single server, 365 days of updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine Complete&lt;/td&gt;
&lt;td&gt;Perpetual, internal commercial use&lt;/td&gt;
&lt;td&gt;No usage limits, multi-server clustering, no designer branding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine NEO Subscription&lt;/td&gt;
&lt;td&gt;Annual subscription&lt;/td&gt;
&lt;td&gt;Data API and RPC API, full hybrid multitenancy, clustering, forms integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine NEO Business&lt;/td&gt;
&lt;td&gt;Perpetual&lt;/td&gt;
&lt;td&gt;Data API and RPC API, full hybrid multitenancy, clustering, forms integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine NEO SaaS&lt;/td&gt;
&lt;td&gt;Perpetual&lt;/td&gt;
&lt;td&gt;Everything in NEO Business, plus the Active Directory Plugin and the BPMN Plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow Engine NEO Enterprise&lt;/td&gt;
&lt;td&gt;Perpetual&lt;/td&gt;
&lt;td&gt;All of the above, plus white-label canvas, OEM redistribution, a Form Engine Enterprise license with OEM rights, one year of source code access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Details are on &lt;a href="https://docs.workflowengine.io/evaluate/workflow-engine-editions/" rel="noopener noreferrer"&gt;Workflow Engine Editions&lt;/a&gt;, key mechanics on &lt;a href="https://docs.workflowengine.io/evaluate/license-key/" rel="noopener noreferrer"&gt;License Key&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation status
&lt;/h2&gt;

&lt;p&gt;The documentation at &lt;strong&gt;docs.workflowengine.io&lt;/strong&gt; is the new primary documentation for Workflow Engine. It is intended to replace the legacy documentation hosted at &lt;strong&gt;workflowengine.io/documentation/&lt;/strong&gt; over time.&lt;/p&gt;

&lt;p&gt;The migration is still in progress. The new documentation already covers evaluation, installation, concepts, features, tutorials and API usage, but some reference material remains on the legacy site while it is being reviewed, updated and migrated.&lt;/p&gt;

&lt;p&gt;At the moment both documentation sites are maintained. If a topic cannot yet be found in the new documentation, the legacy documentation remains the authoritative source for that section until migration is complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hasn't moved yet
&lt;/h2&gt;

&lt;p&gt;This is the section most docs announcements leave out, and it's the one that saves you a search.&lt;/p&gt;

&lt;p&gt;Still on the old host at &lt;code&gt;workflowengine.io/documentation/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web API setup and Web API security reference&lt;/li&gt;
&lt;li&gt;Release notes and version history&lt;/li&gt;
&lt;li&gt;BPMN element tables, BPMN differences, the BPMN plugin reference&lt;/li&gt;
&lt;li&gt;Database entity tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those pages are alive and correct, they just haven't been rewritten for the new site. And Workflow Server, which is a different product with its own license, documents itself at &lt;a href="https://workflowserver.io/documentation/" rel="noopener noreferrer"&gt;workflowserver.io/documentation/&lt;/a&gt;. Don't go hunting for it under the engine docs, it isn't there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go for what
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want&lt;/th&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install into ASP.NET Core&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/get-started/install-workflow-engine/" rel="noopener noreferrer"&gt;&lt;code&gt;/get-started/install-workflow-engine/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install into a console app or worker&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/get-started/framework-agnostic-install/" rel="noopener noreferrer"&gt;&lt;code&gt;/get-started/framework-agnostic-install/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your first workflow&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/get-started/create-your-first-workflow/" rel="noopener noreferrer"&gt;&lt;code&gt;/get-started/create-your-first-workflow/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run or embed the designer&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/get-started/integrate-the-designer/" rel="noopener noreferrer"&gt;&lt;code&gt;/get-started/integrate-the-designer/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A term you don't recognise&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/get-started/concepts/workflow-engine-concepts/" rel="noopener noreferrer"&gt;&lt;code&gt;/get-started/concepts/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One feature in depth&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.workflowengine.io/evaluate/workflow-engine-features/" rel="noopener noreferrer"&gt;&lt;code&gt;/evaluate/workflow-engine-features/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A C# type or method signature&lt;/td&gt;
&lt;td&gt;&lt;a href="https://api.workflowengine.io/" rel="noopener noreferrer"&gt;api.workflowengine.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A trial key&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trial.workflowengine.io/" rel="noopener noreferrer"&gt;trial.workflowengine.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Questions we get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is Workflow Engine by Optimajet?&lt;/strong&gt;&lt;br&gt;
An embeddable .NET library for workflow automation. It's a NuGet package inside your application, using your database, running in-process. No separate workflow server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Workflow Engine open source?&lt;/strong&gt;&lt;br&gt;
No. Commercial software under a commercial license, with a free product, Workflow Engine Free. It is perpetual and needs no key. It covers personal and internal commercial use, with attribution, capped at 10 schemas and 4 execution threads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which databases does it support?&lt;/strong&gt;&lt;br&gt;
SQL Server 2017 and above, PostgreSQL, MySQL, Oracle, SQLite, MongoDB. You install the matching provider package and register it at startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which .NET versions?&lt;/strong&gt;&lt;br&gt;
.NET Framework 4.6.2 and up, plus modern .NET and ASP.NET Core. The ASP.NET Core quickstart targets the .NET SDK 10.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the documentation now?&lt;/strong&gt;&lt;br&gt;
Main docs at docs.workflowengine.io. Generated C# API reference at api.workflowengine.io. Web API setup and security, release notes, BPMN tables and database entity tables are still at workflowengine.io/documentation/. Workflow Server is at workflowserver.io/documentation/.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can an AI agent read the docs?&lt;/strong&gt;&lt;br&gt;
Yes, that's what &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt; are for, and the API reference host has its own &lt;code&gt;llms.txt&lt;/code&gt;. An agent can also get a trial key by following &lt;code&gt;trial.workflowengine.io/llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just write my own state machine?&lt;/strong&gt;&lt;br&gt;
Some teams should. The moment you need persistence, timers, rule evaluation, scheme versioning, clustering and a visual editor a business user can open, you're building a product next to your product. That's the trade we ask people to think about before adopting anything, ours included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go poke at it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.workflowengine.io/get-started/" rel="noopener noreferrer"&gt;docs.workflowengine.io/get-started/&lt;/a&gt; if you want code in front of you in the next quarter of an hour. &lt;a href="https://docs.workflowengine.io/evaluate/" rel="noopener noreferrer"&gt;docs.workflowengine.io/evaluate/&lt;/a&gt; if you're still deciding whether an embedded engine beats rolling your own.&lt;/p&gt;

&lt;p&gt;And if a page is wrong, missing or confusing, tell me in the comments. I'd rather hear it here than not hear it at all.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>documentation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Workflow Engine NEO 22.0.0: the multitenancy questions SaaS teams should ask</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:04:16 +0000</pubDate>
      <link>https://dev.to/optimajet/multi-tenant-saas-on-a-net-workflow-engine-without-restarting-to-onboard-a-customer-h3a</link>
      <guid>https://dev.to/optimajet/multi-tenant-saas-on-a-net-workflow-engine-without-restarting-to-onboard-a-customer-h3a</guid>
      <description>&lt;p&gt;On July 16, 2026, we released &lt;a href="https://workflowengine.io/documentation/release-notes/22.0.0/" rel="noopener noreferrer"&gt;Workflow Engine NEO 22.0.0&lt;/a&gt;. Version 22 builds on the existing &lt;code&gt;TenantId&lt;/code&gt; and multi-tenant Web API support and addresses two harder problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Workflow Engine Web API host can now add and remove tenant configurations while the application is running; and&lt;/li&gt;
&lt;li&gt;logical tenant scope now reaches workflow definitions, configuration, forms, processes, and process-related data more consistently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these changes complete support for physical, logical, and hybrid multitenancy in one Workflow Engine Web API host. A SaaS platform can give selected customers dedicated runtimes and databases, place others in logically isolated groups on shared infrastructure, and manage the entire topology through the same application. This flexibility is the central architectural capability in version 22.&lt;/p&gt;

&lt;p&gt;I am Mike Lukinov, co-founder of &lt;a href="https://optimajet.com/" rel="noopener noreferrer"&gt;Optimajet&lt;/a&gt;. This article explains how the three models fit together, then reviews the questions a SaaS team should answer before choosing its tenant architecture: where isolation lives, how tenants enter and leave the system, where authorization is enforced, what may be shared, and where product responsibilities split.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hybrid multitenancy means
&lt;/h2&gt;

&lt;p&gt;Workflow Engine Web API separates physical and logical tenancy.&lt;/p&gt;

&lt;p&gt;A physical tenant is a runtime and provider boundary. It can use a dedicated database and can serve one or more logical tenant IDs. Logical tenants share that runtime and provider, while their workflow data is separated by &lt;code&gt;TenantId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hybrid multitenancy means using both mappings in one Web API host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One ASP.NET / Workflow Engine Web API host
|
|-- Physical tenant A
|   |-- Runtime and provider A
|   |-- Dedicated database A
|   `-- Logical tenant ID: acme
|
`-- Physical tenant B
    |-- Runtime and provider B
    |-- Shared database B
    `-- Logical tenant IDs: globex, stark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Acme has its own runtime and database boundary. Globex and Stark share a runtime and database with logical separation. That combination is hybrid tenancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five architecture questions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Architecture decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where should the isolation boundary sit?&lt;/td&gt;
&lt;td&gt;Shared database, dedicated database, or a combination of both.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does onboarding require a deployment?&lt;/td&gt;
&lt;td&gt;Whether the tenant catalog belongs to application startup or to a runtime control plane.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where is the security boundary?&lt;/td&gt;
&lt;td&gt;How identity, tenant authorization, routing, and persistence work together.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is isolated and what may be shared?&lt;/td&gt;
&lt;td&gt;Which data belongs exclusively to a tenant and which definitions act as common defaults.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where does the engine's responsibility end?&lt;/td&gt;
&lt;td&gt;The boundary between Workflow Engine and the surrounding SaaS control plane.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Question 1: where should the isolation boundary sit?
&lt;/h2&gt;

&lt;p&gt;Workflow Engine NEO supports three answers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logical tenancy&lt;/strong&gt; puts several tenant IDs behind one runtime, provider, and database. It minimizes the number of infrastructure units while separating workflow data by tenant ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical tenancy&lt;/strong&gt; gives a tenant its own runtime and provider configuration. When its provider points to a dedicated database, it also creates an independent backup and restore boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid tenancy&lt;/strong&gt; uses physical isolation for customers that require it and logical isolation for customers that can share infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice should follow operational requirements: data residency, regulatory commitments, independent backup and restore, database-provider requirements, and the acceptable impact of a database-level incident.&lt;/p&gt;

&lt;p&gt;A physical tenant remains part of the same ASP.NET application deployment. Its boundary covers runtime and persistence, while the application version and host process stay shared. Tenant-specific definitions also work in the logical model, allowing customized workflows to use shared infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 2: does onboarding require a deployment?
&lt;/h2&gt;

&lt;p&gt;Version 22.0.0 lets a multi-tenant host start with a partial tenant catalog and accept tenant registrations later. Adding or removing a Workflow Engine tenant becomes a control-plane operation during the lifetime of the ASP.NET application.&lt;/p&gt;

&lt;p&gt;The important architectural property is consistency during change. New requests use the updated tenant map, while requests already in progress continue with the view under which they started. Removing a tenant stops new requests from being routed to it without invalidating work that is already using the previous routing state.&lt;/p&gt;

&lt;p&gt;Customer onboarding still spans the wider SaaS control plane:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provision a database or choose a shared one;&lt;/li&gt;
&lt;li&gt;create and store credentials;&lt;/li&gt;
&lt;li&gt;configure identity, billing, quotas, and product-specific data; and&lt;/li&gt;
&lt;li&gt;register the resulting runtime and tenant mapping with Workflow Engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Offboarding has the same separation of concerns. Removing a tenant affects routing, while database lifecycle and retention remain separate. Traffic removal, runtime shutdown, archival, and data erasure should remain explicit operations.&lt;/p&gt;

&lt;p&gt;Adding a physical tenant changes the runtime and provider topology. Adding a logical ID extends an existing shared tenant group. The control plane should model them as distinct operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 3: where is the security boundary?
&lt;/h2&gt;

&lt;p&gt;Tenant routing and tenant authorization are separate decisions. The tenant identifier on a request selects context. Caller identity and permissions establish access to that context.&lt;/p&gt;

&lt;p&gt;An effective boundary has four layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity and authorization&lt;/td&gt;
&lt;td&gt;Establish who the caller is and which tenant and operations the caller may access.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant routing&lt;/td&gt;
&lt;td&gt;Map the selected logical tenant to the correct runtime and provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow persistence&lt;/td&gt;
&lt;td&gt;Keep built-in process reads and writes inside the selected tenant or dedicated database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application integration&lt;/td&gt;
&lt;td&gt;Apply the same tenant rule to custom endpoints, actions, external services, and application-owned tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Version 22.0.0 strengthens the built-in Workflow Engine Web API path: Data API and RPC API process operations apply the current tenant context to process access.&lt;/p&gt;

&lt;p&gt;Application-wide tenant safety also depends on direct provider access, custom queries, domain tables, file storage, caches, and message handlers carrying the same tenant context. Workflow Engine enforces the data it owns; the application extends that boundary across the rest of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 4: what is isolated and what may be shared?
&lt;/h2&gt;

&lt;p&gt;The answer differs for operational data and reusable configuration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data group&lt;/th&gt;
&lt;th&gt;Tenant behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process instances and operational records&lt;/td&gt;
&lt;td&gt;Tenant-scoped. This includes process status, parameters, timers, transition history, inbox entries, and approval history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow definitions&lt;/td&gt;
&lt;td&gt;A standard definition can be shared, while a tenant-specific definition can replace it for one customer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forms&lt;/td&gt;
&lt;td&gt;Shared forms can act as defaults, with tenant-specific forms taking priority where configured.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global Parameters&lt;/td&gt;
&lt;td&gt;Values can live in shared or tenant scope. If the application wants shared values to act as defaults, that fallback should be an explicit policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives a SaaS product a useful customization model: most customers can run a standard approval flow, while selected customers use a tenant-specific version without requiring another application deployment.&lt;/p&gt;

&lt;p&gt;The safe general rule is simple: process state belongs to one tenant; reusable definitions and configuration may be shared deliberately. Records without a tenant value follow the sharing and fallback policy of their resource type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 5: where does Workflow Engine's responsibility end?
&lt;/h2&gt;

&lt;p&gt;Workflow Engine NEO 22.0.0 supplies the workflow-side building blocks: tenant topology, runtime lifecycle, tenant-aware API behavior, and shared or scoped workflow resources. The surrounding SaaS architecture decides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which customers require dedicated storage;&lt;/li&gt;
&lt;li&gt;how databases, secrets, identities, and non-workflow data are provisioned;&lt;/li&gt;
&lt;li&gt;how users acquire membership in a tenant;&lt;/li&gt;
&lt;li&gt;how long offboarded tenant data is retained;&lt;/li&gt;
&lt;li&gt;how custom application code enforces tenant context; or&lt;/li&gt;
&lt;li&gt;how tenant limits, billing, and product entitlements are applied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are SaaS architecture and product-policy decisions. Workflow Engine participates in one part of the boundary, and the application completes it. Each responsibility should have an explicit owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  An architecture review checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist during the architecture review. For every item, record the decision, its owner, and the evidence that confirms it. The review is complete when the team has produced all five artifacts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;A tenant placement map.&lt;/strong&gt; Assign each customer segment to a logical, physical, or hybrid topology. Show which tenant IDs share a runtime and database, which customers receive dedicated resources, and which requirement drives each choice.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;A tenant lifecycle flow.&lt;/strong&gt; Document the complete onboarding and offboarding sequence: infrastructure provisioning, secret creation, tenant registration, traffic activation, routing removal, archival, and deletion. Name the system or team responsible for every step.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;An authorization rule and its test.&lt;/strong&gt; Define where the tenant ID comes from, how caller membership is verified, and which permissions are required before routing. Add a test that changes the tenant ID while keeping the same identity and confirms that access is rejected.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;A data ownership matrix.&lt;/strong&gt; Classify processes, operational records, workflow definitions, forms, and Global Parameters as tenant-owned or shared. Record the override and fallback policy for every shared resource type.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;A responsibility map for integrations.&lt;/strong&gt; List custom endpoints, direct provider calls, background jobs, application tables, files, caches, and messages. Assign an owner and document how each path receives and enforces tenant context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting placement map, lifecycle flow, authorization test, data matrix, and responsibility map become the implementation contract for multitenancy. They give developers, security reviewers, and operations teams the same definition of the tenant boundary.&lt;/p&gt;

&lt;p&gt;The detailed migration steps and API changes are in the &lt;a href="https://workflowengine.io/documentation/release-notes/22.0.0/" rel="noopener noreferrer"&gt;Workflow Engine 22.0.0 release notes&lt;/a&gt;. For Workflow Engine NEO licensing options, see &lt;a href="https://optimajet.com/products/pricing/" rel="noopener noreferrer"&gt;Optimajet product pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to check our answers against your own architecture, the upgrade details are on &lt;a href="https://workflowengine.io/blog/" rel="noopener noreferrer"&gt;our blog&lt;/a&gt;, and my team does &lt;a href="https://workflowengine.io/contacts/" rel="noopener noreferrer"&gt;demo walkthroughs&lt;/a&gt; where we build the argument on your tenancy model, not ours.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>saas</category>
      <category>architecture</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Workflow Engine NEO trials are self-serve now, and your coding agent can grab one too</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Thu, 11 Jun 2026 14:17:10 +0000</pubDate>
      <link>https://dev.to/optimajet/workflow-engine-neo-trials-are-self-serve-now-and-your-coding-agent-can-grab-one-too-38am</link>
      <guid>https://dev.to/optimajet/workflow-engine-neo-trials-are-self-serve-now-and-your-coding-agent-can-grab-one-too-38am</guid>
      <description>&lt;p&gt;You can grab a Workflow Engine NEO trial key yourself now, no email thread. Go to &lt;strong&gt;&lt;a href="https://trial.workflowengine.io/" rel="noopener noreferrer"&gt;trial.workflowengine.io&lt;/a&gt;&lt;/strong&gt;, type a business email and a company name, and a 30-day trial key with all features shows up in your inbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm actually excited about
&lt;/h2&gt;

&lt;p&gt;There's a sibling endpoint for AI coding agents. &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;https://trial.workflowengine.io/api/trial/llm&lt;/code&gt; with &lt;code&gt;{ "email": "...", "companyName": "..." }&lt;/code&gt; and you get the same key back. Contract is at &lt;a href="https://trial.workflowengine.io/llms.txt" rel="noopener noreferrer"&gt;trial.workflowengine.io/llms.txt&lt;/a&gt;. Business email only (no gmail, yahoo, disposable).&lt;/p&gt;

&lt;p&gt;Two more &lt;code&gt;llms.txt&lt;/code&gt; files live on the docs side and pair with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.workflowengine.io/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;https://docs.workflowengine.io/llms.txt&lt;/code&gt;&lt;/a&gt; is the index of the current documentation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workflowengine.io/documentation/llms-full.txt" rel="noopener noreferrer"&gt;&lt;code&gt;https://workflowengine.io/documentation/llms-full.txt&lt;/code&gt;&lt;/a&gt; is the full reference in one file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hand all three URLs to your agent (Claude Code, Cursor, Copilot, whatever you use) and it can pull a license and read current docs in the same session. Our library has been through several versions where API names moved around. If your agent writes Workflow Engine NEO code from memory, half the time it'll write something that compiled in 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt I give my own agent
&lt;/h2&gt;

&lt;p&gt;Paste this into your agent's system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are working with OptimaJet Workflow Engine NEO.

Before writing or recommending code:
1. Fetch https://docs.workflowengine.io/llms.txt for the index of the current documentation.
2. Fetch https://workflowengine.io/documentation/llms-full.txt for the full reference.
3. Treat these as the source of truth. Do NOT rely on pre-trained knowledge
   of Workflow Engine NEO; the API surface has changed across versions.

If you need a working license to run examples:
- POST {"email":"you@yourcompany.com","companyName":"Your Co"} to
  https://trial.workflowengine.io/api/trial/llm
- The contract is documented at https://trial.workflowengine.io/llms.txt
- Business email required (no gmail/yahoo/disposable). Three keys max per email.
- The key is returned in the response body and also emailed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When those three URLs are included in the context, your agent’s code actually compiles. Without them, it picks the wrong overload most of the time.&lt;/p&gt;

&lt;p&gt;If you grab a key and run into something the docs don't cover, or you want help planning a rollout, book a call at &lt;strong&gt;&lt;a href="https://workflowengine.io/contacts/" rel="noopener noreferrer"&gt;workflowengine.io/contacts/&lt;/a&gt;&lt;/strong&gt;. Bring a concrete task and we can scope a free POC.&lt;/p&gt;

&lt;p&gt;Tell us in the comments if anything here reads off.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>agents</category>
      <category>workflow</category>
    </item>
    <item>
      <title>When to use Data API vs RPC API in Workflow Engine NEO: a .NET architect's guide</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Tue, 19 May 2026 16:22:40 +0000</pubDate>
      <link>https://dev.to/optimajet/when-to-use-data-api-vs-rpc-api-in-workflow-engine-neo-a-net-architects-guide-3cd5</link>
      <guid>https://dev.to/optimajet/when-to-use-data-api-vs-rpc-api-in-workflow-engine-neo-a-net-architects-guide-3cd5</guid>
      <description>&lt;p&gt;I am Mike Lukinov, co-founder of &lt;a href="https://optimajet.com/" rel="noopener noreferrer"&gt;Optimajet&lt;/a&gt;. Since 2022, I have been closely involved in customer architecture and developer experience work around &lt;a href="https://optimajet.com/workflowengine/" rel="noopener noreferrer"&gt;Workflow Engine by Optimajet&lt;/a&gt;. I sit in most customer architecture meetings where Workflow Engine NEO questions repeat. The pattern I see most often when teams adopt Workflow Engine NEO is this: an architect picks the RPC API for every integration because "the RPC API is the obvious API." Six months later that team has a BI dashboard, a mobile app, and a partner integration all calling the same RPC surface, and a security review is asking why a read-only analyst token can start and cancel processes. This is the article I wrote instead of answering each case from scratch.&lt;/p&gt;

&lt;p&gt;Workflow Engine NEO ships two HTTP surfaces, the Data API and the RPC API; the choice between them is a security and blast-radius decision, not a syntax preference. Everything below is personal opinion, grounded in those customer conversations. Throughout I mark each block as &lt;strong&gt;Documented behavior&lt;/strong&gt;, &lt;strong&gt;Architectural recommendation&lt;/strong&gt;, or &lt;strong&gt;Risk if ignored&lt;/strong&gt;, so you can separate Optimajet facts from opinion. The framing should hold even if you pick a different boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version note.&lt;/strong&gt; The code examples below target Workflow Engine NEO v21.0.0 and later, where Web API authorization uses a compact &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; claim and the builder API. If you are on v20.x or earlier, check the matching documentation for the older per-operation claim format; do not copy v21 snippets into a v20 project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Workflow Engine NEO Data API and RPC API actually do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Documented behavior.&lt;/strong&gt; According to the &lt;a href="https://workflowengine.io/documentation/web-api" rel="noopener noreferrer"&gt;Workflow Engine Web API setup documentation retrieved 2026-05-12&lt;/a&gt;, each surface has a one-sentence definition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"RPC API: Remotely manage Workflow Engine Runtime instances in single-tenant or multi-tenant modes."&lt;br&gt;
"RESTful Data API: Interact safely with the Workflow Engine database without disrupting internal processes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Web API Core is provided by &lt;a href="https://www.nuget.org/packages/OptimaJet.Workflow.Api" rel="noopener noreferrer"&gt;&lt;code&gt;OptimaJet.Workflow.Api&lt;/code&gt;&lt;/a&gt; for .NET 8.0 and .NET 10, and Data access is added through a provider-specific package such as &lt;code&gt;OptimaJet.Workflow.Api.Mssql&lt;/code&gt;, &lt;code&gt;OptimaJet.Workflow.Api.Postgres&lt;/code&gt;, &lt;code&gt;OptimaJet.Workflow.Api.Mysql&lt;/code&gt;, &lt;code&gt;OptimaJet.Workflow.Api.Oracle&lt;/code&gt;, &lt;code&gt;OptimaJet.Workflow.Api.Sqlite&lt;/code&gt;, or &lt;code&gt;OptimaJet.Workflow.Api.Mongo&lt;/code&gt;. Workflow Engine NEO is a separate product based on Workflow Engine; the RPC API and Web API multitenancy were introduced in v19.0.0 per the &lt;a href="https://workflowengine.io/documentation/release-notes/19.0.0" rel="noopener noreferrer"&gt;19.0.0 release notes&lt;/a&gt;. The two surfaces share the same multi-tenant header, &lt;code&gt;Workflow-Api-Tenant-ID&lt;/code&gt;, the same &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; claim model, and are exposed through the same Workflow Engine Web API service with a generated OpenAPI specification. What differs is the operations branch each one exposes, and that difference is where the design intent lives.&lt;/p&gt;

&lt;p&gt;The Data API and the RPC API require a Workflow Engine NEO license key; without it, the Web API starts with only the designer and health check endpoints. The Data/RPC split is therefore a paid Workflow Engine NEO capability; Workflow Engine (Team and Complete editions) and Workflow Engine Free (formerly Community Edition) do not include either API, per the &lt;a href="https://optimajet.com/products/workflowengine/price/" rel="noopener noreferrer"&gt;Workflow Engine pricing and editions page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use the Workflow Engine NEO Data API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Architectural recommendation.&lt;/strong&gt; Use the Data API as the default integration boundary for reporting, audit export, BI dashboards, and support tools that should observe workflow state without driving runtime behavior. The Data API is the right place to surface workflow data to consumers whose blast radius you want to keep small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documented behavior, important clarification.&lt;/strong&gt; The Data API is not strictly read-only. The documented Data API surface includes create, update, and delete operations for global parameters, workflow schemes, process parameters and timers, and delete operations for transitions, approvals, and inbox entries. Calling the Data API does not move a process through state transitions the way an RPC command does, but the Data API can mutate the underlying data. Read-only consumers must therefore receive only the specific &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;get-collection&lt;/code&gt; permissions for the entities they need, not a blanket allow over the Data API surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural recommendation, with v21 code.&lt;/strong&gt; A practical scope for a read-only analyst persona uses the v21 &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; builder API to deny everything by default, scope to a single tenant, and then explicitly allow concrete read operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claim&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DenyAllOperations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.schemes.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.schemes.get-collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.get-collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.parameters.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.parameters.get-collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.transitions.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.transitions.get-collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.approvals.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.approvals.get-collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.inbox-entries.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"workflow-api.data.processes.inbox-entries.get-collection"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DenyAllTenantsExcept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TenantA"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This token reads schemes, process records, parameters, transitions, approvals, and inbox entries inside &lt;code&gt;TenantA&lt;/code&gt;; it cannot mutate any Data entity or reach other tenants. For cross-tenant analyst access, replace &lt;code&gt;DenyAllTenantsExcept("TenantA")&lt;/code&gt; with &lt;code&gt;AllowAllTenants()&lt;/code&gt; as an explicit administrative decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on process history.&lt;/strong&gt; Process-history operations (&lt;code&gt;get-process-history&lt;/code&gt;, &lt;code&gt;get-process-history-count&lt;/code&gt;) live under &lt;code&gt;workflow-api.rpc.*&lt;/code&gt;, not the Data API. A reporting client that needs them should receive them as scoped read-only RPC exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use the Workflow Engine NEO RPC API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Architectural recommendation.&lt;/strong&gt; Use the RPC API when the caller is meant to drive the runtime: start instances, execute commands, advance state. Bulk operations live exclusively on the RPC API, so any client that needs to start or progress many processes in one round-trip belongs here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documented behavior.&lt;/strong&gt; The operations branch &lt;code&gt;workflow-api.rpc&lt;/code&gt; covers, among others, &lt;code&gt;workflow-api.rpc.bulk-create-instance&lt;/code&gt;, &lt;code&gt;workflow-api.rpc.bulk-execute-command&lt;/code&gt;, &lt;code&gt;workflow-api.rpc.bulk-get-process-instance&lt;/code&gt;, &lt;code&gt;workflow-api.rpc.get-available-commands&lt;/code&gt;, &lt;code&gt;workflow-api.rpc.delete-instance&lt;/code&gt;, and &lt;code&gt;workflow-api.rpc.get-process-history&lt;/code&gt;, per the &lt;a href="https://workflowengine.io/documentation/web-api" rel="noopener noreferrer"&gt;Workflow Engine documentation&lt;/a&gt;. Bulk operations, including both read-like bulk retrieval and runtime-changing operations, live under the RPC API branch.&lt;/p&gt;

&lt;p&gt;The RPC API is the right surface for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polyglot integration. A Node.js worker, a Python ETL job, or a Java partner service that needs to start and progress Workflow Engine processes does so over plain HTTP using the RPC API. The host application stays on .NET, but the callers do not have to.&lt;/li&gt;
&lt;li&gt;Front-end clients that drive workflows directly, such as a React or Angular UI that maps user actions onto &lt;code&gt;workflow-api.rpc.execute-command&lt;/code&gt; or &lt;code&gt;workflow-api.rpc.bulk-execute-command&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Backend microservices that orchestrate Workflow Engine state from outside the host process.
&lt;strong&gt;Risk if ignored.&lt;/strong&gt; Each RPC operation has a separate permission identifier. A token authorised for &lt;code&gt;workflow-api.rpc.execute-command&lt;/code&gt; does not automatically gain access to &lt;code&gt;workflow-api.rpc.delete-instance&lt;/code&gt;. In practice, however, teams tend to grant broad RPC allow lists to operational clients, which is where the real blast radius shows up. Keep &lt;code&gt;execute-command&lt;/code&gt;, &lt;code&gt;delete-instance&lt;/code&gt;, runtime control, and bulk operations in separate permission profiles, and only combine them inside dedicated admin tokens that you audit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; enforces the Data/RPC split at the claim level
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Documented behavior.&lt;/strong&gt; The Workflow Engine NEO permission model treats the Data/RPC split as a security primitive. Each Data API operation has a concrete identifier under &lt;code&gt;workflow-api.data.*&lt;/code&gt; or &lt;code&gt;workflow-api.search.*&lt;/code&gt;, and each RPC API operation one under &lt;code&gt;workflow-api.rpc.*&lt;/code&gt;, and tokens are issued by passing concrete identifiers to the &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; builder. The Workflow Engine documentation states verbatim: "Operations are hierarchical. More specific rule wins on conflict."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural recommendation.&lt;/strong&gt; Build claims from concrete operation identifiers. The pattern works for both Data API and RPC API personas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Persona&lt;/th&gt;
&lt;th&gt;Builder pattern&lt;/th&gt;
&lt;th&gt;What the token can do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BI analyst, Data API only&lt;/td&gt;
&lt;td&gt;Deny all operations, allow concrete Data API operations ending with &lt;code&gt;.get&lt;/code&gt; or &lt;code&gt;.get-collection&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Read schemes, process records, parameters, transitions, approvals, and inbox entries; cannot create, update, or delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow operator, RPC without destructive ops&lt;/td&gt;
&lt;td&gt;Deny all operations, allow concrete RPC operations except &lt;code&gt;delete-instance&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Start instances, execute commands, read; cannot delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin excluding one tenant&lt;/td&gt;
&lt;td&gt;Allow all operations, then &lt;code&gt;AllowAllTenantsExcept("TenantX")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full admin access except the excluded tenant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant-scoped admin&lt;/td&gt;
&lt;td&gt;Allow all operations, then &lt;code&gt;DenyAllTenantsExcept("TenantA")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full admin access inside one tenant only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Recipe table compiled from the v21 &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; builder examples in the Workflow Engine documentation, retrieved 2026-05-12.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk if ignored.&lt;/strong&gt; The same compact format describes tenant access. A note on multi-tenancy from the Workflow Engine documentation: "The tenant header selects the tenant context only. It is not an authorization source. When security services are enabled, the current &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; claim must also allow the selected tenant. If the tenant permission is missing... the request is rejected with 403 Forbidden." Architects who treat the &lt;code&gt;Workflow-Api-Tenant-ID&lt;/code&gt; header as an authorization signal will eventually ship a tenant-cross-bleed bug. The header is routing; the claim is authorization.&lt;/p&gt;

&lt;p&gt;A note for v21.0.0 readers: &lt;code&gt;IWorkflowApiPermissions&lt;/code&gt; was redesigned in v21.0.0, and the synchronous &lt;code&gt;WorkflowRuntime&lt;/code&gt; APIs were removed in the same release, per the &lt;a href="https://workflowengine.io/documentation/release-notes/workflow-engine/" rel="noopener noreferrer"&gt;Workflow Engine release notes retrieved 2026-05-12&lt;/a&gt;. Earlier per-operation claim helpers such as &lt;code&gt;AllowAll&lt;/code&gt;, &lt;code&gt;AllowList&lt;/code&gt;, &lt;code&gt;DenyAll&lt;/code&gt;, &lt;code&gt;DenyList&lt;/code&gt;, &lt;code&gt;GetAllPermissions&lt;/code&gt;, and &lt;code&gt;ValidatePermissions&lt;/code&gt; were removed; use the compact &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; claim and the &lt;code&gt;BuildClaim&lt;/code&gt; builder API from v21.0.0 onward.&lt;/p&gt;

&lt;p&gt;Note: some public security pages still show pre-v21 helpers like &lt;code&gt;AllowList(...)&lt;/code&gt;. Follow the v21 release notes; treat older snippets as v20.x reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision table: which Workflow Engine NEO API does my caller need?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Architectural recommendation.&lt;/strong&gt; The table below is the one I draw on whiteboards in support calls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Prefer&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Permission style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BI dashboard&lt;/td&gt;
&lt;td&gt;Data API&lt;/td&gt;
&lt;td&gt;Reads process records, parameters, transitions, approvals, and related Data API entities without runtime commands&lt;/td&gt;
&lt;td&gt;Only concrete &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;get-collection&lt;/code&gt; permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read-only support panel&lt;/td&gt;
&lt;td&gt;Data API&lt;/td&gt;
&lt;td&gt;Lower operational blast radius&lt;/td&gt;
&lt;td&gt;No create, update, or delete permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit and evidence export&lt;/td&gt;
&lt;td&gt;Data API&lt;/td&gt;
&lt;td&gt;Snapshot of state without affecting running work&lt;/td&gt;
&lt;td&gt;Read-only Data API allow list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External service starts workflows&lt;/td&gt;
&lt;td&gt;RPC API&lt;/td&gt;
&lt;td&gt;Requires runtime command execution&lt;/td&gt;
&lt;td&gt;Only &lt;code&gt;create-instance&lt;/code&gt;, &lt;code&gt;execute-command&lt;/code&gt;, possibly &lt;code&gt;get-available-commands&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front-end UI executes commands&lt;/td&gt;
&lt;td&gt;RPC API&lt;/td&gt;
&lt;td&gt;Maps user actions onto runtime transitions&lt;/td&gt;
&lt;td&gt;Per-user token scoped by tenant and command set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin runtime control&lt;/td&gt;
&lt;td&gt;RPC API&lt;/td&gt;
&lt;td&gt;Requires start, stop, delete, status operations&lt;/td&gt;
&lt;td&gt;Separate privileged token, audited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmm7azdl0nlka5fqq91o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmm7azdl0nlka5fqq91o.png" alt="Data API vs RPC API decision canvas" width="799" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Decision canvas compiled by the author from the Workflow Engine NEO operation branches, 2026-05-12.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk if ignored.&lt;/strong&gt; In multi-tenant deployments, avoid relying on an implicit default tenant for external clients. Prefer requiring every caller to send &lt;code&gt;Workflow-Api-Tenant-ID&lt;/code&gt;, and ensure the token's &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; claim also allows that tenant. Default-deny on the operation tree, allow only the concrete operations the caller's job needs, deny destructive operations explicitly, per the &lt;a href="https://workflowengine.io/documentation/web-api/core" rel="noopener noreferrer"&gt;Workflow API core services documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take this to your team
&lt;/h2&gt;

&lt;p&gt;If you are the architect deciding how to surface Workflow Engine NEO to the rest of your organisation, here is the short version to bring to a design review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision rule.&lt;/strong&gt; Pick the Data API for any caller whose job description does not include changing runtime state. Pick the RPC API when the caller must drive the runtime, accepting that the token will carry a larger blast radius. Scope every token, on both APIs, from concrete operation identifiers via the v21 builder API; do not rely on conceptual branch names, and do not rely on the absence of UI affordances. The Data API is safer as an integration boundary, but only with a narrow allow list of read permissions. The API itself is not automatically read-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Candidate filter.&lt;/strong&gt; With one trusted caller and no audit, BI, or analyst workload on the horizon, you can put everything behind the RPC API for now. With more than one consumer of workflow state, such as an analyst tool, a mobile app, or a partner service, split early. Retrofitting permission boundaries after the first quarter in production is expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of getting it wrong.&lt;/strong&gt; A single RPC token with a broad allow list that lands in a BI tool can move workflow state in ways the audit log records but nobody watches. The same is true of a Data API token granted at branch level when the consumer needs only reads. I have seen both patterns in customer support conversations. The fix is organisational, not code: revoke the token, audit downstream callers, and explain to compliance why a read tool had write access for an unknown window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anticipated pushbacks.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"REST is simpler than two surfaces."&lt;/em&gt; Both APIs are REST/HTTP and share an OpenAPI document; one generator produces one client. The operation tree is what you scope against, so the choice adds a permission boundary, not a protocol.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"We can just gate on roles in the host app."&lt;/em&gt; Role checks in the host app hold only until somebody calls Workflow Engine NEO directly with a valid token. &lt;code&gt;WorkflowApiPermissions&lt;/code&gt; enforces the boundary at the runtime, regardless of caller.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"We do not have multi-tenancy, so the security model is overkill."&lt;/em&gt; The Data/RPC split is independent of multi-tenancy. A single-tenant deployment still benefits from a read-only Data API token for analytics and a separate privileged profile for destructive RPC operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A personal note from the author
&lt;/h2&gt;

&lt;p&gt;Thank you for reading. If you have shipped Workflow Engine NEO and you split your Data and RPC clients differently from how I described, or chose not to split at all, I would like to hear about it. The support conversations I draw from are biased toward teams who are already debating the trade-off, and there are deployment patterns I will not see from where I sit.&lt;/p&gt;

&lt;p&gt;The Data API and the RPC API are capabilities of Workflow Engine NEO, a licensed product, and I have a commercial interest in your choice. Treat the framing above as a starting point, not the end. If you disagree with the decision rule, please leave a comment below or push back on &lt;a href="https://www.linkedin.com/in/mikelukinov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;; that feedback will make the next article in this series sharper.&lt;/p&gt;

&lt;p&gt;If you would rather walk through your own architecture, &lt;a href="https://workflowengine.io/contacts/" rel="noopener noreferrer"&gt;book a demo at workflowengine.io&lt;/a&gt;. Come with a concrete task and constraints, and we will help you scope a free POC against Workflow Engine NEO so you can validate the Data/RPC split on your real data before committing.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>api</category>
      <category>saas</category>
    </item>
    <item>
      <title>React JSON Schema Form Alternative: FormEngine vs RJSF Bundle Size Comparison</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Wed, 11 Feb 2026 19:19:23 +0000</pubDate>
      <link>https://dev.to/optimajet/formengine-vs-rjsf-an-honest-bundle-size-comparison-of-two-json-schema-form-libraries-133n</link>
      <guid>https://dev.to/optimajet/formengine-vs-rjsf-an-honest-bundle-size-comparison-of-two-json-schema-form-libraries-133n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Hi! We're the team at &lt;a href="https://optimajet.com" rel="noopener noreferrer"&gt;Optimajet&lt;/a&gt;, creators of FormEngine. This is an honest technical comparison of our library with react-jsonschema-form (RJSF). All tests are public, reproducible, and &lt;a href="https://github.com/optimajet/formengine/tree/master/community/examples/bundle-size" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We compared two direct competitors in the JSON-based React forms space. RJSF is lighter out of the box (175 KB vs 244 KB). But if you already have Material UI &lt;strong&gt;FormEngine becomes 23% lighter&lt;/strong&gt; (188 KB), while RJSF gets 32% heavier (232 KB). &lt;strong&gt;Your choice of UI library determines the winner.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why RJSF vs FormEngine? 🤔
&lt;/h2&gt;

&lt;p&gt;When we developed FormEngine, we often heard: "How are you different from react-jsonschema-form?"&lt;/p&gt;

&lt;p&gt;Fair question. Both libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React-based&lt;/li&gt;
&lt;li&gt;Work with JSON:

&lt;ul&gt;
&lt;li&gt;RJSF uses JSON Schema to describe forms&lt;/li&gt;
&lt;li&gt;FormEngine uses its own JSON format&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Support Material UI&lt;/li&gt;

&lt;li&gt;Open to customization&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;But there's one fundamental architectural difference that &lt;strong&gt;radically affects bundle size&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RJSF&lt;/strong&gt; — adds UI components on top of the base library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FormEngine&lt;/strong&gt; — completely replaces UI components during integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see how this works in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing Methodology
&lt;/h2&gt;

&lt;p&gt;We tested two realistic forms:&lt;/p&gt;

&lt;h3&gt;
  
  
  Login Form (Simple Case)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email + Password fields&lt;/li&gt;
&lt;li&gt;Basic validation&lt;/li&gt;
&lt;li&gt;Submit button&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Booking Form (Complex Case)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Multi-step form&lt;/li&gt;
&lt;li&gt;Conditional logic&lt;/li&gt;
&lt;li&gt;Different field types (text, dates, selects)&lt;/li&gt;
&lt;li&gt;Validation at each step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Conditions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production build (minified + gzipped)&lt;/li&gt;
&lt;li&gt;Vite as bundler&lt;/li&gt;
&lt;li&gt;Tree-shaking enabled&lt;/li&gt;
&lt;li&gt;Default configurations&lt;/li&gt;
&lt;li&gt;Two variants: with default UI (FormEngine Core has no default UI, React Suite v5 was used) and with Material UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full methodology: &lt;a href="https://formengine.io/documentation/bundle-size-comparison" rel="noopener noreferrer"&gt;formengine.io/documentation/bundle-size-comparison&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Results (gzip sizes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Login Form - Default UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🥇 RJSF           175.26 KB  (baseline)
🥈 FormEngine + React Suite (v5)   244.19 KB  (+39% heavier)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RJSF wins&lt;/strong&gt; in its pure form. Bootstrap CSS is lighter than React Suite.&lt;/p&gt;




&lt;h3&gt;
  
  
  Login Form - With Material UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🥇 FormEngine     188.54 KB  (-23% from default!)  
🥈 RJSF           231.62 KB  (+32% from default)

Difference: 43 KB in favor of FormEngine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The picture changes!&lt;/strong&gt; FormEngine becomes lighter when replacing React Suite with MUI.&lt;/p&gt;




&lt;h3&gt;
  
  
  Booking Form - Default UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🥇 RJSF           178.07 KB  (baseline)
🥈 FormEngine + React Suite (v5)    316.69 KB  (+78% heavier)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RJSF is ahead again. But the difference has grown — complex forms require more components.&lt;/p&gt;




&lt;h3&gt;
  
  
  Booking Form - With Material UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🥇 FormEngine     199.26 KB  (-37% from default!)  
🥈 RJSF           234.50 KB  (+32% from default)

Difference: 35 KB in favor of FormEngine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FormEngine is lighter again&lt;/strong&gt; after MUI integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Insight: How UI Kit Changes Everything
&lt;/h2&gt;

&lt;p&gt;Here's what happens when switching from default UI to Material UI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Form&lt;/th&gt;
&lt;th&gt;FormEngine&lt;/th&gt;
&lt;th&gt;RJSF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Login&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;-55.6 KB (-23%)&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;td&gt;+56.4 KB (+32%) ⬆️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Booking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;-117.4 KB (-37%)&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;td&gt;+56.4 KB (+32%) ⬆️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why Does FormEngine Become Lighter?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Comparison of Different UI Library Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FormEngine + React Suite by default:
├── Core engine (~50 KB)
├── React Suite components (~190 KB)
└── React Suite CSS (~52 KB)
= 244 KB total

FormEngine + MUI:
├── Core engine (~50 KB)
├── MUI components (already in project)
└── Minimal CSS (~0.2 KB)
= 188 KB total (-23%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Does RJSF Become Heavier?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Additive Architecture:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RJSF by default:
├── Core engine (~156 KB)
└── Bootstrap CSS (~19 KB)
= 175 KB total

RJSF + MUI:
├── Core engine (~156 KB)
├── MUI adapter layer (~20 KB)
├── MUI components (already in project)
└── Minimal CSS (~0.2 KB)
= 232 KB total (+32%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MUI is &lt;strong&gt;added on top&lt;/strong&gt;, not replacing the base components.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does This Mean for Real Users?
&lt;/h2&gt;

&lt;p&gt;A difference of 35-43 KB might seem small, but let's translate it into seconds:&lt;/p&gt;

&lt;h3&gt;
  
  
  On Slow 3G (Emerging Markets):
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;43 KB Difference&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1.2 sec&lt;/td&gt;
&lt;td&gt;Slower UI appearance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1.0 sec&lt;/td&gt;
&lt;td&gt;Slower interactivity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  On 4G (Typical Conditions):
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;43 KB Difference&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~0.13 sec&lt;/td&gt;
&lt;td&gt;Almost imperceptible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~0.15 sec&lt;/td&gt;
&lt;td&gt;Barely noticeable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; If you have users on slow networks (emerging markets, mobile internet in subways, poor WiFi) — every second is critical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusions (And What We Learned About Our Own Product)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Context Is Everything&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You can't say "FormEngine is better than RJSF" or vice versa. The right answer depends on your project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose RJSF if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need minimal baseline&lt;/li&gt;
&lt;li&gt;JSON Schema is your primary source of truth&lt;/li&gt;
&lt;li&gt;Simple forms with basic validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose FormEngine if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already using Material UI or another UI kit&lt;/li&gt;
&lt;li&gt;Need deep component customization&lt;/li&gt;
&lt;li&gt;Complex forms with conditional logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Our Competitive Advantage Is Integration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We initially designed FormEngine for &lt;strong&gt;embedding into existing projects&lt;/strong&gt; with already installed UI libraries. This architectural decision paid off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minus 23-37% bundle when integrating with MUI compared to React Suite&lt;/li&gt;
&lt;li&gt;Complete UI replacement instead of layering&lt;/li&gt;
&lt;li&gt;Zero overhead for components&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Different Approach to Describing Forms in JSON&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is one of the most important differences that affects not only DX but also bundle size:&lt;/p&gt;

&lt;h4&gt;
  
  
  RJSF: Separate Schemas (Data Schema + UI Schema)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data-schema.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;describes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minLength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ui-schema.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;describes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;UI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:widget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:placeholder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enter your email"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:widget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:help"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Min 8 characters"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros of Separation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅&lt;strong&gt;Standardization&lt;/strong&gt; — JSON Schema is an open standard&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Reusability&lt;/strong&gt; — one data schema can be used in different UIs&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Validation independent of UI&lt;/strong&gt; — can validate on backend with the same schema&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Ecosystem&lt;/strong&gt; — many tools for JSON Schema (generators, validators, documentation)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Separation of concerns&lt;/strong&gt; — logic separate from presentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons of Separation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Synchronization&lt;/strong&gt; — need to maintain two files in sync&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Verbosity&lt;/strong&gt; — more code for simple forms&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Learning curve&lt;/strong&gt; — need to know both formats&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  FormEngine: Unified JSON (Data + UI in One JSON)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;form.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;everything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;place&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TextField"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"placeholder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enter your email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PasswordField"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"help"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Min 8 characters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"minLength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros of Unification:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Simplicity&lt;/strong&gt; — one file, one source of truth&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Less code&lt;/strong&gt; — no duplication of field names&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Conditional logic&lt;/strong&gt; — easier to describe dependencies between fields&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;DX&lt;/strong&gt; — faster to write and understand&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Compactness&lt;/strong&gt; — less boilerplate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons of Unification:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;No standardization&lt;/strong&gt; — proprietary FormEngine format&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Vendor lock-in&lt;/strong&gt; — harder to migrate to another library&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Validation only on client&lt;/strong&gt; — can't reuse schema on backend directly&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Fewer tools&lt;/strong&gt; — no JSON Schema ecosystem&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;UI and data coupling&lt;/strong&gt; — UI changes can affect logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Impact on Bundle Size
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;RJSF (separate schemas):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ JSON Schema validator (~15-20 KB)
✅ Standard ajv (might already be in project)
❌ UI Schema parser (~5 KB)
❌ Mapping layer between schemas (~3-5 KB)

Total: ~23-30 KB overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FormEngine (unified schema):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Custom validator (~8-12 KB)
✅ Less parsing
❌ No ajv reuse
❌ Proprietary format

Total: ~8-12 KB overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; FormEngine saves ~10-15 KB on schema parsing, but loses standardization benefits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt; Although FormEngine uses a proprietary format, it is fully open and &lt;a href="https://formengine.io/documentation/formengine-core/forms-json" rel="noopener noreferrer"&gt;documented&lt;/a&gt;. This means you can study the specification, write your own parsers or form generators, without depending on closed solutions.&lt;/p&gt;




&lt;h3&gt;
  
  
  Real Example: Complex Conditional Logic
&lt;/h3&gt;

&lt;p&gt;Imagine a form where the "Phone" field is shown only if "Contact by phone" is selected:&lt;/p&gt;

&lt;h4&gt;
  
  
  RJSF Approach:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data-schema.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contactMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^[0-9]{10}$"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"contactMethod"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"contactMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"const"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ui-schema.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:widget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui:options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"conditional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"show"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contactMethod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"is"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The condition is described in two places! Data schema says "phone required if...", UI schema says "show phone if...". Need to synchronize.&lt;/p&gt;

&lt;h4&gt;
  
  
  FormEngine Approach:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RadioGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contactMethod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Contact method"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Phone"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PhoneField"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Phone number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"conditional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"show"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{contactMethod === 'phone'}}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{contactMethod === 'phone'}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^[0-9]{10}$"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantage:&lt;/strong&gt; Condition described once. UI and validation automatically synchronized.&lt;/p&gt;




&lt;h3&gt;
  
  
  When Is Each Approach Better?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Choose RJSF (separate schemas) if:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You already have JSON Schema for API validation&lt;/li&gt;
&lt;li&gt;Need one schema for frontend and backend&lt;/li&gt;
&lt;li&gt;Working in a team where backend generates schemas&lt;/li&gt;
&lt;li&gt;Using JSON Schema ecosystem tools&lt;/li&gt;
&lt;li&gt;Need compatibility with third-party systems&lt;/li&gt;
&lt;li&gt;Simple forms without complex conditional logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Choose FormEngine (unified schema) if:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Forms are complex with many conditions&lt;/li&gt;
&lt;li&gt;UI logic is more important than data validation&lt;/li&gt;
&lt;li&gt;Want to avoid duplication&lt;/li&gt;
&lt;li&gt;DX and development speed are priorities&lt;/li&gt;
&lt;li&gt;Not planning to migrate to another library&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. &lt;strong&gt;Transparency Builds Trust&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We've published all data, including scenarios where RJSF is faster. This is our principle: &lt;strong&gt;honesty is more important than marketing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you find errors in the tests — &lt;a href="https://github.com/optimajet/formengine/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;, we'll fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detailed Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Comparison Table (All Scenarios)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;RJSF (gzip)&lt;/th&gt;
&lt;th&gt;FormEngine (gzip)&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Login default UI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;175.26 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;244.19 KB&lt;/td&gt;
&lt;td&gt;RJSF (-39%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login MUI&lt;/td&gt;
&lt;td&gt;231.62 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;188.54 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FormEngine (-19%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking default UI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;178.07 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;316.69 KB&lt;/td&gt;
&lt;td&gt;RJSF (-78%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking MUI&lt;/td&gt;
&lt;td&gt;234.50 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;199.26 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FormEngine (-15%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Detailed Bundle Breakdown
&lt;/h3&gt;

&lt;h4&gt;
  
  
  RJSF Login (default UI):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code:     480.72 KB raw / 156.37 KB gzip (80%)
CSS:      117.18 KB raw / 19.27 KB gzip (20%)
Total:    597.91 KB raw / 175.64 KB gzip
Chunks:   2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  FormEngine Login (default UI):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code:     626.23 KB raw / 192.65 KB gzip (59%)
CSS:      438.18 KB raw / 52.42 KB gzip (41%)
Total:    1.04 MB raw / 245.07 KB gzip
Chunks:   2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  RJSF Login (MUI):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code:     725.19 KB raw / 232.47 KB gzip (100%)
CSS:      186 B raw / 169 B gzip (0%)
Total:    725.37 KB raw / 232.64 KB gzip
Chunks:   2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  FormEngine Login (MUI):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code:     622.22 KB raw / 189.27 KB gzip (100%)
CSS:      186 B raw / 169 B gzip (0%)
Total:    622.40 KB raw / 189.44 KB gzip
Chunks:   2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Observation:&lt;/strong&gt; After switching to MUI, CSS practically disappears for both libraries (186 bytes). The entire difference is in the JS code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If You DON'T Have a UI Library in Your Project:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recommendation: RJSF

Reason: 
- Lighter out of the box (175 KB vs 244 KB)
- Faster setup
- No need to choose UI kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If You HAVE Material UI:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recommendation: FormEngine

Reason:
- Lighter with MUI (188 KB vs 232 KB)
- No duplication of UI code
- Full integration with your Design System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If You Have Another UI Kit (Ant Design, Chakra, etc):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recommendation: Depends on adapter availability

RJSF: Has adapters for many UI kits
FormEngine: Currently only MUI and React Suite

Check documentation for both libraries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If Planning to Change UI Kit in the Future:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recommendation: FormEngine

Reason:
- UI replacement architecture
- Easier to migrate between UI kits
- Less technical debt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reproducibility
&lt;/h2&gt;

&lt;p&gt;All tests are available and reproducible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/optimajet/formengine/tree/master/community/examples/bundle-size" rel="noopener noreferrer"&gt;github.com/optimajet/formengine/tree/master/community/examples/bundle-size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Report:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://formengine.io/documentation/bundle-size-comparison" rel="noopener noreferrer"&gt;formengine.io/documentation/bundle-size-comparison&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Run Yourself:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/optimajet/formengine.git
&lt;span class="nb"&gt;cd &lt;/span&gt;formengine/community/examples/bundle-size
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm run analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What You'll Get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle size report&lt;/li&gt;
&lt;li&gt;Chunk visualization&lt;/li&gt;
&lt;li&gt;Duplicate packages detection&lt;/li&gt;
&lt;li&gt;Tree-shaking analysis&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Try It Yourself
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;RJSF:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;a href="https://rjsf-team.github.io/react-jsonschema-form/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/rjsf-team/react-jsonschema-form" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FormEngine:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;a href="https://formengine.io/documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/optimajet/formengine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Give Feedback
&lt;/h3&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Found errors in tests&lt;/li&gt;
&lt;li&gt;Want to add new scenarios&lt;/li&gt;
&lt;li&gt;Disagree with conclusions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write to us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let us know what you think in the comments 👇&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/optimajet/formengine/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="mailto:hello@optimajet.com"&gt;hello@optimajet.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;There's no universal winner.&lt;/strong&gt; And that's good! &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RJSF&lt;/strong&gt; — excellent choice for quick start and JSON Schema purists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FormEngine&lt;/strong&gt; — optimal for integration into existing projects with UI kits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose based on your context, not marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your experience with RJSF or FormEngine? Or are you using something else? Share in the comments! 👇&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. We're Optimajet, also making &lt;a href="https://workflowengine.io" rel="noopener noreferrer"&gt;Workflow Engine&lt;/a&gt; for processes and &lt;a href="https://workflowserver.io" rel="noopener noreferrer"&gt;Workflow Server&lt;/a&gt; for business automation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>FormEngine Core is Now Open Source</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Tue, 12 Aug 2025 14:22:39 +0000</pubDate>
      <link>https://dev.to/optimajet/formengine-core-is-now-open-source-n31</link>
      <guid>https://dev.to/optimajet/formengine-core-is-now-open-source-n31</guid>
      <description>&lt;p&gt;If you've ever spent hours wrestling with complex forms in React, struggling with validation, localization, or custom components, FormEngine is here to transform your workflow. This powerful tool combines an intuitive visual designer with a flexible, code-first library, enabling you to create any form, from simple to dynamic, data-driven ones, with ease. Born from our team's real-world challenges, FormEngine empowers solo developers and enterprise teams alike to save time and scale projects effortlessly. Curious about how it became a game-changer for developers? Read on to dive into the full story!&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Ultimate Solution for Building Forms in React.
&lt;/h2&gt;

&lt;p&gt;Here’s a little story about how it all became possible. We began developing FormEngine in early 2023 to address our own internal needs, driven by the immaturity of the React form library ecosystem both in terms of backend-driven form generation and visual form designers. Our goal was to replace our outdated FormBuilder.dev, with a more modern and developer-friendly form builder built for React.&lt;/p&gt;

&lt;p&gt;After a thorough investigation, we hoped to find an open-source library we could build on something robust enough to support our team and integrate seamlessly into our commercial products: &lt;strong&gt;Optimajet Workflow Server&lt;/strong&gt; and form-related plugins for &lt;strong&gt;WorkflowEngine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We evaluated the most popular MIT-licensed libraries and have great respect for React Hook Form, Formik, React Final Form, and SurveyJS, each of which has made a significant contribution to the React ecosystem.&lt;br&gt;
When tested against the strict internal requirements of the Optimajet development team, none could fully deliver the flexibility, integration depth, and scalability needed for our enterprise-grade use cases.&lt;/p&gt;

&lt;p&gt;One of the key requirements especially for large teams in big tech companies or custom software development firms is the ability to easily and quickly integrate custom components. We recognized that this was a common pain point across the industry, and we had the expertise to solve it.&lt;/p&gt;

&lt;p&gt;That insight led us to a bold decision: to build the best React-based FormBuilder from scratch-first and foremost, for ourselves. We surveyed our enterprise customers and small teams building business applications for workflow automation. Based on their feedback and requirements, we formed the foundation for what we believe to be an ideal form library one that supports code-first form creation in React, and serves as the engine behind a low or no-code visual form designer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here is the list of requirements and brief explanations that guided the Optimajet team when we started developing FormEngine Core.
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lower learning curve:&lt;/strong&gt; Easily create forms using typed FormEngine Core functions, without writing components or learning complex React concepts. JSON representation of the form is also supported for configuration-driven use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in form validation:&lt;/strong&gt; Data validation works out of the box with no extra code. Required fields, error messages, and rules are defined directly in code or JSON. Easily integrates with third-party validation libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in localization:&lt;/strong&gt; Forms are ready for multi-language support out of the box. Third-party localization libraries can be integrated if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic CSS and responsive layout support:&lt;/strong&gt; Forms automatically adapt to different devices and styles  no additional layout code needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in actions:&lt;/strong&gt; Forms support actions like submit, reset, and external calls out of the box. Define only what you need in code or JSON and it’ll just work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable forms across apps:&lt;/strong&gt; Cross-project compatibility just copy and use forms are easily portable between applications with minimal effort. A single JSON object contains the full structure, logic, styles, validation, and actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic form creation and dynamic generation:&lt;/strong&gt; Build or modify forms in real time using code or external data sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready-to-use component library:&lt;/strong&gt; Includes built-in components and supports easy integration of custom ones. Get started fast without sacrificing flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy custom component integration&lt;/strong&gt;: Quickly define your own component by specifying its name, type, and props.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready to use with popular frameworks:&lt;/strong&gt; Works seamlessly with Next.js, Remix, and other modern React stacks  no special setup needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High performance and lightweight:&lt;/strong&gt; Fast load times, smooth rendering, and minimal impact on app performance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On &lt;strong&gt;October 31, 2023&lt;/strong&gt;, we released &lt;strong&gt;FormEngine 0.0.13&lt;/strong&gt;  our new standalone commercial product. &lt;a href="https://formengine.io/documentation/release-notes#0013" rel="noopener noreferrer"&gt;Release notes →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This marked the beginning of a journey to bring to life everything our development team had envisioned for the platform. And right from day one, the results confirmed we were on the right track.&lt;/p&gt;

&lt;p&gt;We delivered a complete, time-saving tool for development teams  something that works just as well for solo developers as it does for large projects. Whether you're building a few simple forms or managing hundreds or thousands of dynamically generated ones from a backend  &lt;strong&gt;FormEngine just works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The initial sales were strong, and as demand grew, we even experimented with pricing tiers and product options to better understand what triggered the “Aha!” moment for our users.&lt;/p&gt;

&lt;p&gt;That moment where everything clicks  and the value is obvious.&lt;/p&gt;

&lt;p&gt;We’re still searching for a definitive answer to what exactly made the product such a commercial success, especially among enterprise teams. But one thing is clear: we hit a real need, and we’re excited to keep building on it. &lt;/p&gt;

&lt;p&gt;Thanks to the commercial licenses issued to our partners custom software companies we’ve been improving the &lt;strong&gt;FormEngine&lt;/strong&gt; libraries day by day, with one clear goal:&lt;/p&gt;

&lt;p&gt;To create the best React-based form solution on the market for developers and businesses who want powerful forms without the usual complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We give developers everything they need to build real, working forms  without the usual mess of extra tools, boilerplate, and setup.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@react-form-builder/core" rel="noopener noreferrer"&gt;@react-form-builder/core&lt;/a&gt; Community MIT Open Source Form Library for React  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@react-form-builder/components-rsuite" rel="noopener noreferrer"&gt;@react-form-builder/components-rsuite&lt;/a&gt; Form Components Library &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@react-form-builder/designer" rel="noopener noreferrer"&gt;@react-form-builder/designer&lt;/a&gt; Premium Drag-and-Drop Form Builder, also known as the Visual Form Designer&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FormEngine Core is Now Open Source?
&lt;/h2&gt;

&lt;p&gt;Many of our partners, who use FormEngine as part of their business process automation and approval workflow applications, asked us to open up the core Formengine  so they could deeply integrate it into their systems and even extend its functionality where needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And now, after nearly two years of development and five major releases, the time has come.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;On 26 June 2025, we are proud of the quality, stability, and architecture of our code, and we are ready to share it. By open-sourcing the FormEngine Core library, we make this technology freely available to the global developer community, fostering innovation, transparency, and long-term adoption.&lt;/p&gt;

&lt;p&gt;Your support means the world to us, and we’d love for you to be part of this exciting new chapter for FormEngine.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;If you find value in what we’re building, consider giving us a ⭐️ on &lt;a href="https://github.com/optimajet/formengine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;  it helps more amazing developers like you discover FormEngine Core.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Open source thrives on collaboration, and we warmly welcome contributors who want to join us on this journey. Whether it’s contributing code, sharing ideas, or spreading the word, your involvement will &lt;a href="https://github.com/optimajet/formengine/discussions" rel="noopener noreferrer"&gt;help FormEngine, and our growing community thrive&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With gratitude and excitement, &lt;strong&gt;The Optimajet Developers Team&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>opensource</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A True React Hook Form Alternative – FormEngine Core 4.0 Is Now MIT Licensed and Free! 🚀</title>
      <dc:creator>Optimajet Limited</dc:creator>
      <pubDate>Tue, 27 May 2025 18:06:46 +0000</pubDate>
      <link>https://dev.to/optimajet/a-true-react-hook-form-alternative-formengine-40-is-now-mit-licensed-and-free-1gke</link>
      <guid>https://dev.to/optimajet/a-true-react-hook-form-alternative-formengine-40-is-now-mit-licensed-and-free-1gke</guid>
      <description>&lt;p&gt;React developers, here's big news! On May 15, 2025, Optimajet released FormEngine Core 4.0, making essential libraries available under the permissive MIT license free even for commercial use. FormEngine is now the leading alternative to React Hook Form, especially for teams building complex, interactive forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why switch from React Hook Form to FormEngine?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡️ Rapid and Simple Setup
&lt;/h2&gt;

&lt;p&gt;React Hook Form requires extensive manual setup of controllers, hooks, and validation logic. FormEngine simplifies this by leveraging intuitive, schema-based definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎨 Powerful Drag-and-Drop FormEngine Visual Designer
&lt;/h2&gt;

&lt;p&gt;Unlike React Hook Form’s code-only approach, FormEngine includes an intuitive visual editor, allowing instant form creation and modification, significantly speeding up development. &lt;em&gt;(Note: FormEngine Visual Designer is a paid component for commercial use; however, its full functionality is available for unlimited testing.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔌 Easy Custom Components Integration
&lt;/h2&gt;

&lt;p&gt;React Hook Form’s custom component integration can be cumbersome. FormEngine makes this seamless, simplifying component binding and custom logic implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  📱 Real-Time Form Previews
&lt;/h2&gt;

&lt;p&gt;Get instant visual feedback as you build forms. React Hook Form needs extra setup or third-party tools for similar functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-In Component Library
&lt;/h2&gt;

&lt;p&gt;Unlike React Hook Form, FormEngine comes with a built-in library of ready-to-use components (@react-form-builder/components-rsuite), further accelerating development and reducing the need to create components from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completely Free and MIT Licensed
&lt;/h2&gt;

&lt;p&gt;FormEngine’s core libraries are now MIT-licensed and fully free, even for commercial use:&lt;/p&gt;

&lt;p&gt;📦 &lt;a href="https://www.npmjs.com/package/@react-form-builder/core" rel="noopener noreferrer"&gt;@react-form-builder/core&lt;/a&gt;&lt;br&gt;
📦 &lt;a href="https://www.npmjs.com/package/@react-form-builder/components-rsuite" rel="noopener noreferrer"&gt;@react-form-builder/components-rsuite&lt;/a&gt;&lt;br&gt;
📦 &lt;a href="https://www.npmjs.com/package/@react-form-builder/viewer-bundle" rel="noopener noreferrer"&gt;@react-form-builder/viewer-bundle&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Highlights of FormEngine Core 4.0:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;UI-Agnostic: Integrate effortlessly with UI libraries like MUI, Ant Design, shadcn/ui, and more.&lt;/li&gt;
&lt;li&gt;Framework-Friendly: Built-in support for Next.js, Remix, and framework-agnostic CDN deployment.&lt;/li&gt;
&lt;li&gt;Multi-Database Compatibility: Seamlessly use MySQL, PostgreSQL, MongoDB, SQLite, and more.&lt;/li&gt;
&lt;li&gt;Robust Validation: Powered by Zod, with extensible support for Yup, AJV, Superstruct, and Joi.&lt;/li&gt;
&lt;li&gt;Dynamic &amp;amp; Responsive: Create reactive, adaptive layouts with MobX-powered dynamic properties.&lt;/li&gt;
&lt;li&gt;Flexible Storage: Store forms as JSON or generate them programmatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎬 Watch the video to see FormEngine in action—start building your forms faster, smarter, and with greater ease than ever before!&lt;/p&gt;

&lt;p&gt;In this step-by-step tutorial, I'll walk you through building a simple React demo app using FormEngine. You'll see how quickly you can design, render, and manage forms — without writing endless custom code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @react-form-builder/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;viewWithCss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-form-builder/components-rsuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buildForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormViewer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-form-builder/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simpleForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;errorType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsErrorMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsContainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;builder&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firstName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeholder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter your first name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;First Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lastName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeholder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter your last name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;birthDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsDatePicker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Birth Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oneTap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1900-01-07T12:25:37.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RsButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;children&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appearance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onClick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commonAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;failOnError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;customAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onSubmit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormViewer&lt;/span&gt;
    &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;viewWithCss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;getForm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;simpleForm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
      &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// submit the form to the backend&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form data: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you'll learn:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;How to install and set up the FormEngine Core Library&lt;/li&gt;
&lt;li&gt;Creating and editing forms using JSON&lt;/li&gt;
&lt;li&gt;Running your app and previewing form behavior&lt;/li&gt;
&lt;li&gt;Using the FormEngine Visual Designer to create forms&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🌍 Help us grow the ecosystem, if you like the project:
&lt;/h2&gt;

&lt;p&gt;⭐ Star us on &lt;a href="https://github.com/optimajet/formengine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💬 Share feedback! Have feature requests or thoughts on the docs? Share them in the comments below!&lt;/p&gt;

&lt;p&gt;🔗 Mention &lt;a class="mentioned-user" href="https://dev.to/optimajet"&gt;@optimajet&lt;/a&gt; Formengine &lt;a href="https://x.com/" rel="noopener noreferrer"&gt;X / Twitter&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 This is just the beginning — part two is on the way!
&lt;/h2&gt;

&lt;p&gt;We'll provide a detailed comparison between React Hook Form and FormEngine Core.&lt;/p&gt;

&lt;p&gt;📘 Docs, Live Demo &amp;amp; Examples: &lt;a href="https://formengine.io/documentation" rel="noopener noreferrer"&gt;formengine.io/documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’d love to address your specific questions, so please leave your questions in the comments, and we'll include the answers in our upcoming comparison!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
