# Angular workflow designer, one module and one endpoint
Source: https://workflowengine.io/features/angular-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, through its Angular wrapper @optimajet/workflow-designer-angular. Ran on 2026-10-07: wrapper 22.1.0, Angular 22.2.1 (zoneless, standalone), Angular CLI 22.2.1, Node 24.21.0, Workflow Engine 22.1.0 on SQLite, with ng serve and with the production build of ng build.
## What it is
Workflow Designer is a JavaScript editor for workflow schemes, and the Angular package wraps it in one component, declared in an NgModule. A standalone component imports WorkflowDesignerStrictModule from @optimajet/workflow-designer-angular/strict and renders ; the wrapper's stylesheet goes into styles in angular.json. The component talks over HTTP to one endpoint in your backend, the Designer API, which hands each request to WorkflowRuntime.DesignerAPIAsync. The scheme the user builds is the scheme the engine runs. Workflow Engine Free includes the full designer and the wrapper. Version 22.1.0 needs Angular 22 (peer @angular/core ^22.0.0) and ships TypeScript declarations.
## Four parts, three calls
- **App to component.** Two inputs: designerConfig with apiurl and renderTo, and schemeCode or processId. The component draws once the view is ready, and you reach its methods through viewChild.
- **Component to your API.** Plain HTTP to one address. On first load, eight calls: exists and load as GET, then the engine info, custom activities and plugin names as POST. Save, validate and the file operations follow.
- **API to engine.** One method call. DesignerAPIAsync reads and writes your database and returns the text the designer expects.
## Foblex Flow or a workflow designer
Foblex Flow (MIT, @foblex/flow 19.3.0, Angular 17.3 and later) and ngDiagram by Synergy Codes (Apache 2.0, ng-diagram 1.4.0, Angular 18 and later) are Angular-native libraries for node editors: they render the canvas, and your application decides which nodes exist, which connections are valid, and how the workflow is stored or executed. Sequential Workflow Designer has an Angular package for step-by-step flows (MIT). They are the right pick when the diagram is the product. A workflow designer is for a diagram that must run as a business process: commands, timers and people on steps, schemes that load again next year, and a runtime that executes them.
| The job | With Foblex Flow or ngDiagram | Workflow Designer ships |
| --- | --- | --- |
| Canvas, palette, drag and drop | Foblex Flow or ngDiagram renders it; you build the palette and the panels as Angular components | The designer UI: palette, property panels, undo and redo, copy, full screen |
| The scheme model | Your node and connection types and their JSON | Activities, transitions, commands, timers, actors and parameters in one scheme |
| Checks before save | Connection rules plus your validation code | getDesignerErrors() in the app and the scheme parser on the server |
| Save, load, versions | Your state layer and your storage | save(), exists, load, downloadScheme and upload through DesignerAPIAsync; scheme versions kept by the engine |
| Running the process | Your engine, or one you bring | 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 | processId opens it read-only with the current activity highlighted; live updates with the Real-Time Tracking Plugin |
| Diagrams from BPMN tools | A converter you write | BPMN import through the BPMN Plugin on the server, with a license; import only |
## The sample
An Angular 22 app from ng new with one standalone component around the designer, loaded with @defer, and a Designer API in ASP.NET Core with CORS for the app. Every block ran.
### 1 · src/app/designer.ts: the module, the settings, the cleanup
```typescript
import { Component, OnDestroy, viewChild } from '@angular/core';
import {
WorkflowDesignerStrictComponent,
WorkflowDesignerStrictModule,
} from '@optimajet/workflow-designer-angular/strict';
// The wrapper's component lives in an NgModule, so a standalone component
// imports the module. The /strict entry leaves the stylesheet to angular.json,
// which is what lets `ng build` bundle the designer's fonts.
@Component({
selector: 'app-designer',
imports: [WorkflowDesignerStrictModule],
template: `
`,
})
export class Designer implements OnDestroy {
private readonly wrapper = viewChild(WorkflowDesignerStrictComponent);
schemeCode = 'SimpleWF';
// This object replaces the wrapper's defaults, so it names the div and the
// upload form itself. Without renderTo, nothing draws.
designerConfig = {
renderTo: 'wfe-designer',
uploadFormId: 'wfe-upload-form',
uploadFileId: 'wfe-upload-file',
apiurl: 'http://localhost:5199/Designer/API',
widthDiff: 0,
heightDiff: 0,
};
// The wrapper does not destroy the designer when Angular removes it.
ngOnDestroy() {
this.wrapper()?.innerDesigner?.destroy();
}
}
```
### 2 · src/app/app.ts: the designer behind @defer
```typescript
import { Component } from '@angular/core';
import { Designer } from './designer';
// @defer moves the designer, about 3.8 MB of script, into its own chunk, so
// the first load stays under the default 1 MB budget of `ng build`. @defer
// splits only standalone components, hence the Designer component.
@Component({
selector: 'app-root',
imports: [Designer],
template: `
@defer {
}
`,
})
export class App {}
```
### 3 · angular.json: the one line added, in build options
```json
"styles": [
"node_modules/@optimajet/workflow-designer-angular/workflowdesigner.min.css",
"src/styles.css"
]
```
### 4 · Program.cs: the Designer API, with CORS for the app's origin
```csharp
using System.Collections.Specialized;
using System.Text;
using OptimaJet.Workflow;
using OptimaJet.Workflow.Core.Runtime;
using WfeDesignerApi;
var builder = WebApplication.CreateBuilder(args);
// One WorkflowRuntime for the whole app. SQLite keeps the sample self-contained.
builder.Services.AddSingleton(_ =>
WorkflowRuntimeSetup.Create("Data Source=workflow.db"));
// The designer runs in the browser on another origin (a static page, Vite,
// ng serve), so the API must say so. Name the origins you serve; never
// AllowAnyOrigin outside development.
builder.Services.AddCors(options => options.AddPolicy("designer", policy =>
policy.WithOrigins("http://localhost:3001", "http://localhost:5173", "http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()));
var app = builder.Build();
app.UseCors("designer");
// The endpoint the designer talks to: query and form parameters go to
// WorkflowRuntime.DesignerAPIAsync, an uploaded file becomes a stream.
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);
}).RequireCors("designer");
// Start the runtime (and run the SQLite migrations) before the first request.
app.Services.GetRequiredService();
app.Run();
```
### 5 · Run it
```bash
# 1. The Designer API on :5199 (Workflow Engine 22.1.0 on SQLite)
cd samples/designer-api
dotnet run --urls http://localhost:5199
# 2. The Angular app on :4200 (Angular 22.2.1, Node 24)
cd samples/angular-workflow-designer
npm install
npx ng serve
# 3. The production build: the designer in its own chunk
npx ng build
```
## Inputs
| Input | What it does |
| --- | --- |
| designerConfig | The settings object of the plain designer: apiurl, renderTo, uploadFormId, uploadFileId, widthDiff, heightDiff, tenantId and any other designer setting by name. It replaces the wrapper's defaults; a change to one of its keys draws the designer again |
| schemeCode | The scheme to open, by its code. Read when the designer first draws |
| processId | A process to show instead of a scheme. It opens read-only |
The 22.1.0 class also declares designerFolder, which its code does not use. The component has no outputs.
## Methods, through viewChild
| Method | What it does |
| --- | --- |
| save(successCallback, errorCallback) | Validates in the browser, then saves through your API. The error callback gets the validation errors; a server failure reaches neither callback |
| getDesignerErrors() | Returns the scheme errors the designer found |
| clearScheme() | Clears the canvas, the same as starting an empty scheme |
| downloadScheme() | Downloads the scheme as an XML file |
| upload('scheme' | 'bpmn', callback) | Opens a file dialog and loads a scheme file into the canvas; save() keeps it. 'bpmn' needs the BPMN Plugin with a license, and we did not run it |
| isSchemeExist() | True when the scheme in schemeCode exists |
| isProcessExist() | True when the process in processId exists |
| refresh() | Reloads the data in the designer |
| innerDesigner | The plain designer object, for every method the wrapper does not expose, destroy() among them |
## Four things the package README does not tell you
- **Name the div yourself.** designerConfig replaces the wrapper's defaults instead of merging with them, and the wrapper names its div after renderTo. The README's example object has no renderTo, so nothing draws: no request, no error. Set renderTo, uploadFormId and uploadFileId.
- **Use the strict entry for ng build.** The regular entry imports the stylesheet from JavaScript. ng serve draws the designer; ng build stops on six .woff2 fonts, and with a font loader it ships the designer unstyled. WorkflowDesignerStrictModule plus the stylesheet in angular.json builds and draws.
- **Keep 3.8 MB behind @defer.** In the main bundle the designer fails the default 1 MB budget of ng build. @defer splits only standalone components, and the wrapper's component sits in an NgModule, so wrap it in a standalone one and defer that. The chunk is about 797 kB compressed.
- **Your API must allow the app's origin.** The designer calls the Designer API from the browser. With the app on :4200 and the API on :5199, the API must send CORS headers for :4200, or the browser blocks every call. Name the origins in the policy; AllowAnyOrigin is for development only.
## What Workflow Engine Free covers
- The full Workflow Designer, the same editor the paid embedded products get, and its Angular wrapper
- 10 schemas and 4 execution threads; activities, transitions and commands are not limited
- Perpetual, no license key, no time limit
- Personal use and internal commercial use, with attribution
- The designer shows the Optimajet logo
A public web app or SaaS needs Workflow Engine NEO Subscription or Workflow Engine NEO SaaS, and OEM distribution needs Workflow Engine NEO Enterprise. Workflow Engine Team costs $2,000, one-time and perpetual, for internal use: unlimited schemas, 8 execution threads, and 100 commands, 100 activities and 500 transitions per scheme. Current prices: https://workflowengine.io/pricing/
## What is still your job
- The Designer API is .NET: it runs on a server whatever the front end is. The route is 35 lines of C# with its comment, plus the CORS policy with your origins and the authentication you add the way you add it to any other route.
- Each wrapper release names one Angular major. 22.1.0 needs Angular 22; no release lists Angular 21.
- The canvas is sized from the window minus widthDiff and heightDiff. A layout with a header or a sidebar passes their sizes as the diffs; CSS on the host element does not size the canvas.
- Changing schemeCode or processId after the first draw does nothing. Create the component again to open another scheme, and never call save() before that: it writes the drawn diagram under the new code.
- save() reports only the checks it runs in the browser. A failed save on the server reaches neither callback; the designer writes it to the console.
- The wrapper never destroys the designer. Call innerDesigner.destroy() in your component's ngOnDestroy, or each mount leaves resize, focus and visibility listeners behind.
- It needs the browser: the wrapper sets window.$ and window.moment when its module loads. We did not run it with server-side rendering.
- Live updates of a running process (the Interactive Designer) need the Real-Time Tracking Plugin and its SignalR hub, and work on Workflow Engine Free too. The jQuery headers do not reach that connection.
- The vendor's sample repository is on Angular 22 in the NgModule style, and its apiurl points at demo.workflowengine.io, which sends no CORS headers. Read it for the localization setup; start a new app from ng new.
## 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 )
- 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.
## Common questions
### Is there a free Angular workflow designer?
Yes. Workflow Engine Free includes the full Workflow Designer, and its Angular wrapper is on npm. Free is a perpetual license for personal use and internal commercial use, with attribution. It is limited to 10 schemas and 4 execution threads, and the designer shows the Optimajet logo. Use a trial key to evaluate the licensed features. For a public web app, SaaS, OEM, or internal use beyond those limits, pick the edition by 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.
### How do I build a workflow editor in Angular?
You can draw one on a canvas library, or embed one that is already built. Workflow Designer is the second kind. Run npm install @optimajet/workflow-designer-angular and import WorkflowDesignerStrictModule from its /strict entry into a standalone component. Render with a schemeCode and a designerConfig that holds apiurl and renderTo, then add the wrapper's stylesheet to styles in angular.json. One endpoint in your ASP.NET Core backend, the Designer API, answers the designer's requests, and Workflow Engine runs the schemes it saves.
### Which Angular versions does the wrapper support?
Version 22.1.0 asks for Angular 22: its peer range is @angular/core ^22.0.0. Each release names one Angular major: 17.x Angular 19, 18.x to 21.x Angular 20, 22.x Angular 22. No release lists Angular 21, so an Angular 21 app moves to 22 or overrides the peer check, which we did not test. Wrapper 22.0.0 came out on 16 July 2026, six weeks after Angular 22.0.0. We ran the sample on Angular 22.2.1, zoneless, with standalone components.
### Why does ng build fail, or show the designer without styles?
The regular entry of the package imports the designer's stylesheet from JavaScript. The Angular application builder then stops on the six .woff2 fonts that stylesheet references. With a file loader for .woff2 the build passes, but the extracted stylesheet is never linked, so the designer renders without styles. Import WorkflowDesignerStrictModule from @optimajet/workflow-designer-angular/strict instead, and list node_modules/@optimajet/workflow-designer-angular/workflowdesigner.min.css in styles in angular.json. ng serve works with either entry, so test with ng build.
### Why does the designer stay empty?
Two causes come up. If no request reaches your API and the console is silent, renderTo is missing: designerConfig replaces the wrapper's defaults, and the wrapper names its div after designerConfig.renderTo, so the README's example object draws nothing. If the requests go out and the console shows a CORS error, your API does not allow the app's origin. The sample allows http://localhost:4200; demo.workflowengine.io sends no CORS headers. CORS is not authentication, so secure each operation, tenant and scheme on the server.
### How do I send an auth token with the designer's requests?
The designer sends its requests with jQuery, which the wrapper puts on window, so Angular HttpClient interceptors never see them. Call window.jQuery.ajaxSetup({ headers: { Authorization: 'Bearer ...' } }) before the designer loads: in our run the header reached every call, after a CORS preflight that the API must allow. The headers do not reach the separate SignalR connection that live process updates use. Check the token on the server for every operation.
### How do I open another scheme on the same page?
Create the component again: put it under an @if, a route or a track key that changes with the scheme code. The wrapper reads schemeCode and processId only when the designer first draws. In our run, a new schemeCode sent no request and the canvas kept the old scheme, and a save() after that wrote the old diagram under the new code.
### Can the page show a running process?
Yes. Pass a processId instead of a schemeCode, and the wrapper opens a snapshot of that process read-only, with its current activity highlighted. Live updates require the Real-Time Tracking Plugin and its SignalR setup; the plugin has no separate license gate. We did not run a process view: the sample's API creates no process instances.
### Foblex Flow or Workflow Designer?
Foblex Flow is an MIT-licensed Angular library for node editors. It renders the canvas; your application decides which nodes exist, which connections are valid, and how the workflow is stored or executed. ngDiagram by Synergy Codes (Apache 2.0) takes the same approach. Workflow Designer edits native Workflow Engine schemes that Workflow Runtime executes on .NET. Choose by the runtime contract you need.
### Elsa or Workflow Designer for an Angular app?
Elsa 3.8 embeds Elsa Studio in an Angular app through its custom elements, from the @elsa-workflows/elsa-studio-wasm package, such as elsa-workflow-definition-editor; its documentation describes no first-class Angular host and ships a wrapper for React only. Workflow Designer has an Angular wrapper on npm and talks to one endpoint in your own ASP.NET Core app. The engines differ in more than the designer, and the comparison of Elsa and Workflow Engine covers the rest.
### Does it work with AngularJS?
The wrapper is for Angular 22, not for AngularJS 1.x. An AngularJS app can use the plain JavaScript designer, created with new WorkflowDesigner({...}) from a script tag or an npm import, the way any page does. We did not run it inside AngularJS.
## A prompt for your coding agent
Read https://workflowengine.io/features/angular-workflow-designer/index.md before you change anything. Then add Workflow Designer by Optimajet to my Angular app the way that page shows: npm install @optimajet/workflow-designer-angular; in a standalone component, import WorkflowDesignerStrictModule from '@optimajet/workflow-designer-angular/strict' and render ; put renderTo, uploadFormId and uploadFileId in designerConfig, because the object replaces the wrapper's defaults; add node_modules/@optimajet/workflow-designer-angular/workflowdesigner.min.css to styles in angular.json; load that component inside @defer; call innerDesigner.destroy() in its ngOnDestroy. Point apiurl at a Designer API I run: the page prints the ASP.NET Core endpoint with CORS; put my app's origin in its policy, never AllowAnyOrigin outside development, and never point the designer at demo.workflowengine.io, it sends no CORS headers. Check that ng build passes, then run ng serve and open the page.
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 .
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.
## Sitemap
[Sitemap of workflowengine.io](https://workflowengine.io/sitemap.md): every page of the site, with the description each page carries.