Skip to main content

Introducing Formengine - The New Formbuilder, try for FREE formengine.io.

Forms Plugin

The Forms Plugin integration adds ready-to-use Forms endpoints to the Workflow Engine Web API and attaches a FormsPlugin instance to every Workflow Runtime created by the Web API. For an ASP.NET application, this removes the need to implement a Forms controller for the standard form retrieval, save, validation, and execution scenarios.

For an overview of forms, the viewer and manager components, and workflow configuration, see the Forms documentation.

Use operation callbacks to customize these four operations. The Forms sample demonstrates template authorization and server-side fallback without changing the library defaults.

Integration​

Start with the application from the Web API Setup guide, then make the following additions.

Install the Forms Plugin Web API package​

dotnet add package OptimaJet.Workflow.Api.Forms

The package contains both the Forms Plugin runtime integration and the Web API operations.

Register the Forms Plugin​

Add the Forms Plugin namespace to Program.cs:

using OptimaJet.Workflow.Api.Forms;

Register the plugin after the Workflow Engine Web API core and runtime services. The relevant part of the Program.cs example from Web API Setup becomes:

using OptimaJet.Workflow.Api;
using OptimaJet.Workflow.Api.Options;
using OptimaJet.Workflow.Api.Sqlite;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.Api.Forms;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddWorkflowApiSqlite();
builder.Services.AddWorkflowApiCore(SetupWorkflowApiCore);
builder.Services.AddWorkflowRuntime(SetupWorkflowRuntime);
builder.Services.AddFormsPlugin(SetupFormsPlugin);

var app = builder.Build();

app.UseRouting();
app.MapWorkflowApi();

app.Run();

void SetupWorkflowApiCore(WorkflowApiCoreOptions options)
{
options.LicenseKey = "PUT YOUR LICENSE KEY HERE";
}

void SetupWorkflowRuntime(WorkflowTenantCreationOptions options)
{
options.ConnectionString = "Data Source=workflow_engine.db";
options.WorkflowRuntimeCreationOptions.ConfigureWorkflowRuntime = runtime =>
{
runtime.AsSingleServer();
};
}

void SetupFormsPlugin(FormsPluginCreationOptions options)
{
options.FormsManagerUrl = "https://your-application.example.com/forms-manager";
}

MapWorkflowApi() maps the Forms endpoints together with the other Workflow Engine Web API endpoints. No additional controller registration or endpoint mapping is required.

The same registration works in multi-tenant mode. The Web API runtime configurator attaches an independently configured Forms Plugin instance to every tenant's WorkflowRuntime, including tenants registered dynamically.

Forms Plugin Options​

Configure the integration through FormsPluginCreationOptions, passed to AddFormsPlugin.

NameTypeDefaultDescription
FormsManagerUrlstring""URL of the Forms Manager page opened from Workflow Designer. Set it to the Forms Manager route in your application when users edit forms from the process scheme designer.
FormsProcessParameterNamestring"FormsPlugin_ProcessForms"Name of the process parameter that stores the Forms Plugin metadata describing which forms are associated with workflow activities and commands.
FormVersionPropertyNamestring"__formVersion"Name of the property in form data used for optimistic concurrency checks when form data is saved or executed.
DefaultFormDefinitionstring"{}"Form definition returned by the plugin when an empty/default form definition is needed.
FormValidatorIFormValidator?nullServer-side validator used by save and execute operations. When null, the Forms Plugin creates its default validator.

To add custom server-side validation, implement IFormValidator and assign it during registration:

void SetupFormsPlugin(FormsPluginCreationOptions options)
{
options.FormsManagerUrl = "https://your-application.example.com/forms-manager";
options.FormValidator = new MyFormValidator();
}

See Server-Side Form Validation for the validator contract and an implementation example.

Important Behavior​

Endpoint paths​

All Forms operations use POST and are mapped under the Web API base path. With the default WorkflowApiCoreOptions.BasePath, the endpoints start with /workflow-api/forms/. If you change BasePath, the Forms paths change with the rest of the Web API.

Form data pipelines and property names​

Form data is sent as a JSON object. Form Engine binds values and validation errors to components by their keys, including letter case. The Forms API preserves names and values when no form data pipeline steps are registered.

Implement IFormsApiFormDataPipelineStep to transform dynamic form data at the HTTP boundary. Register each implementation with AddFormDataPipelineStep<TStep>(order); it is created as a singleton and can use constructor injection like any other service. The Forms API owns and registers the internal pipeline that composes the steps:

public sealed class AuditingFormsApiFormDataPipelineStep(
ILogger<AuditingFormsApiFormDataPipelineStep> logger) : IFormsApiFormDataPipelineStep
{
public Dictionary<string, object?> ConvertToRuntime(
IReadOnlyDictionary<string, object?> formData)
{
logger.LogDebug("Converting form data for the Forms runtime.");
return formData;
}

public Dictionary<string, object?> ConvertToClient(
IReadOnlyDictionary<string, object?> formData)
{
logger.LogDebug("Converting form data for the HTTP client.");
return formData;
}
}

builder.Services.AddFormDataPipelineStep<AuditingFormsApiFormDataPipelineStep>(order: 100);

The Forms API applies the pipeline in these places:

  • Inbound: form data in save-form and execute-form, before it reaches the Forms runtime.
  • Outbound: process form data, executable form data, saved form data, and save/execute validation error objects.

Multiple implementations are composed by their registration order value. Inbound conversion runs in ascending order, using DI registration order for equal values. Outbound conversion runs in the exact reverse order, which lets each implementation undo its corresponding inbound transformation. Consequently, equal-order implementations run in reverse registration order on the way back to the client.

Each step accepts a string-keyed dictionary and returns a dictionary. Values can still contain nested dictionaries, arrays, scalar values, and null. A step can inject IOptions<FormsPluginCreationOptions> when its conversion depends on settings such as FormVersionPropertyName. The standard package includes CamelCaseFormsApiFormDataPipeline for recursive lowerCamelCase/PascalCase conversion; register it explicitly when the client contract needs that behavior.

Validation​

save-form and execute-form call the configured IFormValidator. Validation failures are returned as the standard Error envelope with 400 Bad Request, code ValidationError, and an errors dictionary. A successful save returns the normalized form data; a successful execute returns whether the workflow command was executed.

Runtime errors​

Forms runtime failures are returned as typed Error responses. The API does not throw a Forms-specific exception for these expected failures, so the response body remains available independently of the application's exception handler configuration.

StatusError codeWhen it is returned
400 Bad RequestBadRequestThe submitted form data is invalid, a form version check fails, or another request value cannot be accepted.
400 Bad RequestValidationErrorThe configured form validator rejected one or more form values.
404 Not FoundNotFoundA requested form, command, or process is missing or is not accessible to the current tenant.
500 Internal Server ErrorSystemErrorForms Plugin configuration or stored runtime state is inconsistent, or command execution fails internally.

The JSON response contains code and message properties. Validation errors additionally contain an errors dictionary whose keys match form component names. Unexpected exceptions outside the typed Forms runtime result continue through the application's standard ASP.NET exception handling.

Multitenancy​

The Forms API uses the same tenant resolution as the rest of the Workflow Engine Web API. In multi-tenant mode, send the tenant in the Workflow-Api-Tenant-ID header. See Multitenancy for tenant registration and request resolution.

  • get-form resolves a tenant-specific form first and can fall back to a shared form when the Forms Plugin supports a shared definition for the requested key.
  • get-executable-forms, save-form, and execute-form validate that the process belongs to the current tenant. A process from another tenant is exposed in the same way as a missing process and returns 404 Not Found.

Do not use a tenant ID from a request body or query string as an authorization decision. Let the Web API resolve the tenant from its configured request context and, when security is enabled, from tenant permissions.

Security​

When security services are enabled, the Forms operations participate in the standard Workflow Engine Web API authorization model. The common permission branch is workflow-api.forms; individual operation IDs are listed in the API reference below.

For example, the following permission configuration allows only the Forms API and all tenants:

var claim = permissions.BuildClaim(builder => builder
.DenyAllOperations()
.Allow("workflow-api.forms")
.AllowAllTenants());

API Reference​

AddFormsPlugin(this IServiceCollection services, Action<FormsPluginCreationOptions>? setupAction)​

Adds the Forms Plugin runtime integration and Forms API operations to the Workflow Engine Web API.

Parameters

NameTypeDescription
servicesIServiceCollectionThe ASP.NET service collection.
setupActionAction<FormsPluginCreationOptions>?A delegate that configures Forms Plugin creation options. Pass null to use all default values.

Returns

TypeDescription
IServiceCollectionThe service collection, which can be used for further configuration.

IFormsApiFormDataPipelineStep​

Defines one application-provided form data transformation in the OptimaJet.Workflow.Api.Forms namespace.

MemberDescription
Dictionary<string, object?> ConvertToRuntime(IReadOnlyDictionary<string, object?> formData)Converts client data before save-form or execute-form passes it to the Forms runtime.
Dictionary<string, object?> ConvertToClient(IReadOnlyDictionary<string, object?> formData)Converts runtime data before the Forms API returns form data or validation errors to the client.

Register zero or more implementations with AddFormDataPipelineStep<TStep>(order). With no registrations, the Forms API leaves form data unchanged.

Forms operations​

The paths below are relative to WorkflowApiCoreOptions.BasePath.

Operation IDMethodPathDescription
workflow-api.forms.get-formPOSTforms/get-formRetrieves a standalone form definition by name and optional version.
workflow-api.forms.get-executable-formsPOSTforms/get-executable-formsRetrieves the forms and commands currently available for a process.
workflow-api.forms.save-formPOSTforms/save-formValidates and saves form data without executing a command.
workflow-api.forms.execute-formPOSTforms/execute-formValidates form data and executes the selected workflow command.

The Forms HTTP API exposes these four operations. The former get-process-form endpoint and its generated HTTP-client method have been removed. FormsRuntimeApi.GetProcessFormAsync(GetProcessFormParameters) remains available in the server-side FormsPlugin SDK; applications choose DataParameterName in trusted server code.

All operations can return the typed Error responses described in Runtime errors. Form validation failures from save-form and execute-form use the same envelope with Code = "ValidationError" and field messages in Errors.

get-form​

Request

NameTypeRequiredDescription
FormKeyFormKeyYesName and optional version of the form to return.

Response

NameTypeDescription
FormFormThe resolved standalone form.

get-executable-forms​

Request

NameTypeRequiredDefaultDescription
ProcessIdGuidYes—ID of the process.
IdentityIdstring?NonullIdentity used to determine available commands.
CommandNameFilterstring?NonullCommand name used to restrict the returned forms and commands.
ConditionCheckboolNofalseWhether command conditions are evaluated when resolving executable forms.
Culturestring?Nonull.NET culture name used for localized command names.

Response

NameTypeDescription
FormsList<ExecutableForm>Forms and commands available for the process.

save-form​

Request

NameTypeRequiredDefaultDescription
ProcessIdGuidYes—ID of the process.
FormKeyFormKeyYes—Name and optional version of the form.
FormDataDictionary<string, object?>Yes—JSON object containing the form values.
IdentityIdstring?NonullIdentity used to check access to the form and commands.
CommandNameFilterstring?NonullCommand name used to restrict access checks.
ConditionCheckboolNofalseWhether command conditions are evaluated before the form is saved.

Response

NameTypeDescription
DataDictionary<string, object?>The saved and normalized form data.

execute-form​

Request

NameTypeRequiredDefaultDescription
ProcessIdGuidYes—ID of the process.
FormKeyFormKeyYes—Name and optional version of the form.
CommandNamestringYes—Workflow command to execute.
FormDataDictionary<string, object?>Yes—JSON object containing the form values.
IdentityIdstring?NonullIdentity executing the command.
ImpersonatedIdentityIdstring?NonullIdentity on whose behalf the command is executed, if required.

Response

NameTypeDescription
WasExecutedboolWhether the workflow command was executed.

Common response models​

ModelPropertiesDescription
FormKeyFormName: string, FormVersion: int?Identifies a form and, optionally, a specific version.
FormFormKey: FormKey, FormCode: stringA standalone form definition.
ProcessFormProcessId: Guid, FormKey: FormKey, FormCode: string, FormData: Dictionary<string, object?>A form definition with data bound to a process.
ExecutableFormProcessId: Guid, FormKey: FormKey, FormCode: string, FormData: Dictionary<string, object?>, AllowSave: bool, Commands: List<FormCommand>A process form together with its available actions.
FormCommandCommandName: string, LocalizedName: stringA workflow command displayed for an executable form.
ErrorCode: string, Message: string, Errors: Dictionary<string, string[]>?The standard API error envelope. Errors is populated for validation failures.

The generated OpenAPI document uses this single Error schema for every Forms 400 response, including validation.

For a complete MSSQL weekly-report application using these endpoints directly from React, see the FormsPlugin Web API sample.