Building Custom Workflows in D365 Finance & Operations from Scratch
Custom workflows in Dynamics 365 Finance and Operations look intimidating because they span metadata, X++, security, queries, and the workflow editor. The real problem is not the wizard; it is understanding which artifact owns each responsibility. Once you separate document identity, submit logic, workflow events, and approval behavior, the framework becomes predictable.
A workflow starts with a document that the business can reason about
Do not begin with the approval step. Begin with the document. The workflow document is the business record being submitted, such as a custom request, contract, journal header, or compliance review. The table should already have a stable primary key, status field, and enough data for an approver to make a decision.
Most custom workflows need these building blocks:
| Artifact | Purpose | Example |
|---|---|---|
| Workflow category | Groups workflow types in a module | Procurement custom workflows |
| Workflow type | Defines the workflow document and runtime behavior | Vendor onboarding workflow |
| Query | Supplies fields available to workflow conditions | Header table with related dimensions |
| Approval element | Captures approve, reject, delegate, and request change | Manager approval |
| Event handlers | React to started, completed, and canceled events | Update document status |
| Menu item | Opens the workflow editor or submitted document | Display custom request |
The workflow category is metadata. Choose the module carefully because it affects where users configure the workflow. A finance document should not appear under a random custom module just because the developer created the category there first.
The wizard gives structure, but the query gives context
The workflow type wizard creates the core classes and metadata references. You still need a query that exposes the document and related fields used in conditions. If approvers need to route by amount, company, department, requester, or vendor group, those fields must be available through the workflow document query.
Keep the query focused. A workflow query is not a reporting model. Add the header table as the primary data source, then add only the relationships needed for routing and conditions. Overloaded queries make configuration confusing and can slow workflow evaluation.
<Query name="TAGVendorOnboardingWorkflowQuery">
<DataSources>
<DataSource name="TAGVendorOnboardingTable" table="TAGVendorOnboardingTable">
<Fields dynamic="Yes" />
</DataSource>
</DataSources>
</Query>
In the workflow type properties, point the document query to this query. Then verify the workflow editor can display the fields business users expect. If the condition builder is missing a field, fix the query or table metadata before adding code workarounds.
Workflow elements model decisions, not screen buttons
Workflow elements represent business steps. Approval elements are for formal approval with outcomes like approve, reject, delegate, and request change. Task elements assign work without necessarily approving the document. Automated tasks run code. Manual and automated decisions branch the flow based on user choice or rules.
| Element | Use when | Avoid when |
|---|---|---|
| Approval | A user must approve or reject the document | The user only needs to enter more data |
| Task | A user must perform work | Approval history is required |
| Automated task | X++ should perform a workflow step | The action needs human judgment |
| Manual decision | A user chooses the next path | The choice can be calculated |
| Automated decision | Rules determine the next path | The rule needs subjective review |
Name elements in business language. Use names like Credit manager approval or Compliance review, not Step 1 or Approval node. Those names appear in tracking, notifications, and support conversations.
The table must know whether it can be submitted
The submit button should not be available for every record state. Implement canSubmitToWorkflow on the table when the workflow framework needs to ask whether a record is eligible. Keep the check deterministic and based on the current record.
[ExtensionOf(tableStr(TAGVendorOnboardingTable))]
final class TAGVendorOnboardingTable_Workflow_Extension
{
public boolean canSubmitToWorkflow(str _workflowType = '')
{
boolean ret = next canSubmitToWorkflow(_workflowType);
ret = ret
&& this.RecId != 0
&& this.WorkflowStatus == TAGWorkflowStatus::Draft
&& this.VendGroup
&& this.Requester;
return ret;
}
}
Pair that with form logic that enables the workflow submit control only when the table says the record is eligible. Do not duplicate complex eligibility logic in the form if the table can own it.
Submit code changes status and hands control to workflow
The submit action usually runs from a menu item or form button. It should validate the record, activate the workflow instance, update the document status, and persist the workflow correlation. Use the generated workflow type name and the record context.
public static void submitToWorkflow(TAGVendorOnboardingTable _request, str _comment)
{
WorkflowCorrelationId correlationId;
WorkflowTypeName workflowTypeName = workflowTypeStr(TAGVendorOnboardingWorkflow);
if (!_request.canSubmitToWorkflow(workflowTypeName))
{
throw error("The vendor onboarding request is not ready for workflow.");
}
ttsbegin;
correlationId = Workflow::activateFromWorkflowType(
workflowTypeName,
_request.RecId,
_comment,
NoYes::No);
_request.WorkflowCorrelationId = correlationId;
_request.WorkflowStatus = TAGWorkflowStatus::Submitted;
_request.update();
ttscommit;
}
The transaction boundary matters. You do not want a workflow instance without a corresponding document status update, or a document marked submitted without a workflow instance.
Event handlers keep document status aligned with workflow state
Workflow event handlers are where the document reacts to lifecycle events. Started, completed, canceled, returned, and other events should update the document in a way the business understands. Keep these handlers boring and auditable.
public final class TAGVendorOnboardingWorkflowEventHandler
{
public void started(WorkflowEventArgs _workflowEventArgs)
{
this.updateStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), TAGWorkflowStatus::Submitted);
}
public void completed(WorkflowEventArgs _workflowEventArgs)
{
this.updateStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), TAGWorkflowStatus::Approved);
}
public void canceled(WorkflowEventArgs _workflowEventArgs)
{
this.updateStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), TAGWorkflowStatus::Draft);
}
private void updateStatus(RecId _recId, TAGWorkflowStatus _status)
{
TAGVendorOnboardingTable request;
ttsbegin;
select forupdate request
where request.RecId == _recId;
request.WorkflowStatus = _status;
request.update();
ttscommit;
}
}
After deployment, activate the workflow configuration in the workflow editor. A workflow type is not useful until a business user or administrator creates an active workflow version with assignments, conditions, and escalation behavior.
The clean implementation path is document first, metadata second, submit logic third, events fourth, and editor activation last. Build it that way and custom workflows stop feeling magical; they become a controlled state machine with business-friendly configuration on top.
Keep reading
Extending Data Entities in D365 Finance & Operations Without Breaking Upgrades
Add fields, computed columns, and validation to standard D365 Finance & Operations data entities the upgrade-safe way — with X++ examples and the staging-table traps to avoid.
Chain of Command vs Event Handlers: Extending D365 F&O the Right Way
When to use Chain of Command and when to use pre/post event handlers in Dynamics 365 Finance & Operations — with X++ examples, a decision table, and the gotchas that trip up teams.
Electronic Reporting in D365 Finance: Building Custom Formats Without Code
A practical guide to the Electronic Reporting (ER) framework in D365 Finance — data models, model mappings, and format configurations to produce custom files without X++.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
No comments yet. Be the first.