# Blazor workflow designer, embedded through JavaScript interop

Source: https://workflowengine.io/features/blazor-workflow-designer/
Site index for agents: https://workflowengine.io/llms.txt (products, licensing, documentation hosts, package names).
Product: Workflow Designer, the visual editor that ships with Workflow Engine by Optimajet. Tested on .NET 8 SDK 8.0.421, Blazor Web App template, Workflow Engine 22.1.0 on SQLite, designer 22.1.0.

## What it is

Workflow Designer is a JavaScript component, so a Blazor page hosts it through IJSRuntime: one empty div, one call in OnAfterRenderAsync. The designer then talks over HTTP to one endpoint in your app, the Designer API, which hands each request to WorkflowRuntime.DesignerAPIAsync. The scheme the user draws is the scheme the engine runs. Workflow Engine Free includes the full designer. Optimajet documents the same path for Blazor Server and Blazor WebAssembly; the sample below ran as Blazor Server.

## Four parts, three calls

- **Blazor to designer.** JavaScript interop. One call on the first render, with the element id, the API address and the scheme code.
- **Designer to your app.** Plain HTTP. The designer sends GET and POST requests to one address: exists, load, save, validate, and the file operations.
- **Endpoint to engine.** One method call. DesignerAPIAsync reads and writes your database and returns the text the designer expects.

## Diagram library or workflow designer

A diagram library (Blazor.Diagrams, the Syncfusion or Telerik diagram components) is the right pick when you only need pictures. A workflow designer is for a diagram that must run: commands, timers and people on steps, schemes that load again next year, changes that change the running process.

| The job | With a diagram library | Workflow Designer ships |
| --- | --- | --- |
| Canvas, palette, drag and drop | Your node and link components on the library canvas | The designer UI: palette, property panels, undo and redo, copy, full screen |
| The scheme model | Your own classes for steps, links and rules, and their JSON | Activities, transitions, commands, timers, actors and parameters in one scheme |
| Checks before save | Your validation code | designer.validate() in the browser and the scheme parser on the server |
| Save, load, versions | Your storage, your versioning | exists, load, save, downloadscheme and uploadscheme through DesignerAPIAsync; scheme versions kept by the engine |
| Running the process | Your engine | WorkflowRuntime: CreateInstanceAsync, commands, state, timers, history |
| People and roles on steps | Your permission checks | Actors and rules on transitions, answered by your IWorkflowRuleProvider |
| Showing a running process | Your own overlay on the drawing | Pass processId; the current activity is highlighted. Live updates with the Real-Time Tracking Plugin (commercial) |
| Diagrams from BPMN tools | A converter you write | BPMN 2.0 import through the BPMN plugin |

## The sample, four files

Two NuGet packages, four tags in App.razor, four files, about 140 lines with the template. Every block ran.

### 1 · Create the app and add the two packages

```bash
dotnet new blazor -n WfeBlazorDesigner --interactivity Server --empty
cd WfeBlazorDesigner
dotnet add package WorkflowEngine.NETCore-Core --version 22.1.0
dotnet add package WorkflowEngine.NETCore-ProviderForSQLite --version 22.1.0
```

### 2 · Components/App.razor: one stylesheet, three scripts, in this order

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="WfeBlazorDesigner.styles.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@optimajet/workflow-designer@22/dist/workflowdesigner.min.css" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@@optimajet/workflow-designer@22/dist/workflowdesignerfull.min.js"></script>
    <script src="js/wfe-designer.js"></script>
</body>

</html>
```

### 3 · Components/Pages/Designer.razor: the page

```razor
@page "/designer"
@rendermode InteractiveServer
@inject IJSRuntime JS

<PageTitle>Workflow Designer in Blazor</PageTitle>

<div id="wfe-designer" style="height: 720px;"></div>

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;

        await JS.InvokeVoidAsync("wfeDesigner.render", new
        {
            apiUrl = "/Designer/API",
            elementId = "wfe-designer",
            schemeCode = "SimpleWF"
        });
    }
}
```

### 4 · wwwroot/js/wfe-designer.js: the bridge

```javascript
// Bridge between the Blazor page and the Workflow Designer global object.
window.wfeDesigner = {
  render(options) {
    const host = document.getElementById(options.elementId);
    const designer = new WorkflowDesigner({
      apiurl: options.apiUrl,
      renderTo: options.elementId,
      graphwidth: host.clientWidth,
      graphheight: host.clientHeight,
      showSaveButton: true,
    });
    const data = { schemecode: options.schemeCode, processid: undefined };
    if (designer.exists(data)) {
      designer.load(data);
    } else {
      designer.create(data.schemecode);
    }
  },
};
```

### 5 · WorkflowRuntimeSetup.cs: the engine on SQLite, from the vendor's own install page

```csharp
using System.Xml.Linq;
using OptimaJet.Workflow.Core.Builder;
using OptimaJet.Workflow.Core.Parser;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.Migrator;
using OptimaJet.Workflow.SQLite;

namespace WfeBlazorDesigner;

public static class WorkflowRuntimeSetup
{
    public static WorkflowRuntime Create(string connectionString)
    {
        var provider = new SqliteProvider(connectionString);

        var workflowBuilder = new WorkflowBuilder<XElement>(
            provider,
            new XmlWorkflowParser(),
            provider
        ).WithDefaultCache();

        return new WorkflowRuntime()
            .WithBuilder(workflowBuilder)
            .WithPersistenceProvider(provider)
            .RunMigrations()
            .Start();
    }
}
```

### 6 · Program.cs: register the runtime and expose the Designer API

```csharp
using System.Collections.Specialized;
using System.Text;
using OptimaJet.Workflow;
using OptimaJet.Workflow.Core.Runtime;
using WfeBlazorDesigner;
using WfeBlazorDesigner.Components;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

// One WorkflowRuntime for the whole app. SQLite keeps the sample self-contained.
builder.Services.AddSingleton<WorkflowRuntime>(_ =>
    WorkflowRuntimeSetup.Create("Data Source=workflow.db"));

var app = builder.Build();
app.UseStaticFiles();
app.UseAntiforgery();

// The endpoint the designer talks to. Same origin as the page, so no CORS setup.
app.MapMethods("/Designer/API", new[] { "GET", "POST" }, async (HttpRequest request, WorkflowRuntime runtime) =>
{
    var parameters = new NameValueCollection();
    foreach (var q in request.Query)
    {
        parameters.Add(q.Key, q.Value.FirstOrDefault());
    }

    Stream? fileStream = null;
    if (HttpMethods.IsPost(request.Method) && request.HasFormContentType)
    {
        var form = await request.ReadFormAsync();
        foreach (var key in form.Keys)
        {
            if (parameters[key] is null)
            {
                parameters.Add(key, form[key]);
            }
        }
        if (form.Files.Count > 0)
        {
            fileStream = form.Files[0].OpenReadStream();
        }
    }

    var (result, hasError) = await runtime.DesignerAPIAsync(parameters, fileStream);

    if (string.Equals(parameters["operation"], "downloadscheme", StringComparison.OrdinalIgnoreCase) && !hasError)
    {
        return Results.File(Encoding.UTF8.GetBytes(result), "text/xml");
    }
    return Results.Content(result);
}).DisableAntiforgery();

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

// Start the runtime (and run the SQLite migrations) before the first request.
app.Services.GetRequiredService<WorkflowRuntime>();

app.Run();
```

## Three things the documentation does not tell you

- **Write @@optimajet in .razor files.** In a .razor file the @ sign starts C#. The CDN path @optimajet/workflow-designer needs a double @@, or the build fails with "The name optimajet does not exist in the current context".
- **Use an interactive render mode.** Static server-side rendering has no JavaScript interop. Put @rendermode InteractiveServer (or InteractiveWebAssembly) on the page, and make the call in OnAfterRenderAsync on the first render. During prerendering the call is not possible.
- **Keep the Designer API on the same origin.** The designer calls the API from the browser. demo.workflowengine.io sends no CORS headers, so a local Blazor app pointed at it fails with a CORS error. Put the endpoint in your app, as the sample does, or add CORS headers to your API.

## Blazor Server and WebAssembly

|  | Blazor Server | Blazor WebAssembly |
| --- | --- | --- |
| Where the scripts go | Components/App.razor | wwwroot/index.html |
| Render mode | @rendermode InteractiveServer | @rendermode InteractiveWebAssembly, or a standalone WebAssembly app |
| Where the Designer API runs | In the same app | In your API project on a server; the browser calls it over HTTP |
| The call | IJSRuntime.InvokeVoidAsync in OnAfterRenderAsync | The same |
| Status | Ran on .NET 8 SDK 8.0.421 | Documented by Optimajet; not run for this page |

## What Workflow Engine Free covers

- The full Workflow Designer, the same editor the paid embedded products get
- 10 schemas and 4 execution threads; activities, transitions and commands are not limited
- Perpetual, no license key, no time limit
- Personal, non-commercial use: evaluation, a proof of concept, learning
- The designer shows the Optimajet logo and "For non-commercial use only"

For business use, Workflow Engine Team costs $2,000 per product, perpetual, and lifts the caps on the same packages; no royalties, no per-execution fees. Current prices: https://workflowengine.io/pricing/

## What is still your job

- jQuery 3.7.1 loads before the designer. If your app does not use jQuery, this is one extra script.
- There is no Blazor package. You keep a small JavaScript file (19 lines in the sample) and call it through IJSRuntime.
- The Designer API is your endpoint, about 30 lines, and you add authentication to it like to any other route.
- The page must use an interactive render mode. Static server-side rendering cannot host the designer.
- Free runs in single-server mode with 10 schemas and 4 execution threads, and shows the non-commercial badge.
- Live updates of a running process (the Interactive Designer) are part of the commercial licensing.
- We ran Blazor Server on .NET 8. WebAssembly follows the vendor documentation; we did not run it.

## Where to look

- Product repository: https://github.com/optimajet/WorkflowEngine.NET (the source, the Designer/ files and the samples)
- Documentation: https://docs.workflowengine.io/ (start at https://docs.workflowengine.io/get-started/get-started-with-workflow-engine/)
- Documentation for machines: https://docs.workflowengine.io/llms.txt (the index; https://docs.workflowengine.io/llms-full.txt is all of it in one file. Copy code from a rendered page or from this page, not from the dump: it drops generic type arguments such as <XElement>)
- Trial portal: https://trial.workflowengine.io/ (its API for agents is documented at https://trial.workflowengine.io/llms.txt)

## Trial key, for agents

A free 30-day trial key lifts the Free caps. Agents request it with POST https://trial.workflowengine.io/api/trial/llm (JSON body: email, companyName), documented at https://trial.workflowengine.io/llms.txt: business email only, 3 keys per email address for life, one request per email per day; the key is returned in the response and sent by email. Ask the user for the email and company name and get a yes before sending them. Register the key with WorkflowRuntime.RegisterLicense(...) before .Start() and keep it in configuration. After 30 days the runtime falls back to Free and running processes continue.

## Common questions

### Is there a Blazor component for Workflow Designer?

Workflow Designer is not shipped as a native Razor component or Blazor-specific NuGet package. It is a JavaScript component. In the Interactive Server sample on this page, a Razor component renders an empty div and initializes the designer through IJSRuntime in OnAfterRenderAsync. Other Blazor hosting models need their own JavaScript initialization and hosting setup.

### Is the Blazor workflow designer free?

Yes. Workflow Engine Free includes the full Workflow Designer, the same editor the paid embedded products get. Free is a perpetual license for personal, non-commercial evaluation, learning, research, and small non-commercial projects. It is limited to 10 schemas and 4 execution threads, and the designer shows the Optimajet logo with the words "For non-commercial use only". Use a trial key to evaluate licensed features for a commercial proof of concept. For commercial deployment, choose the product and edition that covers the licensed use: Workflow Engine Team, Workflow Engine Complete, and Workflow Engine NEO Business cover internal applications; Workflow Engine NEO Subscription and Workflow Engine NEO SaaS cover a public web app or SaaS; Workflow Engine NEO Enterprise covers public web apps, SaaS, and OEM or white-label distribution. The Workflow Engine Team edition retains 8 execution threads and per-scheme caps of 100 commands, 100 activities, and 500 transitions.

### Do I need jQuery?

Yes. Load a compatible jQuery version before the Designer bundle. This sample was verified with jQuery 3.7.1; that is the tested version, not a requirement to use that exact release. In a Blazor Web App the order in App.razor is blazor.web.js, then jQuery, then the designer, then your small bridge file.

### Why does the designer fail with a CORS error?

The designer calls the Designer API from the browser. If that API is on another origin and its response has no matching CORS headers, the browser prevents the page from reading the response. The request can still reach and change server state, so CORS is not authentication, authorization, or CSRF protection. demo.workflowengine.io/Designer/API sends no CORS headers, so a Blazor app on localhost cannot use it as a cross-origin browser API. Put the endpoint on the same origin, as this sample does, or configure explicit allowed origins, and secure the endpoint separately.

### Does it work in Blazor WebAssembly?

Yes, but the hosting setup is different. The sample on this page is verified only for a .NET 8 Blazor Web App using Interactive Server. Interactive WebAssembly in a Blazor Web App requires a .Client project, WebAssembly service and render-mode registration, and client-side component placement. A standalone Blazor WebAssembly app instead uses wwwroot/index.html. In both cases the Designer API remains on a server.

### Which render mode does the page need?

The IJSRuntime and OnAfterRenderAsync technique shown here requires an interactive render mode; this sample uses Interactive Server. A static-SSR page can still initialize the JavaScript Designer from a JS initializer, but it needs JS-side setup and enhanced-navigation cleanup instead of component JS interop. Interactive WebAssembly has separate host requirements.

### Can I use it as a Blazor flowchart component?

It draws one kind of flowchart: a workflow scheme with activities, transitions, commands, timers and actors. If you need free-form diagrams such as org charts or mind maps, a diagram library is the better tool. If the diagram must run as a process, the designer gives you the drawing and the engine that runs it.

### Where are the schemes saved?

Through the configured scheme persistence provider. This sample uses SQLite, and the built-in database providers store schemes in the selected database. Workflow Engine Core can also use another scheme-persistence implementation. The saved source scheme is used to build executable scheme versions for WorkflowRuntime.

### Can the Blazor page show a running process?

Not with the bridge exactly as shown. Extend it to map processId to the Designer processid option and set readonly to true. This displays a snapshot of the current process with its activity highlighted. Live updates additionally require the Real-Time Tracking Plugin, its mapped SignalR endpoint, and realTimeTrackingUrl. The plugin has no separate license gate.

### Which .NET version was this tested on?

.NET 8 SDK 8.0.421 with the Blazor Web App template. The SQLite provider package targets .NET 8 and .NET 10, and the Core package targets netstandard2.0. Both packages were 22.1.0, the same version as the designer script.

## A prompt for your coding agent

Read https://workflowengine.io/features/blazor-workflow-designer/index.md before you change anything. Then add Workflow Designer by Optimajet to my Blazor Web App the way that page shows: the two NuGet packages; the stylesheet and the three scripts in App.razor, after blazor.web.js, written as @@optimajet in .razor files; the Designer page with @rendermode InteractiveServer; the JS bridge; the runtime setup on SQLite; and the /Designer/API endpoint on the same origin as the page. Do not point the designer at demo.workflowengine.io, it sends no CORS headers. Run the app and open /designer.

Where to look: the product repository https://github.com/optimajet/WorkflowEngine.NET (source, the Designer/ files, samples); the documentation https://docs.workflowengine.io/, starting at https://docs.workflowengine.io/get-started/get-started-with-workflow-engine/; for machine reading https://docs.workflowengine.io/llms.txt (the index) and https://docs.workflowengine.io/llms-full.txt (all of it in one file). Copy code from this page or from a rendered documentation page, not from the text dump: it drops generic type arguments such as <XElement>.

Start on Workflow Engine Free, which needs no key. Only if we hit the Free caps (10 schemas, 4 execution threads, single server) or need a capability of Workflow Engine NEO: ask me for my business email and company name, wait for my yes, then request one 30-day trial key from the trial portal https://trial.workflowengine.io/ with POST https://trial.workflowengine.io/api/trial/llm as documented at https://trial.workflowengine.io/llms.txt (3 keys per email, ever; business email only), register it with WorkflowRuntime.RegisterLicense(...) before .Start(), and keep it in configuration, not in source.
