DEV Community

Cover image for Multi-tenant SaaS on a .NET workflow engine, without restarting to onboard a customer
Optimajet Limited
Optimajet Limited

Posted on

Multi-tenant SaaS on a .NET workflow engine, without restarting to onboard a customer

The most expensive restart in SaaS is often the one nobody planned: onboarding a new customer. Every SaaS team expects adding a tenant to be a database operation. I still see production systems where it is a deployment.

I am Mike Lukinov, co-founder of Optimajet. In July 2026 we shipped Workflow Engine by Optimajet version 22.0.0, and for SaaS teams it changes the thing that matters most: tenants become runtime objects instead of startup configuration. Tenants register and unregister while the host is running, and multi-tenancy is finally full and hybrid, meaning every layer of engine data can be tenant-scoped or shared, side by side in one installation.

Here is the scene that drove this work. Last month I spoke with a team running about 150 enterprise tenants on .NET. Everything worked, until sales signed one more customer. Their onboarding checklist contained the line "restart production". Every restart window interrupted other tenants' approvals mid-flight. That was not a workflow problem, it was an architecture problem.

This post is the announcement written the way I explain it on architecture calls: what a SaaS team should demand from a .NET workflow engine, and how the new version answers it. I mark each block as Documented behavior, Architectural recommendation, or Honest limitation, so you can separate facts from my opinion.

What a multi-tenant SaaS needs from a workflow engine

Architectural recommendation. When teams evaluate an enterprise workflow engine for SaaS, they usually compare designers and API surfaces. The tenancy questions surface later, in the security review, and they are the expensive ones. Five of them matter most:

  1. Can shared and tenant-specific data live side by side, or is tenancy all-or-nothing?
  2. Can I add a tenant without redeploying or restarting anything?
  3. Is tenant isolation enforced by the engine and its API, or by discipline in my own code?
  4. Does isolation cover all engine data, or only the process table?
  5. Can I adopt tenancy gradually, or does it require a data migration on day one?
Capability Why it matters
Hybrid tenancy Shared standard workflows plus per-tenant custom ones
Runtime tenant registration No production restart to onboard a customer
API-enforced tenant isolation Leaks blocked by the engine, not by code review
Isolation across all data Audit compliance: history and inboxes, not just processes
Gradual adoption No day-one migration project

Hold any engine you evaluate against these five. The rest of this post is how Workflow Engine NEO answers them.

Question 1: can shared and tenant-specific data live side by side

Documented behavior. The engine now implements full hybrid multi-tenancy. Every kind of engine data (workflow schemes, Global Parameters, forms, process records) exists in one of two forms: shared and visible to every tenant, or scoped to one tenant. A record with an empty tenant value is shared; a record created in a tenant context carries that tenant's ID. The Forms Plugin resolves a tenant-specific form first and falls back to the shared one.

Shared (no tenant ID)
├── Standard workflow schemes
├── Shared forms
└── Global Parameters
        │ visible to every tenant
   ┌────┴─────────┬───────────────┐
Tenant A       Tenant B        Tenant C
processes      processes       custom scheme
history        history         custom forms
inbox          inbox           processes, history
Enter fullscreen mode Exit fullscreen mode

Architectural recommendation. This hybrid model matches how a SaaS grows. One standard approval flow serves most customers; the accounts that pay for custom routing get multi-tenant workflows scoped to them, in the same installation. The alternative, a separate deployment per customized customer, is how SaaS margins die.

Question 2: runtime tenant onboarding while the host is running

Documented behavior. Per the release notes, retrieved 2026-07-21, the tenant registry accepts changes at runtime. This is the dynamic tenant registration piece:

// A new customer signs up. Register them as a tenant, at runtime:
var tenantRegistry = services.GetRequiredService<IWorkflowTenantRegistry>();

var tenants = await tenantRegistry.RegisterTenantsAsync(new WorkflowTenantCreationOptions
{
    TenantIds = ["TenantA"],
    PersistenceProviderId = PersistenceProviderId.Mssql,
    ConnectionString = connectionString
});

// No restart. Existing tenants keep processing their workflows.
var tenant = tenants.Single();

// Offboarding is the same call in reverse. The host keeps running.
await tenantRegistry.UnregisterTenantsAsync(tenant);
Enter fullscreen mode Exit fullscreen mode

Registry-managed tenants start automatically and shut down gracefully when removed. If your application owns tenant lifecycle itself, mark them with WorkflowTenantLifecycleOwnership.External and the registry skips the automatic lifecycle calls. Reads stay consistent while the list changes: IWorkflowTenantSnapshot gives request code an immutable, point-in-time view of registered tenants.

Architectural recommendation. Runtime tenant onboarding becomes one call in your provisioning code, next to creating the tenant database and issuing credentials. Onboarding turns into a database operation, not a deployment event.

Question 3: who enforces tenant isolation

Documented behavior. The tenant boundary is enforced by the API layer, not by convention. A Data API request for another tenant's process returns a not-found result. RPC endpoints check the process tenant against the current HTTP tenant and reject cross-tenant commands. My favorite detail: rpc/is-process-exists answers false for a process that belongs to another tenant. One tenant cannot even probe whether another tenant's data exists.

The same version closes a quieter hole: system Global Parameter types (security settings, LDAP configuration, CORS settings, and similar) are no longer reachable through generic Data API operations.

Architectural recommendation. The tenant header selects context, it does not grant access. Authorization still comes from the WorkflowApiPermissions claim. I wrote about scoping those claims per caller in When to use Data API vs RPC API in Workflow Engine NEO; everything there still holds, now with tenant isolation enforced underneath it.

Question 4: how deep the isolation goes

Documented behavior. Tenant isolation now covers everything the engine stores: schemes, Global Parameters, forms, and every process-related record, from statuses and timers to transition history, inbox entries, and approval history. The Forms Plugin runs tenant-aware on Form Engine 10.0.1.

Architectural recommendation. The record types at the end of that list are the ones to check in any engine you evaluate. Schemes and processes get the attention, but transition history, inbox entries, and approval history are what your auditor asks about, and they leak tenant activity just as well as a process record does. Isolation that stops at the process table is not isolation.

Question 5: what happens to the data you already have

Documented behavior. Nothing changes on its own. Existing records have no tenant value, so under the hybrid model from Question 1 they are shared and stay visible across tenants, exactly as they behaved before the upgrade. Only new records created in a tenant context get a tenant ID.

Architectural recommendation. Upgrade first, then adopt tenancy at the pace your contracts demand, not the pace your engine dictates. Keep standard schemes shared, scope the customer-specific ones, and let historical process data age out as shared records.

Honest limitations

I would rather you hear these from me than from your own upgrade weekend.

  • 22.0.0 is a breaking release. The tenant contract changed (custom IWorkflowTenant implementations need lifecycle members), several IPersistenceProvider signatures now take a tenant ID, and the AssignmentPlugin was removed. Database migrations are mandatory: SQL providers run RunMigrations() before starting the updated runtime, MongoDB needs the update_22.0.0.js index script run manually. The release notes pair every breaking change with a migration scenario. Budget a real upgrade task, not a version bump.
  • The Web API and full multi-tenancy are licensed NEO features. Workflow Engine is commercial software with a free tier, source-available under a commercial EULA, not open source, and the Web API does not start without a license key. A trial key from trial.workflowengine.io lets you evaluate it; the free Community Edition covers the embeddable in-process library, not the Web API.
  • Runtime registration does not provision infrastructure. RegisterTenantsAsync registers a tenant against a connection string you supply. Creating the database, running its schema, and storing its secrets remain your provisioning code's job.

Version note

Everything above targets Workflow Engine 22.0.0 (July 16, 2026), verified against the official release notes and the NuGet registry (WorkflowEngine.NETCore-Core 22.0.0) on 2026-07-21. On 21.x and earlier the tenant list is static and the data-layer scoping described here does not exist; do not read this article as documentation for those versions.

The questions are older than the release

Most workflow engine evaluations still focus on designers, BPMN coverage, and APIs. In SaaS, the tenant lifecycle becomes the real architectural bottleneck much later, when changing the answer is already expensive. The five questions above are worth asking before that day arrives, whichever workflow engine for SaaS you end up choosing.

If you want to check our answers against your own architecture, the upgrade details are on our blog, and my team does demo walkthroughs where we build the argument on your tenancy model, not ours.

Top comments (3)

Collapse
 
merbayerp profile image
Mustafa ERBAY

Runtime tenant registration is a meaningful improvement, especially for SaaS platforms where onboarding should never require a production restart.

I also like the hybrid model, but I’d add one architectural caution: API-level tenant isolation is only one layer. Background jobs, caches, direct persistence access, audit exports, and operational tooling all need the same tenant context and enforcement.

The real test is whether a missing or incorrect tenant context fails closed everywhere, not only through the public API.

Collapse
 
optimajet profile image
Optimajet Limited

Mustafa, thank you for this comment, I agree with your main point. А missing tenant context should fail everywhere, not only in the API.

We did push tenant context below the API in this release: timers and process records take their tenant from the process instance, and IPersistenceProvider signatures now require a tenant id, so a provider that ignores it won't compile. But you're right that the engine only covers its own surface. Raw SQL, caches, ops tooling stay the platform's responsibility, and that's where I see most real problems.

And while I have the luck of talking to someone who has clearly been around these systems, let me use it. Would Workflow Engine NEO pass your own technical bar as the main workflow solution for a .NET application? If yes, I'd like to know what would tip the scale for you. And I'm honestly even more interested in the other side: what weaknesses do you see, what's missing, what would stop you, if that's relevant to your work at all. Feel free to be blunt. That kind of feedback is worth more to me than praise.

Thanks in advance,
Mike Lukinov

Collapse
 
merbayerp profile image
Mustafa ERBAY

Thanks, Mike. I spent some time with the live designer and documentation after your reply.

Yes, Workflow Engine NEO would pass my initial technical bar and I would put it into a serious PoC for a .NET SaaS or ERP. The embedded designer, scheme versioning, hybrid tenancy, runtime tenant registration, and explicit tenant context in the persistence contracts are all strong architectural choices.

What would decide whether it became the main workflow solution would be operational behaviour rather than additional designer features:

  • What happens if a node dies midway through an action?
  • Are retries and duplicate execution semantics explicit enough to build idempotent integrations?
  • How does it behave with large timer backlogs and multiple runtime nodes?
  • Can one tenant be backed up, restored, migrated, or exported independently?
  • What OpenTelemetry traces, metrics and stuck-process signals are available?
  • What happens when tenant context is missing in timers, async work or custom actions?
  • How are running instances handled during scheme upgrades?
  • What happens to the runtime if licence validation becomes temporarily unavailable?

One design choice I would examine especially carefully is treating an empty tenant ID as shared data. Hybrid tenancy is useful, but I would want global object creation to be an explicit privileged operation, so a missing tenant context fails closed rather than accidentally creating something visible to every tenant.

So my honest answer is: yes, it is a credible candidate. What would tip the scale is proving recovery, observability, idempotency and tenant-level operations under failure—not adding more boxes to the designer. Features attract the first demo; operational behaviour decides whether the engine stays in production for ten years.