Skip to Content
Evaluate Get Started

Workflow as Code

Key takeaways

Workflow as Code lets you create workflow schemes in C# using the fluent ProcessDefinitionBuilder API with compile-time checking and full IDE support. The builder produces a ProcessDefinition object through chainable factory methods. Build steps provide a pipeline of transformations that run every time a scheme is loaded into memory, regardless of whether the scheme came from the visual Designer, XML, or ProcessDefinitionBuilder.

The Programmatic Scheme Builder is a fluent C# API - ProcessDefinitionBuilder - that lets you construct workflow schemes in code instead of using the visual Designer. Available since Workflow Engine 13.3.0, it is useful when you need to generate schemes dynamically, enforce naming conventions programmatically, or build workflows in environments without a browser-based Designer.

ProcessDefinitionBuilder produces a ProcessDefinition object through chainable factory methods - CreateActivity, CreateTransition, CreateCommand, CreateTimer, CreateActor, and others. After building, extract the result from builder.ProcessDefinition and register it with the runtime.

Workflow Engine also provides a build step pipeline - a chain of transformations that run every time a scheme is loaded into memory, regardless of how the scheme was authored. Whether the scheme came from the visual Designer, an XML file, or ProcessDefinitionBuilder, the same build steps apply: they can add activities, enforce naming conventions, inject transitions, or reject invalid schemes before they are cached. Build steps are available from Workflow Engine 4.0 and later.

What it is

Think of ProcessDefinitionBuilder as a code-based alternative to drawing a scheme in the Designer. Instead of dragging activities onto a canvas and connecting them with transitions, you call methods that declare the same structure: "Create this activity, mark it as initial, connect it to that activity with a transition triggered by that command."

The builder has three static factory methods:

  • ProcessDefinitionBuilder.Create(name) - creates a new builder for a fresh scheme.
  • ProcessDefinitionBuilder.WithProcessDefinition(pd) - wraps an existing ProcessDefinition for reading.
  • ProcessDefinitionBuilder.WithMutableProcessDefinition(pd) - wraps an existing ProcessDefinition for modification.

Each Create* method returns a sub-builder with chainable configuration methods for that element. You set properties, attach actions, add conditions, and capture references for later use - all without touching XML.

For developers: Use CreateActivity(name) with .Initial() or .Final() to mark start and end states. Capture references with .Ref(out ActivityDefinition var) and .Ref(out CommandDefinition var), then pass them to CreateTransition(name, from, to) and .TriggeredByCommand(cmd). Extract the final definition with builder.ProcessDefinition and register it via runtime.Builder.SaveProcessSchemeAsync(schemeCode, pd).

To add a custom build step, extend BuildStep and override Name and ExecuteAsync. Register it with workflowBuilder.AddBuildStep(order, position, step) before passing the builder to WorkflowRuntime. The step receives the ProcessDefinition, mutates a clone, and returns BuildStepResult.Success(modified) or BuildStepResult.Fail(error). The same pipeline handles built-in features like scheme inlining and activity timeouts.

using OptimaJet.Workflow.Core.Model;using OptimaJet.Workflow.Core.Model.Builder;using OptimaJet.Workflow.Core.Runtime;var builder = ProcessDefinitionBuilder.Create("ApprovalScheme");// Create activities and capture references for transitionsbuilder.CreateActivity("Draft").Initial()    .Ref(out ActivityDefinition draft);builder.CreateActivity("ManagerApproval")    .Ref(out ActivityDefinition managerApproval);builder.CreateActivity("DirectorApproval")    .Ref(out ActivityDefinition directorApproval);builder.CreateActivity("Approved").Final()    .Ref(out ActivityDefinition approved);builder.CreateActivity("Rejected").Final()    .Ref(out ActivityDefinition rejected);// Create commands and capture referencesbuilder.CreateCommand("Submit")    .Ref(out CommandDefinition submitCmd);builder.CreateCommand("Approve")    .Ref(out CommandDefinition approveCmd);builder.CreateCommand("Reject")    .Ref(out CommandDefinition rejectCmd);// Create transitions with activity and command referencesbuilder.CreateTransition("toManagerApproval", draft, managerApproval)    .TriggeredByCommand(submitCmd);builder.CreateTransition("toDirectorApproval", managerApproval, directorApproval)    .TriggeredByCommand(approveCmd);builder.CreateTransition("toApproved", directorApproval, approved)    .TriggeredByCommand(approveCmd);builder.CreateTransition("toRejected", managerApproval, rejected)    .TriggeredByCommand(rejectCmd);// Build and register the definitionvar definition = builder.ProcessDefinition;var (success, errors, failedStep) = await runtime.Builder.SaveProcessSchemeAsync("ApprovalScheme", definition);

What the builder supports

The builder covers the same elements as the Designer:

  • Activities - initial, intermediate, and final, with state names, position on the canvas, and auto-scheme-update flags.
  • Transitions - direct and reverse classifiers, command triggers, timer triggers, auto triggers, and conditional expressions.
  • Commands - named triggers that appear in the UI for users to execute.
  • Timers - interval, date, and expression-based timers for scheduled transitions.
  • Actors and rules - define who can execute which command through actor definitions and restriction rules.
  • Actions - attach code actions to activities and transitions with parameters.
  • Conditions - code conditions and expression-based conditions for branching.
  • Localization - per-language state names via CreateOrUpdateLocalizationForState.
  • Annotations - key-value metadata on activities and transitions.
  • Inline schemes - embed another scheme as a sub-process via CreateInlineActivity.

Registering with the runtime

After building, call runtime.Builder.SaveProcessSchemeAsync(schemeCode, definition) to persist the scheme to the database. The runtime can then create process instances from it:

var definition = builder.ProcessDefinition;var (success, errors, failedStep) =    await runtime.Builder.SaveProcessSchemeAsync("ApprovalScheme", definition);if (success){    await runtime.CreateInstanceAsync("ApprovalScheme", Guid.NewGuid());}

Why it matters

Workflow as Code delivers these outcomes:

  • Compile-time safety - The builder catches missing activities, dangling transitions, and invalid configurations at build time, not when the runtime tries to execute a malformed scheme.

  • Dynamic scheme generation - Approval chains, escalation paths, and branching rules can be read from a database or API response and turned into a valid scheme on the fly, without storing XML templates per variation.

  • Version-controlled schemes - Scheme definitions live in source code alongside the rest of your application, subject to the same code review, testing, and CI/CD pipeline as any other source file.

  • No Designer dependency - Schemes can be created in headless environments - CI/CD pipelines, automated tests, or server-side code - where no browser-based Designer is available.

  • Enforce conventions across all schemes - Register a build step that validates naming rules, injects audit activities, or rejects non-compliant schemes at load time. The same rules apply to every scheme, whether it was designed visually, authored in XML, or generated with ProcessDefinitionBuilder.

Who it is for

Evaluator (CEO, CTO, PM): The Programmatic Scheme Builder lets developers generate and maintain workflow definitions in code, reducing the operational cost of managing per-tenant scheme variations and making scheme changes part of the standard deployment pipeline.

Developer: Call ProcessDefinitionBuilder.Create(name) and chain CreateActivity, CreateTransition, CreateCommand, and CreateTimer to define the full scheme. Capture references with .Ref(out ...) to pass ActivityDefinition and CommandDefinition objects between builder methods. Extract the result with builder.ProcessDefinition and register it via runtime.Builder.SaveProcessSchemeAsync(schemeCode, pd).

Enterprise architect: The builder enables scheme-as-code patterns where workflow definitions are generated, validated, and deployed through the same CI/CD pipeline as application code. Existing schemes can be modified through WithMutableProcessDefinition without rebuilding from scratch.

When to use it

Use the Programmatic Scheme Builder when the scheme structure must be determined at application startup based on configuration, user input, or external data. A typical scenario is migrating an existing business rules engine into Workflow Engine: the rules are already defined in a database table or configuration file (who approves what, under which conditions, with which deadlines), and the builder reads those rules and constructs a matching scheme - without manually translating each rule into an XML scheme.

Use build steps when the same structural transformation must apply to every scheme in your deployment, regardless of how it was authored - Designer, XML, or programmatic. Typical examples:

  • Mandatory compliance activities - Every scheme must include an audit trail activity. A build step injects it at load time, so no one can forget to add it - whether they designed the scheme in the visual Designer or generated it in code.
  • Naming convention enforcement - A build step validates that initial activities follow a naming standard like Start* and rejects non-compliant schemes with a clear error before they are cached.
  • Cross-cutting transitions - Every scheme needs a timeout transition that escalates stalled tasks. A build step reads the timeout configuration from the activity properties and generates the required timer and transition automatically.

Do not use the builder when the scheme is authored by a business analyst or end user - those teams work in the visual Designer. The builder is a developer tool for scenarios where the scheme is generated from code.

How it compares

The table below compares using the Programmatic Scheme Builder with the alternative of authoring schemes in the visual Designer.

Comparison of scheme authoring approaches
ApproachWhat it requiresResult
Visual DesignerA browser-based UI, manual drag-and-drop, exporting XML per schemeBest for human-authored workflows; requires manual effort per scheme variation
Workflow Engine Programmatic Scheme BuilderC# code using ProcessDefinitionBuilder with chainable factory methodsSchemes generated, validated, and deployed as code - repeatable, testable, CI/CD-friendly

Both approaches produce the same ProcessDefinition format and support the same runtime features. The choice depends on who authors the scheme and whether the scheme structure is fixed or dynamic.

Frequently asked questions

When should I use the Programmatic Scheme Builder instead of the visual Designer?

Use the builder when schemes are generated from code - dynamic approval chains, conventions enforced at build time, or rules migrated from an existing system. Use the Designer when a human authors the scheme.

Does the builder support all Designer features?

Yes. ProcessDefinitionBuilder covers activities, transitions, commands, timers, actors, conditions, actions, localization, annotations, and inline subprocesses. Each element has a dedicated Create* method and chainable configuration.

How do I register a builder-created scheme with the runtime?

Extract the ProcessDefinition with builder.ProcessDefinition and pass it to runtime.Builder.SaveProcessSchemeAsync(schemeCode, definition). The method returns validation errors. The scheme is persisted to the same database tables as Designer-authored schemes.

What are build steps?

Build steps are transformations that run every time a scheme is loaded into memory, regardless of how the scheme was created - visual Designer, XML file, or ProcessDefinitionBuilder. They can add activities, inject transitions, enforce naming conventions, or reject invalid schemes. The same pipeline powers built-in features like scheme inlining and activity timeouts. Custom build steps are registered through IWorkflowBuilder.AddBuildStep() and apply uniformly to every scheme in your deployment.

What API changes came with the Programmatic Scheme Builder?

The builder was introduced in Workflow Engine 13.3.0. Earlier versions required XML authoring through the Designer API or manual XML construction.

See also