FlowOrchestrator.PostgreSQL
1.26.0
dotnet add package FlowOrchestrator.PostgreSQL --version 1.26.0
NuGet\Install-Package FlowOrchestrator.PostgreSQL -Version 1.26.0
<PackageReference Include="FlowOrchestrator.PostgreSQL" Version="1.26.0" />
<PackageVersion Include="FlowOrchestrator.PostgreSQL" Version="1.26.0" />
<PackageReference Include="FlowOrchestrator.PostgreSQL" />
paket add FlowOrchestrator.PostgreSQL --version 1.26.0
#r "nuget: FlowOrchestrator.PostgreSQL, 1.26.0"
#:package FlowOrchestrator.PostgreSQL@1.26.0
#addin nuget:?package=FlowOrchestrator.PostgreSQL&version=1.26.0
#tool nuget:?package=FlowOrchestrator.PostgreSQL&version=1.26.0
FlowOrchestrator
Code-first DAG orchestration for .NET. Runs on Hangfire, in-process, or Azure Service Bus.
π Documentation Β· NuGet Β· GitHub
What's new in v1.25 β Enterprise webhook hardening pipeline. The dashboard webhook receive endpoint now ships an opt-in HMAC signature verifier covering 17 partner dialects (GitHub, Stripe, Slack, Shopify, Twilio, Square, Zoom, Linear, Dropbox, Mailgun, MS Teams, Atlassian, Calendly, Bitbucket, Generic, Custom, GitHubLegacy), replay protection with timestamp + nonce store, token-bucket rate limiting, IP allow/deny lists with curated
KnownPublisherCidrspresets, body-size cap, DLQ + recent-deliveries log, and a new "Webhooks" dashboard tab. SQL Server + PostgreSQL backends ship for the replay + DLQ stores; in-memory remains the default. Three enforcement modes (Off,Audit,Enforce) let operators dry-run before flipping to reject. EventIds 4000β4010 + four new metrics (webhook_received_total,webhook_rejected_total,webhook_body_bytes,webhook_processing_ms) for observability. Full hardening cookbook.v1.24 β Realtime SSE push for the dashboard (replaces 5-second polling with
EventSource). v1.22 β Third runtime adapterFlowOrchestrator.ServiceBus; engine rejects triggers for disabled flows across all runtimes. v1.21 shipped server-side timeseries; v1.19 added health checks; v1.18 shippedWaitForSignal; v1.17 shippedWhenconditions. Full CHANGELOG.
When to choose FlowOrchestrator
β Choose FlowOrchestrator if:
- You want multi-step DAGs in .NET without standing up a separate workflow server
- Your team writes C# and wants flows defined as plain code, not JSON or a designer
- You need conditional branching (
When), polling, fan-out (ForEach), human-in-loop (WaitForSignal), and cron in one library - You want a built-in dashboard with Timeline, DAG, and Gantt views
- You want flows that are unit-testable in-process (
FlowTestHost) and renderable as Mermaid diagrams in a PR - You already use Hangfire β or want Azure Service Bus for cloud-native multi-replica scale-out β or want zero infrastructure at all (in-process runtime works without Hangfire or a database)
β Choose something else if:
- You need multi-language workflows (Python + Go + .NET) β Temporal
- You want replay-based deterministic execution β Temporal
- You're running a service mesh and want workflow as one of several building blocks β Dapr Workflows
- Non-developers need to author workflows in a visual designer β Elsa Workflows
- You only need fire-and-forget background jobs with no DAG β Hangfire alone
FlowOrchestrator is intentionally narrow. It is the DAG layer Hangfire is missing β nothing more, nothing less.
How it compares
| Hangfire | FlowOrchestrator | Elsa v3 | Temporal .NET | Dapr Workflows | |
|---|---|---|---|---|---|
| Background job execution | β | β (via Hangfire) | β | β | β |
Multi-step DAG with runAfter |
Manual | β | β | Implicit (code) | Implicit (code) |
| Polling pattern (no thread block) | Manual | β built-in | β | β durable timers | β durable timers |
| Code-first C# definitions | β | β | β | β | β |
| JSON / YAML workflow files | β | β by design | β | β | β |
| Visual designer | β | β by design | β Studio | β | β |
| Built-in DAG / Gantt / Timeline UI | β | β | β Studio | β Web UI | β |
| Polyglot SDK | .NET only | .NET only | .NET only | Go, Java, TS, Python, .NET | .NET, Python, JS, Java, Go |
| Separate server / sidecar required | β | β | Optional | β Required | β Sidecar |
| Storage you already have | SQL Server, PG, Redis | SQL Server, PG, in-memory | SQL Server, PG, MongoDB | Cassandra, MySQL, PG | State store of choice |
| Runtime / dispatcher options | n/a | Hangfire, in-process, Azure Service Bus | Hangfire, Quartz | Built-in cluster | Built-in actor system |
| Deterministic replay | β | β | β | β | β |
| External signals / human-in-loop | β | β WaitForSignal |
β | β | β |
| Operational complexity | Low | Low | LowβMedium | High | Medium |
| Learning curve (.NET dev) | Low | Low | Medium | MediumβHigh | Medium |
FlowOrchestrator deliberately ships fewer features than Temporal or Dapr Workflows. It does not replay. It does not run a separate server. It is for teams that want DAG orchestration inside their existing ASP.NET Core app β alongside Hangfire if they have it, or fully in-process if they do not.
Comparison verified 2026-04-30 against Elsa v3, Temporal .NET SDK v1, Dapr .NET SDK v1. PRs welcome to keep it current.
Coming from Hangfire?
// Before β recurring job with manual chaining, no DAG, no run history
RecurringJob.AddOrUpdate<NightlyOrdersJob>("nightly-orders",
job => job.RunAsync(), "0 2 * * *");
// Inside RunAsync: call FetchOrders, then SubmitToWms, then NotifySlack.
// Error branching, retry-per-step, run history, Gantt view β all on you.
// After β FlowOrchestrator declarative manifest
public sealed class NightlyOrdersFlow : IFlowDefinition
{
public Guid Id { get; } = new("a1b2c3d4-0000-0000-0000-000000000001");
public FlowManifest Manifest { get; set; } = new()
{
Triggers = { ["cron"] = new() { Type = TriggerType.Cron,
Inputs = { ["cronExpression"] = "0 2 * * *" } } },
Steps = {
["fetch"] = new() { Type = "FetchOrders" },
["submit"] = new() { Type = "SubmitToWms",
RunAfter = { ["fetch"] = [StepStatus.Succeeded] } },
["notify"] = new() { Type = "NotifySlack",
RunAfter = { ["submit"] = [StepStatus.Succeeded] } }
}
};
}
// Dashboard, per-step retry, full run history, DAG view β included.
And yes β your flows are testable. See FlowOrchestrator.Testing for a one-liner test host that runs flows in-process without Hangfire or ASP.NET.
Coming from Temporal or Dapr?
If you don't need replay-based determinism and a separate cluster, here is the simpler model:
// Temporal .NET β deterministic replay; requires Temporal Server cluster
[Workflow]
public class OrderWorkflow
{
[WorkflowRun]
public async Task RunAsync(string orderId)
{
await Workflow.ExecuteActivityAsync(
(Activities a) => a.FetchOrderAsync(orderId),
new() { ScheduleToCloseTimeout = TimeSpan.FromMinutes(5) });
await Workflow.ExecuteActivityAsync(
(Activities a) => a.SubmitToWmsAsync(orderId),
new() { ScheduleToCloseTimeout = TimeSpan.FromMinutes(5) });
}
}
// Requires: Temporal Server (Cassandra / MySQL / PG + Elasticsearch + server cluster)
// FlowOrchestrator β same outcome, runs inside your existing ASP.NET Core app
public sealed class OrderFlow : IFlowDefinition
{
public Guid Id { get; } = new("a1b2c3d4-0000-0000-0000-000000000002");
public FlowManifest Manifest { get; set; } = new()
{
Triggers = { ["manual"] = new() { Type = TriggerType.Manual } },
Steps = {
["fetch"] = new() { Type = "FetchOrder" },
["submit"] = new() { Type = "SubmitToWms",
RunAfter = { ["fetch"] = [StepStatus.Succeeded] } }
}
};
}
// Requires: SQL Server or PostgreSQL you already have, plus Hangfire.
Why FlowOrchestrator?
- Zero new infrastructure (or your choice) β runs inside your existing Hangfire app on SQL Server / PostgreSQL, in-process with a
Channel<T>and zero deps, or on Azure Service Bus for cloud-native scale-out. - Code-first, always β flows are plain C# classes; no YAML, no JSON files, no designer to learn.
- Built-in dashboard with realtime updates β Timeline, DAG, and Gantt views with retry, cancel, and re-run controls; state changes stream over Server-Sent Events the moment they happen, polling only kicks in if the stream stalls.
- Runtime-agnostic core β three runtimes ship today (Hangfire, in-process, Azure Service Bus); add your own without touching flow definitions.
Pick a runtime
FlowOrchestrator separates storage (where flow definitions and run history live) from the runtime adapter (which dispatches and executes steps).
| Hangfire runtime | InMemory runtime | ServiceBus runtime | |
|---|---|---|---|
| Step dispatcher | IBackgroundJobClient |
Channel<T> inside the host process |
Azure Service Bus topic + per-flow subscription |
| Cron triggers | IRecurringJobManager (multi-instance safe) |
PeriodicTimer (single-instance only) |
Self-perpetuating scheduled messages on a queue (multi-instance safe) |
| Survives process restart | β (jobs in Hangfire storage) | β (in-memory queue) | β (messages survive in the SB namespace) |
| Multi-instance horizontal scale | β | β | β (workers compete on the subscription) |
| Extra infrastructure | Hangfire + SQL Server / PostgreSQL | None | Azure Service Bus namespace (or local emulator) |
| Best for | Production workloads on .NET infra | Local dev, integration tests, single-node side projects | Cloud-native deployments, multi-region scale-out |
Storage is independent β InMemory storage works only for dev / tests, while SQL Server and PostgreSQL are production-ready under either runtime.
Install
dotnet add package FlowOrchestrator.Core
# Runtime adapter β pick one
dotnet add package FlowOrchestrator.Hangfire # Hangfire-backed (production default)
dotnet add package FlowOrchestrator.InMemory # In-process Channel<T> (dev / testing / single-node)
dotnet add package FlowOrchestrator.ServiceBus # Azure Service Bus (cloud-native multi-instance)
# Storage backend β pick one
dotnet add package FlowOrchestrator.SqlServer # or FlowOrchestrator.PostgreSQL
# FlowOrchestrator.InMemory ships its own storage too
# Optional
dotnet add package FlowOrchestrator.Dashboard # REST API + SPA dashboard
dotnet add package FlowOrchestrator.Testing # FlowTestHost β in-process integration test helper
Quick Start β SQL Server + Hangfire
// Program.cs
builder.Services.AddHangfire(c => c
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(connectionString));
builder.Services.AddHangfireServer();
builder.Services.AddFlowOrchestrator(options =>
{
options.UseSqlServer(connectionString); // persist + auto-migrate tables
options.UseHangfire(); // Hangfire step dispatcher
options.AddFlow<OrderFulfillmentFlow>();
});
builder.Services.AddStepHandler<FetchOrdersStep>("FetchOrders");
builder.Services.AddStepHandler<SubmitToWmsStep>("SubmitToWms");
builder.Services.AddFlowDashboard(builder.Configuration); // optional
app.UseHangfireDashboard("/hangfire");
app.MapFlowDashboard("/flows");
Define a flow:
public sealed class OrderFulfillmentFlow : IFlowDefinition
{
// Always use a fixed GUID literal β never Guid.NewGuid()
public Guid Id { get; } = new("a1b2c3d4-0000-0000-0000-000000000002");
public FlowManifest Manifest { get; set; } = new()
{
Triggers = {
["manual"] = new() { Type = TriggerType.Manual },
["webhook"] = new() { Type = TriggerType.Webhook,
Inputs = { ["webhookSlug"] = "order-fulfillment" } }
},
Steps = {
["fetch"] = new() { Type = "FetchOrders" },
["submit"] = new() { Type = "SubmitToWms",
RunAfter = { ["fetch"] = [StepStatus.Succeeded] } }
}
};
}
Open http://localhost:5000/flows β trigger the flow, watch steps execute in the DAG view, retry any failure.
Quick Start β InMemory (zero infrastructure)
For local development, prototypes, and single-node side projects β no Hangfire, no database:
// Program.cs
builder.Services.AddFlowOrchestrator(options =>
{
options.UseInMemory(); // storage in-process
options.UseInMemoryRuntime(); // Channel<T> dispatcher + PeriodicTimer cron
options.AddFlow<OrderFulfillmentFlow>();
});
builder.Services.AddStepHandler<FetchOrdersStep>("FetchOrders");
builder.Services.AddStepHandler<SubmitToWmsStep>("SubmitToWms");
builder.Services.AddFlowDashboard(builder.Configuration);
app.MapFlowDashboard("/flows");
All run data is lost on restart β see Storage Backends for the full picture, and Production Checklist for why this combo is unsuitable for production.
For PostgreSQL, see π Getting Started.
Quick Start β Azure Service Bus
For cloud-native deployments where workers scale horizontally across replicas/regions:
// Program.cs β runtime is Azure Service Bus, storage stays in your existing DB.
builder.Services.AddFlowOrchestrator(options =>
{
options.UseSqlServer(connectionString); // (or UsePostgreSql / UseInMemory)
options.UseAzureServiceBusRuntime(sb =>
{
sb.ConnectionString = builder.Configuration.GetConnectionString("ServiceBus")!;
sb.AutoCreateTopology = true; // creates topic + sub-per-flow at startup
});
options.AddFlow<OrderFulfillmentFlow>();
});
builder.Services.AddStepHandler<FetchOrdersStep>("FetchOrders");
builder.Services.AddFlowDashboard(builder.Configuration);
app.MapFlowDashboard("/flows");
Topology β one topic (flow-steps) with one subscription per registered flow (SQL filter on FlowId); plus one queue (flow-cron-triggers) for self-perpetuating cron schedules. The engine's Dispatch many, Execute once invariant (dispatch ledger + claim guard) handles Service Bus's at-least-once delivery model β duplicate messages cannot run a step twice.
Local development uses the official Microsoft Service Bus emulator. The included Aspire AppHost wires it via AddAzureServiceBus("servicebus").RunAsEmulator(); run with dotnet run --project ./FlowOrchestrator.AppHost and the flow-servicebus instance comes up on port 5104.
Full documentation
| Topic | Link |
|---|---|
| Getting started (all runtimes) | getting-started |
| Core concepts β Flow, Step, RunId | core-concepts |
| Step handlers | step-handlers |
| Trigger types | triggers |
Expression reference (@triggerBody()) |
expressions |
| Polling pattern | polling |
| ForEach / fan-out | foreach |
| Dashboard & REST API | dashboard |
| Storage backends | storage |
| Configuration reference | configuration |
| Architecture | architecture |
| Observability | observability |
| Mermaid export | mermaid-export |
Conditional execution (When) |
conditional-execution |
Human-in-loop (WaitForSignal) |
wait-for-signal |
Testing flows (FlowTestHost) |
testing |
| Versioning flows in production | versioning |
| Production deployment checklist | production-checklist |
Production?
Before changing any deployed flow, read Versioning Flows β it explains which manifest changes are safe and which need a maintenance window. Before go-live, walk through the Production Checklist for storage, multi-instance, monitoring, secrets, capacity, and upgrade guidance. Wire AddFlowOrchestratorHealthChecks() into /health so your load balancer can drop traffic when the flow store is unreachable.
Visualize any flow with one line
flow.ToMermaid() returns a Mermaid flowchart string that renders in GitHub
READMEs, Notion, Confluence, and any modern Markdown surface β no running app
required. Here is the sample OrderFulfillmentFlow:
flowchart TD
classDef trigger fill:#e1f5ff,stroke:#0288d1
classDef entry fill:#c8e6c9,stroke:#388e3c
classDef polling fill:#fff9c4,stroke:#f57f17
classDef loop fill:#f3e5f5,stroke:#7b1fa2
T_manual["β‘ manual<br/>Manual"]:::trigger
T_webhook["β‘ webhook<br/>Webhook /order-fulfillment"]:::trigger
fetch_orders["fetch_orders<br/><i>QueryDatabase</i>"]:::entry
submit_to_wms["submit_to_wms<br/><i>CallExternalApi</i>"]:::polling
save_result["save_result<br/><i>SaveResult</i>"]
T_manual --> fetch_orders
T_webhook --> fetch_orders
fetch_orders -- Succeeded --> submit_to_wms
submit_to_wms -- Succeeded --> save_result
The dashboard ships a Copy Mermaid button on every flow detail page, and the
sample app exposes --export-mermaid <flowId> for CI integrations.
Compatibility
| Package | Target frameworks |
|---|---|
FlowOrchestrator.Core |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.Hangfire |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.InMemory |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.SqlServer |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.PostgreSQL |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.Dashboard |
net8.0 Β· net9.0 Β· net10.0 |
FlowOrchestrator.Testing |
net8.0 Β· net9.0 Β· net10.0 |
License
MIT β see the LICENSE file.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Dapper (>= 2.1.72)
- FlowOrchestrator.Core (>= 1.26.0)
- FlowOrchestrator.Hangfire (>= 1.26.0)
- Hangfire.Core (>= 1.8.23)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql (>= 10.0.2)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
-
net8.0
- Dapper (>= 2.1.72)
- FlowOrchestrator.Core (>= 1.26.0)
- FlowOrchestrator.Hangfire (>= 1.26.0)
- Hangfire.Core (>= 1.8.23)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql (>= 10.0.2)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
- System.Text.Json (>= 10.0.7)
-
net9.0
- Dapper (>= 2.1.72)
- FlowOrchestrator.Core (>= 1.26.0)
- FlowOrchestrator.Hangfire (>= 1.26.0)
- Hangfire.Core (>= 1.8.23)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql (>= 10.0.2)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
- System.Text.Json (>= 10.0.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.26.0 | 21 | 5/10/2026 |
| 1.26.0-preview.134 | 21 | 5/10/2026 |
| 1.26.0-preview.133 | 26 | 5/10/2026 |
| 1.25.1 | 34 | 5/10/2026 |
| 1.25.0 | 29 | 5/9/2026 |
| 1.25.0-preview.129 | 28 | 5/9/2026 |
| 1.24.0 | 87 | 5/3/2026 |
| 1.23.0 | 87 | 5/3/2026 |
| 1.23.0-preview.128 | 33 | 5/6/2026 |
| 1.23.0-preview.126 | 41 | 5/5/2026 |
| 1.23.0-preview.125 | 32 | 5/5/2026 |
| 1.23.0-preview.124 | 31 | 5/5/2026 |
| 1.23.0-preview.123 | 41 | 5/3/2026 |
| 1.23.0-preview.120 | 45 | 5/3/2026 |
| 1.23.0-preview.118 | 46 | 5/3/2026 |
| 1.22.0 | 97 | 5/3/2026 |
| 1.22.0-preview.116 | 46 | 5/3/2026 |
| 1.21.0 | 82 | 5/2/2026 |
| 1.21.0-preview.114 | 44 | 5/2/2026 |
| 1.20.0-preview.113 | 41 | 5/2/2026 |