Skip to main content
AI/MLCrestApps

orchardcore-workflows

Skill for creating and managing Orchard Core workflows. Covers workflow type definitions, custom activities, events, workflow expressions, and workflow triggers. Use this skill when requests mention Orchard Core Workflows, Create a Workflow, Enabling Workflows, Built-in Event Activities, Built-in Task Activities, Creating a Custom Activity, or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.Workflows, OrchardCore.Workflows.Http, OrchardCore.Workflows.Timers, OrchardCore.Workflows.Activities, OrchardCore.Workflows.Models, TaskActivity, IStringLocalizer, IEnumerable, WorkflowExecutionContext, ActivityContext, EventActivity, IServiceCollection. It also helps with workflow examples, Built-in Task Activities, Creating a Custom Activity, Creating a Custom Event, plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.

Stars
13
Source
CrestApps/CrestApps.AgentSkills
Updated
2026-05-29
Slug
CrestApps--CrestApps.AgentSkills--orchardcore-workflows
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/CrestApps/CrestApps.AgentSkills/HEAD/plugins/orchardcore/skills/orchardcore-workflows/SKILL.md -o .claude/skills/orchardcore-workflows.md

Drops the SKILL.md into .claude/skills/orchardcore-workflows.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Orchard Core Workflows

Workflows are reusable visual definitions made of activities and outcome transitions. Create a workflow in Workflows → Workflows, add an event as a start activity, configure tasks/events, connect outcomes, and enable the definition. Export a tested workflow through the workflow deployment step and reuse its serialized payload in a WorkflowType recipe step rather than hand-creating activity IDs or transitions.

In Orchard Core 3.0, WorkflowIndex.WorkflowStatus is stored as an integer that matches the WorkflowStatus enum. Update raw SQL and custom index queries to compare the column with the enum's integer value. Existing values are migrated automatically.

Enable the required features

Enable the base feature and every feature that contributes an activity before importing a workflow that uses it.

{
  "steps": [
    {
      "name": "Feature",
      "enable": [
        "OrchardCore.Workflows",
        "OrchardCore.Workflows.Http",
        "OrchardCore.Workflows.Timers",
        "OrchardCore.Contents"
      ],
      "disable": []
    }
  ]
}

Define a workflow with a recipe

The WorkflowType step imports the serialized workflow definitions in data. Create the definition in the admin editor, then export it with AllWorkflowTypeDeploymentStep; retain that output as the recipe payload. This preserves activity properties, stable activity IDs, start flags, and transitions.

{
  "steps": [
    {
      "name": "WorkflowType",
      "data": [
        {
          "WorkflowTypeId": "[js: uuid()]",
          "Name": "Example workflow",
          "IsEnabled": true,
          "Activities": [],
          "Transitions": []
        }
      ]
    }
  ]
}

An event can start a workflow and can also block an existing instance. A task performs work and returns one or more outcomes. Only events can be configured as start activities.

Built-in activities in release/3.0

These are activity class names, not UI labels. Availability depends on the owning feature.

Feature area Event activities Task activities
Core workflows WorkflowFaultEvent NotifyTask, SetPropertyTask, SetOutputTask, CorrelateTask, ForkTask, JoinTask, ForLoopTask, ForEachTask, WhileLoopTask, IfElseTask, ScriptTask, LiquidTask, LogTask, CommitTransactionTask
HTTP workflows HttpRequestEvent, HttpRequestFilterEvent, SignalEvent HttpRedirectTask, HttpRequestTask, HttpResponseTask
Timers and user tasks TimerEvent, UserTaskEvent
Contents ContentCreatedEvent, ContentDeletedEvent, ContentPublishedEvent, ContentUnpublishedEvent, ContentUpdatedEvent, ContentDraftSavedEvent, ContentVersionedEvent CreateContentTask, RetrieveContentTask, UpdateContentTask, DeleteContentTask, PublishContentTask, UnpublishContentTask
Users UserCreatedEvent, UserDeletedEvent, UserEnabledEvent, UserDisabledEvent, UserUpdatedEvent, UserLoggedInEvent, UserConfirmedEvent AssignUserRoleTask, ValidateUserTask, RegisterUserTask
Optional integrations EmailTask, SmsTask, NotifyUserTask, NotifyContentOwnerTask, UpdateTwitterStatusTask, ValidateReCaptchaTask
Forms, roles, tenants ValidateAntiforgeryTokenTask, AddModelValidationErrorTask, ValidateFormTask, ValidateFormFieldTask, BindModelStateTask, HttpRedirectToFormLocationTask, UnassignUserRoleTask, GetUsersByRoleTask, DisableTenantTask, EnableTenantTask, CreateTenantTask, SetupTenantTask

ContentDraftSavedEvent is a built-in content event, and UserRegisteredEvent is not the release/3.0 activity name. Use UserCreatedEvent or RegisterUserTask as appropriate.

Expressions and workflow context

WorkflowExecutionContext exposes input, output, properties, correlation ID, the workflow instance, and the current last result. CorrelateTask, ForEachTask, ForLoopTask, IfElseTask, SetOutputTask, SetPropertyTask, and WhileLoopTask have a syntax selector using WorkflowScriptSyntax.JavaScript or WorkflowScriptSyntax.Liquid.

var item = input("ContentItem");
var count = property("Count");
setProperty("Count", count + 1);
output("Result", item);
setCorrelationId(item.ContentItemId);
{{ Workflow.Input.ContentItem.DisplayText }}
{{ Workflow.Properties.Count }}
{{ Workflow.CorrelationId }}
{{ 'Approved' | signal_url }}

Use the JavaScript APIs workflow(), workflowId(), input(name), output(name, value), property(name), setProperty(name, value), lastResult(), correlationId(), setCorrelationId(id), and signalUrl(signal) only in script-enabled activity fields. Use Liquid only in fields whose syntax is Liquid.

Correlation, blocking, and resuming

CorrelateTask evaluates its configured JavaScript or Liquid expression and sets WorkflowExecutionContext.CorrelationId. Use a stable domain identifier such as a content item ID when a later event must resume the same instance.

EventActivity.Execute halts the workflow. The workflow manager persists a halted instance when it reaches a blocking event, then TriggerEventAsync finds matching definitions/instances, checks the event's CanExecuteAsync, and resumes the activity with the supplied input. A workflow with no blocking activity completes in one execution.

Trigger a custom event with an event name matching the activity Name and use the same correlation ID when resuming a correlated instance:

await workflowManager.TriggerEventAsync(
    nameof(MyApprovalEvent),
    new Dictionary<string, object>
    {
        ["RequestId"] = requestId,
    },
    correlationId: requestId);

IWorkflowManager.TriggerEventAsync also has isExclusive to avoid starting a duplicate instance already halted on the matching start event, and isAlwaysCorrelated to resume based on event type regardless of correlation ID. Prefer normal correlation unless the event semantics require one of those exceptions.

Write a custom task activity

Derive from TaskActivity<TActivity>, provide localized display metadata, declare all possible outcomes, and return an outcome from ExecuteAsync. Add a display driver and its Razor shapes under Views/Items.

using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Models;

namespace MyModule.Workflows.Activities;

public sealed class MyApprovalTask : TaskActivity<MyApprovalTask>
{
    private readonly IStringLocalizer<MyApprovalTask> _localizer;

    public MyApprovalTask(IStringLocalizer<MyApprovalTask> localizer)
    {
        _localizer = localizer;
    }

    public override LocalizedString DisplayText => _localizer["Approval task"];

    public override LocalizedString Category => _localizer["My Module"];

    public override IEnumerable<Outcome> GetPossibleOutcomes(
        WorkflowExecutionContext workflowContext,
        ActivityContext activityContext)
        => Outcomes(_localizer["Done"], _localizer["Rejected"]);

    public override Task<ActivityExecutionResult> ExecuteAsync(
        WorkflowExecutionContext workflowContext,
        ActivityContext activityContext)
        => Task.FromResult(Outcomes("Done"));
}

Write a custom event activity

Derive from EventActivity. Its base Execute returns Halt, so implement CanExecuteAsync to accept only the input intended for the event and override Resume or ResumeAsync to return the continuation outcome.

using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Models;

namespace MyModule.Workflows.Activities;

public sealed class MyApprovalEvent : EventActivity
{
    private readonly IStringLocalizer<MyApprovalEvent> _localizer;

    public MyApprovalEvent(IStringLocalizer<MyApprovalEvent> localizer)
    {
        _localizer = localizer;
    }

    public override string Name => nameof(MyApprovalEvent);

    public override LocalizedString DisplayText => _localizer["Approval received"];

    public override LocalizedString Category => _localizer["My Module"];

    public override IEnumerable<Outcome> GetPossibleOutcomes(
        WorkflowExecutionContext workflowContext,
        ActivityContext activityContext)
        => Outcomes(_localizer["Done"]);

    public override Task<bool> CanExecuteAsync(
        WorkflowExecutionContext workflowContext,
        ActivityContext activityContext)
        => Task.FromResult(workflowContext.Input.ContainsKey("RequestId"));

    public override ActivityExecutionResult Resume(
        WorkflowExecutionContext workflowContext,
        ActivityContext activityContext)
        => Outcomes("Done");
}

Add display drivers and register activities

ActivityDisplayDriver<TActivity> creates thumbnail and design shapes. Add MyApprovalTask.Fields.Thumbnail.cshtml and MyApprovalTask.Fields.Design.cshtml under Views/Items; use ActivityDisplayDriver<TActivity, TEditViewModel> plus an unsealed view model and *.Fields.Edit.cshtml when the activity has configurable properties.

using OrchardCore.Workflows.Display;

namespace MyModule.Workflows.Drivers;

public sealed class MyApprovalTaskDisplayDriver
    : ActivityDisplayDriver<MyApprovalTask>;

public sealed class MyApprovalEventDisplayDriver
    : ActivityDisplayDriver<MyApprovalEvent>;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Modules;
using OrchardCore.Workflows.Helpers;

namespace MyModule;

public sealed class Startup : StartupBase
{
    public override void ConfigureServices(IServiceCollection services)
    {
        services.AddActivity<MyApprovalTask, MyApprovalTaskDisplayDriver>();
        services.AddActivity<MyApprovalEvent, MyApprovalEventDisplayDriver>();
    }
}

The exact registration API is AddActivity<TActivity, TDriver>(). It registers the activity and display driver in WorkflowOptions; it does not automatically trigger an event. Trigger the registered event through IWorkflowManager.TriggerEventAsync.