# Approval Plugin Overview ApprovalPlugin connects [WorkflowRuntime](https://docs.workflowengine.io/get-started/concepts/workflow-runtime/) to approval persistence and provides three approval views: **Approval history** shows the timeline of a process, **Inbox** shows work waiting for each user, and **Outbox** shows work a user has already completed. Use it when the application must show who can approve a document now, what is waiting for a user, how a document was approved, or which documents a user has already handled. ApprovalPlugin supplies data and workflow integration. The host application renders its own screens, sends notifications when needed, and calls the [WorkflowRuntime command APIs](https://docs.workflowengine.io/get-started/concepts/command/) when a user selects a command. ## See how approval views change Consider a document scheme with three states: `Request`, `Manager signed`, and `Boss signed`. The first command is available to the manager or boss, and the second is available only to the boss. ApprovalPlugin changes its application views as the document advances. ![Approval process from Request through Manager signed to Boss signed](https://docs.workflowengine.io/images/plugins/approval-plugin/overview/approval-flow.png) | Process stage | Approval history | Inbox | Outbox | |---------------|------------------|-------|--------| | `Request` | Two predicted stages; the first allows Manager and Boss, and the second allows Boss | Manager and Boss can execute the first `Approve` | Empty | | `Manager signed` | Completed manager stage and pending boss stage | Boss can execute `Approve` | Manager has one process summary | | `Boss signed` | Completed manager and boss stages | Empty when no command remains | Manager and boss each have a process summary | After a user executes `Approve`, Approval history records the completed stage, Outbox includes the process in that user's completed work, and Inbox is recalculated for the next stage. ## Register ApprovalPlugin Register ApprovalPlugin after the base runtime configuration and before `StartAsync()`. Set registration-sensitive members such as `AutoApprovalHistory` before calling `WithPlugin`, because `OnPluginAdd` decides whether to install the automatic build step. ```csharp filename="ApprovalPluginRegistration.cs" using OptimaJet.Workflow.Core.Runtime; using OptimaJet.Workflow.Plugins; public static class ApprovalPluginRegistration { public static WorkflowRuntime AddApprovalPlugin(this WorkflowRuntime runtime) { var plugin = new ApprovalPlugin { AutoApprovalHistory = true, DisableApprovalHistory = false, DisableInbox = false, InboxCheckConditions = false, UseLocalizedNamesInApprovalHistory = true }; return runtime.WithPlugin( plugin, ["DocumentApproval"] ); } } ``` The optional scheme list scopes ApprovalPlugin actions and Designer formats because `WithPlugin` passes the list to both providers. The plugin also uses the list to restrict its runtime handler and automatic history build step. Omit the list or pass an empty list to register those capabilities globally. Before testing the result, verify the workflow scheme supplies the data ApprovalPlugin needs. When automatic history is enabled, its build step adds `FillApprovalHistory` to activities with a non-blank `State` in the schemes covered by the plugin. An inbox row requires an outgoing command transition with [actor restrictions](https://docs.workflowengine.io/get-started/concepts/rule/) that resolve the identity, and at least one resolved transition for that identity must have the [`Direct` classifier](https://docs.workflowengine.io/get-started/concepts/transition/) unless `InboxActorsWithOnlyRejectCommands` is enabled. > **Known limitation - custom activity scope:** `WithPlugin` adds generated custom activity types without a scheme filter, so they remain > available in every scheme even when the registration call supplies a scheme list. Use the registration list, rather than activity > visibility in the Designer, to determine which schemes the plugin handles. ## Understand plugin capabilities ApprovalPlugin is included in `WorkflowEngine.NETCore-Core` under the `OptimaJet.Workflow.Plugins` namespace. The plugin registers four asynchronous [actions](https://docs.workflowengine.io/get-started/concepts/action/), Designer parameter formats, a runtime process handler, and an optional build step. Its base provider also creates Designer custom activity types for every action that is not excluded when `WithPlugin` runs. With the default `AutoApprovalHistory` value, these are `GetApprovalHistory`, `GetInbox`, and `GetOutbox`; setting the field to `false` before registration also adds `FillApprovalHistory`. The plugin registers no synchronous actions, conditions, rules, or custom condition forms. Table: ApprovalPlugin capabilities | Capability | Role | |-----------|------| | `FillApprovalHistory` action | Writes a pending or completed item that the provider inserts or applies to a pending match | | `GetApprovalHistory` action | Reads history by process or identity and writes the list to a process parameter | | `GetInbox` action | Reads inbox entries by process or identity and writes the list to a process parameter | | `GetOutbox` action | Reads process summaries for one identity and writes the list to a process parameter | | Custom activity types | Provide action wrappers on the Activities tab of the Designer Elements panel | | Runtime process handler | Deletes stale pending history, runs pre-execution, and rebuilds inbox rows before the root process becomes idled or finalized | | `AutoApprovalHistoryBuilderStep` | Adds `FillApprovalHistory` to state activities when automatic history is enabled | ApprovalPlugin uses [IApprovalProvider](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/) through `WorkflowRuntime.PersistenceProvider`. `IPersistenceProvider` extends `IApprovalProvider`, so every supported persistence provider supplies this contract. Actor rules that represent roles or teams must resolve to workflow identity IDs. ApprovalPlugin persists inbox work for each resolved identity instead of storing one shared role or team task. ## Configure ApprovalPlugin ApprovalPlugin configuration members control history generation, inbox selection, names, and comments. Configure the plugin instance before registration because `OnPluginAdd` captures the automatic build-step decision; most other members are read when their handler or action runs. Table: ApprovalPlugin configuration members | Member | Kind and type | Default | When read and effect | |--------|---------------|---------|----------------------| | `AutoApprovalHistory` | Field, `bool` | `true` | At registration, excludes the `FillApprovalHistory` custom activity type and installs the build step when history is enabled; its current value also hides the action from ordinary Designer action lists | | `DisableApprovalHistory` | Property, `bool` | `false` | At registration, prevents build-step installation; on each runtime-handler call, skips pending-row cleanup and history pre-execution; it does not block a `FillApprovalHistory` action already in a scheme | | `DisableInbox` | Property, `bool` | `false` | On each runtime-handler call, skips inbox deletion and rebuilding; existing inbox records remain | | `InboxCheckConditions` | Field, `bool` | `false` | During each inbox refresh, evaluates transition conditions before resolving actors and commands | | `InboxActorsWithOnlyRejectCommands` | Field, `bool` | `false` | During each inbox refresh, keeps an empty-command row for an identity that has no `Direct` transition | | `AllowedActorsFilter` | Property, `Func` | `null` | During each inbox refresh, filters `Allow` restrictions; `Restrict` restrictions are unaffected | | `GetUserNamesByIds` | Field, `Func, List>` | `null` | During history pre-execution, maps distinct predicted identity IDs to values stored in `AllowedTo` | | `NameParameterForComment` | Field, `string` | `"Comment"` | During completed history execution, names the process parameter copied to `Commentary` | | `UseLocalizedNamesInApprovalHistory` | Property, `bool` | `true` | When `FillApprovalHistory` writes a record, resolves localized state and command names with technical-name fallbacks | Set `AutoApprovalHistory` and `DisableApprovalHistory` before `WithPlugin`. Changing either member later does not remove a build step that was already registered or remove `FillApprovalHistory` from a scheme that was already built. A later `AutoApprovalHistory` change affects ordinary action discovery, but it does not regenerate the custom activity types already added to the runtime. When `GetUserNamesByIds` is configured, ApprovalPlugin stores the list returned by that delegate. When it is `null`, ApprovalPlugin calls `WorkflowRuntime.GetUserByIdentityAsync` for each distinct identity if that runtime delegate is configured. If both delegates are `null`, the plugin stores the identity IDs unchanged in `AllowedTo`. ### Apply automatic history changes Use this deployment sequence when automatic history changes for schemes that already have process instances: 1. Create new runtime instances with the required settings applied before `WithPlugin`, and deploy the same configuration to every runtime node. Changing a field on a runtime that already registered the build step does not unregister that step. 2. After the new runtimes start, call `SetSchemeIsObsoleteAsync(schemeCode, tenantId)` for each affected scheme. The next scheme creation or update rebuilds the generated process scheme with the current build-step pipeline. 3. Existing process instances remain bound to their old generated scheme until `UpdateSchemeIfObsoleteAsync(processId)` migrates them. The current activity must allow automatic scheme updates unless the overload with `ignoreAutoSchemeUpdate: true` is used. Rebuilding changes future behavior only. It does not backfill approval stages that were never recorded or delete existing `WorkflowApprovalHistory` rows. A `FillApprovalHistory` action saved explicitly in the source scheme also remains after automatic injection is disabled. ### What Disabled controls `ApprovalPlugin.Disabled` affects action and Designer-format discovery. When it is `true` during registration, no generated custom activity types are added; setting it later hides existing types from Designer discovery but does not remove them from the runtime. `Disabled` does not prevent `WithPlugin` from registering the runtime process handler or automatic build step, so it is not a master switch for approval history and inbox maintenance. ## Use ApprovalPlugin actions ApprovalPlugin actions run inside workflow schemes. The three query actions read the persistence provider and save their result to the process parameter named by `ParameterName`; they do not return the list to application code. Table: ApprovalPlugin actions | Action | Parameters | Stored result | |--------|------------|---------------| | `FillApprovalHistory` | None | Adds or updates `WorkflowApprovalHistory` directly | | `GetApprovalHistory` | Query selector, output parameter, optional paging | `List` | | `GetInbox` | Query selector, output parameter, optional paging | `List` | | `GetOutbox` | Identity, output parameter, optional paging | `List` | `GetApprovalHistory` and `GetInbox` use the same parameter structure. Table: GetApprovalHistory and GetInbox parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `GetParameterType` | `GetParameterType` | Yes | - | Selects `ProcessId` or `IdentityId` | | `GetParameterValue` | `string` | Yes | - | Supplies the process GUID or identity according to the selector | | `ParameterPurpose` | `ParameterPurpose` | Yes in Designer | `Persistence` | Controls how Workflow Engine stores the output parameter | | `ParameterName` | `string` | Yes | - | Names the process parameter that receives the result list | | `PageIndex` | `int` | No | `-1` | Uses a 1-based page index; values below 1 disable paging | | `PageSize` | `int` | No | `-1` | Enables paging only when both page values are greater than zero | When an action omits `PageIndex` or `PageSize`, the handler checks inherited `PluginSettings` for a value with the same name before using `-1`. `GetOutbox` accepts one identity instead of a `ProcessId` or `IdentityId` selector. Table: GetOutbox parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `IdentityId` in Designer | `string` | Yes | - | Selects the identity whose history is grouped into outbox summaries | | `ParameterPurpose` | `ParameterPurpose` | Yes in Designer | `Persistence` | Controls how Workflow Engine stores the output parameter | | `ParameterName` | `string` | Yes in Designer | `""` in the handler | Names the process parameter that receives the result list | | `PageIndex` | `int` | No | `-1` | Uses a 1-based page index; values below 1 disable paging | | `PageSize` | `int` | No | `-1` | Enables paging only when both page values are greater than zero | ## Understand runtime integration ApprovalPlugin combines a build-time action injection with runtime activity handling. Automatic history adds `FillApprovalHistory` to the pre-execution and execution implementations of eligible activities that have a non-blank state. The runtime handler then removes obsolete pending rows, starts pre-execution from the current activity, and rebuilds inbox entries each time root process execution is about to become idled or finalized. The pre-execution pass used while approval history is enabled loads persisted parameters, resolves actor restrictions through the rule provider, evaluates transition conditions unless `ResultOnPreExecution` supplies a fixed result, and runs actions from `PreExecutionImplementation`. Keeping approval history enabled can therefore call custom rules, conditions, and pre-execution actions before a user executes the next command. Temporary values from the preceding request are not reloaded, so persist data required by those callbacks or handle its absence. Custom callbacks must be safe to run during simulation and more than once. Check `processInstance.IsPreExecution` before sending notifications, writing to an external database, or calling an API that changes external state. For a condition with unsafe or expensive evaluation, set `ResultOnPreExecution` to the result that simulation should use. The simulated activity path is not committed, but external effects produced by custom code are not rolled back. ```mermaid flowchart TB subgraph prediction[Build and predict] direction LR A[Build process scheme] --> B[Inject FillApprovalHistory] B --> C[Create pending history] end subgraph completion[Execute and refresh] direction LR D[Execute command, timer, or SetState with execution] D --> E[Update or insert completed history] E --> F[Refresh pending history and inbox] end prediction --> completion ``` `SetStateAsync` with `SetStateParams.PreventExecution = true` changes the current activity without running its implementation or invoking this runtime handler. Approval history and inbox rows are therefore not refreshed by that operation and can continue to describe the previous activity until later workflow execution invokes the handler. The runtime handler skips its refresh cycle when both history and inbox are disabled. It also skips refreshes for a subprocess instance; actor resolution for a root process can still inspect that process and its descendants. `FillApprovalHistory` itself has no subprocess guard, so a build-injected or explicitly configured action can still write history while a subprocess executes. ## Understand persistence requirements ApprovalPlugin persists its primary records in two provider-managed tables or MongoDB collections. `WorkflowInbox` contains current per-identity work, and `WorkflowApprovalHistory` contains pending and completed stages. Outbox queries group completed history by identity and process, so no separate outbox store exists. The plugin also persists `WorkflowPluginApprovalHistoryOrder` as an internal process parameter to assign the next `Sort` value. Query actions write their results to the output parameter requested by the scheme, but the inbox and history themselves are not serialized into process parameters. ### Delete a process and approval data The persistence provider removes ApprovalPlugin records during normal process deletion. Both [DeleteProcessAsync(Guid)][api-delete-process] and [DeleteProcessAsync(Guid[])][api-delete-processes] delete the matching `WorkflowInbox` and `WorkflowApprovalHistory` records as part of process deletion. Export approval history before deleting the process when the application must retain an audit record after the process is removed. [api-delete-process]: https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IPersistenceProvider/#method-deleteprocessasync-guid [api-delete-processes]: https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IPersistenceProvider/#method-deleteprocessasync-guid-array The [approval history DELETE operations](https://docs.workflowengine.io/plugins/approval-plugin/approval-history/#read-history-through-web-api) and [inbox DELETE operations](https://docs.workflowengine.io/plugins/approval-plugin/inbox-outbox/#build-worklists-through-web-api) in the [Workflow Engine HTTP API](https://docs.workflowengine.io/workflow-engine-http-api/) have a narrower scope. They delete selected projection records without deleting the process or running ApprovalPlugin. A later inbox refresh can recreate deleted inbox records, while deleted completed history is not backfilled automatically. ### Use provider mutation methods [IApprovalProvider](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/) is the persistence contract through which ApprovalPlugin and application code read and modify Inbox and Approval history data and read derived Outbox summaries. It exposes mutation methods because ApprovalPlugin and administrative application code share this contract. ApprovalPlugin owns the normal inbox replacement and pending-history lifecycle; direct calls bypass that lifecycle and should be reserved for controlled repair, cleanup, or import operations. | Method | ApprovalPlugin use | |--------|--------------------| | [DropWorkflowInboxAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-dropworkflowinboxasync-guid) | Removes the current process inbox before the plugin inserts a refreshed snapshot | | [InsertInboxAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-insertinboxasync-list-inboxitem) | Inserts the identities and available commands calculated during inbox refresh | | [FillApprovalHistoryAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-fillapprovalhistoryasync-approvalhistoryitem) | Inserts a history item or updates a matching pending item | | [DropEmptyApprovalHistoryAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-dropemptyapprovalhistoryasync-guid) | Removes pending predictions before ApprovalPlugin runs pre-execution again | | [DropApprovalHistoryByProcessIdAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-dropapprovalhistorybyprocessidasync-guid) | Removes all approval history for one process | | [DropApprovalHistoryByIdentityIdAsync](https://api.workflowengine.io/core/OptimaJet.Workflow.Core.Persistence/IApprovalProvider/#method-dropapprovalhistorybyidentityidasync-string) | Removes every history row whose `IdentityId` matches; ApprovalPlugin normally assigns this value only to completed rows | Calling an inbox delete without the matching insert leaves the worklist empty until a later refresh. Calling a history delete changes both the approval timeline and any derived outbox summaries. The provider methods do not execute a workflow transition or re-evaluate actor rules on their own. ## See also ## Frequently asked questions **How do I apply an AutoApprovalHistory change to existing processes?** Register a fresh ApprovalPlugin instance with the new setting, mark each affected scheme obsolete with `SetSchemeIsObsoleteAsync`, and migrate running instances with `UpdateSchemeIfObsoleteAsync`. Existing history is neither backfilled nor deleted by that migration. **When must I set AutoApprovalHistory?** Set `AutoApprovalHistory` before registering ApprovalPlugin. `OnPluginAdd` reads the field to decide whether to add `AutoApprovalHistoryBuilderStep` to the runtime builder. **Why can ApprovalPlugin call my rules or conditions before the next command?** ApprovalPlugin calls pre-execution before a root process becomes idled or finalized whenever approval history is enabled. Pre-execution resolves actor rules and may evaluate transition conditions; use `ProcessInstance.IsPreExecution` and `ResultOnPreExecution` to keep custom code safe during that simulation. **Can I enable ApprovalPlugin for selected schemes?** Yes. Pass a `List` of scheme codes as the second argument to `runtime.WithPlugin`. The list scopes ApprovalPlugin actions, Designer formats, its runtime handler, and the automatic history build step. **Does ApprovalPlugin store inbox and history in process parameters?** No. The persistence provider stores inbox and history records. ApprovalPlugin uses one internal process parameter for history ordering, and its query actions can write result lists to parameters selected by the scheme.