# Workflow as Code The Programmatic Scheme Builder is a fluent C# API - `ProcessDefinitionBuilder` - that lets you construct workflow schemes in code instead of using the Workflow 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. It is part of the [embeddable .NET workflow engine library](https://workflowengine.io/products/dotnet-workflow-engine/). `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 Workflow Builder runs when it creates a new built Scheme version, regardless of how the Scheme was authored. Whether the Scheme came from the Workflow Designer, an XML file, or `ProcessDefinitionBuilder`, the same build steps can add activities, enforce naming conventions, inject transitions, or reject invalid Schemes before the built version is stored. Saving a Scheme also runs the pipeline on a copy for validation and dependency discovery; loading an existing stored or cached version does not run it again. 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. ```csharp 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 transitions builder.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 references builder.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 references builder.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 definition var 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: ```csharp 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 when Schemes are built** - Register a build step that validates naming rules, injects audit activities, or rejects non-compliant Schemes. The same rules apply when Workflow Builder creates a built version, whether the Scheme was designed visually, authored in XML, or generated with `ProcessDefinitionBuilder`. ## Who it is for **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 whenever Workflow Builder creates a built Scheme version, regardless of how the Scheme was authored - Designer, XML, or programmatic. Typical examples: - **Mandatory compliance activities** - Every Scheme must include an audit trail activity. A build step injects it when the built version is created, so no one can forget to add it - whether they designed the Scheme in the Workflow 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 Workflow 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 Workflow Designer. Table: Comparison of scheme authoring approaches | Approach | What it requires | Result | |---------------------------------------------|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| | Workflow Designer | A browser-based UI, manual drag-and-drop, exporting XML per scheme | Best for human-authored workflows; requires manual effort per scheme variation | | Workflow Engine Programmatic Scheme Builder | C# code using `ProcessDefinitionBuilder` with chainable factory methods | Schemes 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 Workflow 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 ordered transformations or validations that Workflow Builder runs when it creates a new built Scheme version, regardless of whether the Scheme came from Workflow Designer, an XML file, or `ProcessDefinitionBuilder`. Saving a Scheme runs the pipeline on a copy to validate it and discover Scheme Inlining dependencies, while loading an existing stored or cached version skips the pipeline. Build steps 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()`. **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 - Programmatic Scheme Builder Reference: Full API reference and advanced usage patterns. - Custom Build Steps: Implement and register custom build steps that transform a Scheme when its built version is created. - Scheme Versioning: How scheme updates coexist with running process instances. - [Workflow Designer](https://docs.workflowengine.io/evaluate/workflow-engine-features/workflow-designer/): The browser-based alternative for authoring schemes.