Business Central Approval Workflow Generator
Overview
Generates all AL objects required to integrate a Business Central entity (standard or custom table) into the built-in Workflow approval engine. The generated code follows the pattern used by standard BC approvals (Purchase Orders, Sales Orders) and subscribes to the same event hooks.
Quick Start
Example requests:
- "Create an approval workflow for Statistical Account"
- "Add approval process to my custom Job Request table"
- "Generate send/cancel approval actions for the Transfer Order card"
- "Implement approval workflow on the Vendor Card"
Prerequisites
- Available object IDs from
app.json→idRanges - Project affix from
app.json→affixes - The target table must already exist (standard or custom)
- Knowledge of which page(s) display the entity (Card and/or List)
Interview — Mandatory Questions
Before generating any code the skill MUST ask the user the following
questions using vscode_askQuestions. Do NOT proceed until every answer
is collected.
Question 1 — Target entity
Which table should have the approval workflow?
If the user already mentioned the entity, confirm it. Otherwise present a free-text input. Validate the table exists in the workspace or is a known standard BC table.
Question 2 — Table type
Is this a custom table you own, or a standard BC table that you will extend via table extension?
| Option | Impact |
|---|---|
| Custom table | Field added directly; OnDelete trigger in table |
| Standard table (extension) | Field added via tableextension; OnBeforeDelete trigger |
Question 3 — Related sub-tables (lock-down)
Does this entity have related sub-tables (e.g. lines on a document) that should be locked (Editable = false) when the approval status is not Open?
If Yes, ask for the sub-table name(s) and the page part(s) that
display them. The generator will add Editable property binding on
those subpage parts.
Question 4 — Pages
Which pages should receive the Send / Cancel Approval Request actions?
Present options:
- Card page only
- List page only
- Both Card and List (default / recommended)
Ask for the exact page names if not obvious from the entity name.
Question 5 — Approver routing
Which approver routing should the workflow template default to?
| Option | Description |
|---|---|
| Direct Approver | Routes to the user's direct approver in Approval User Setup (default) |
| First Qualified Approver | Routes to the first user with sufficient approval limit |
| Approver Chain | Routes through the full approval hierarchy |
| Specific User | Always routes to a fixed user |
Code Generation Steps
Step 1 — Read project metadata
- Read
app.jsonto obtainidRanges,affixes,name,publisher. - Determine the affix (e.g.
BCS). - Allocate object IDs for: Enum, Codeunit (Approval Mgmt.), Codeunit (Workflow Setup), and optionally Table Extension / Page Extensions.
Step 2 — Determine naming
Derive names from the entity name and project affix:
| Artefact | Naming pattern | Example (entity = Statistical Account, affix = BCS) |
|---|---|---|
| Enum | <Affix> <Short> Appr. Status | BCS Stat. Acc. Appr. Status |
| Table Extension | <Affix> <Entity> | BCS Statistical Account |
| Approval Mgmt. CU | <Affix> <Short> Approval Mgmt. | BCS Stat. Acc. Approval Mgmt. |
| Workflow Setup CU | <Affix> <Short> Workflow Setup | BCS Stat. Acc. Workflow Setup |
| Page Extension (Card) | <Affix> <Entity> Card | BCS Statistical Account Card |
| Page Extension (List) | <Affix> <Entity> List | BCS Statistical Account List |
Step 3 — Determine folder structure
Place generated files under the appropriate feature folder:
Custom table (you own the table and page objects):
<feature-folder>/
├── Enum/
│ └── <Affix><ShortPascal>ApprStatus.Enum.al
├── Table/
│ └── <Affix><Entity>.Table.al (add field + OnDelete)
├── Page/
│ ├── <Affix><Entity>Card.Page.al (add actions + triggers)
│ └── <Affix><Entity>List.Page.al
└── Codeunit/
├── <Affix><ShortPascal>ApprovalMgmt.Codeunit.al
└── <Affix><ShortPascal>WorkflowSetup.Codeunit.al
Standard table (extend via tableextension / pageextension):
<feature-folder>/
├── Enum/
│ └── <Affix><ShortPascal>ApprStatus.Enum.al
├── Table Extension/
│ └── <Affix><Entity>.TableExt.al
├── Page Extension/
│ ├── <Affix><Entity>Card.PageExt.al
│ └── <Affix><Entity>List.PageExt.al
└── Codeunit/
├── <Affix><ShortPascal>ApprovalMgmt.Codeunit.al
└── <Affix><ShortPascal>WorkflowSetup.Codeunit.al
Step 4 — Generate the Approval Status Enum
Create an enum with four values: Open, Pending Approval, Rejected,
Approved. Mark as Extensible = true.
See: references/enum-template.md
Step 5 — Generate the Table / Table Extension
Add a field of the enum type with:
Editable = falseDataClassification = CustomerContent
Deletion guard logic:
- Blocks deletion if status is Approved or Pending Approval.
- Calls
ApprovalsMgmt.DeleteApprovalEntries(Rec.RecordId).
Editability helper:
- Add an
ApprovalStatusAllowModify(): Booleanprocedure that returnsfalsewhen status is Approved or Pending Approval,trueotherwise. Pages use this to bindEditableon layout groups.
| Table type | Trigger | Template |
|---|---|---|
| Custom table | OnDelete directly in the table | references/custom-table-template.md |
| Standard table | OnBeforeDelete in a tableextension | references/table-extension-template.md |
Step 6 — Generate the Approval Management Codeunit
This is the core object. It contains:
- Integration event publishers —
OnSend…ForApproval,OnCancel…ApprovalRequest - Validation —
Check…ApprovalPossible,Is…ApprovalWorkflowEnabled - Event code identifiers —
Run…Code(): Code[128] - Event subscribers for workflow events — handle
HandleEventcalls - Event subscribers for the workflow library — register events and predecessors
- Event subscribers for approval entry — populate entry arguments
- Event subscribers for status updates — pending, approve, reject
- Event subscribers for response handling — release, open, response predecessors
- Card page mapping —
OnConditionalCardPageIDNotFound - Set status to Open helper —
Set{Entity}StatusToOpenprocedure called from the Cancel action on pages to revert status
See: references/approval-mgmt-codeunit-template.md
Step 7 — Generate the Workflow Setup Codeunit
Subscribes to Workflow Setup events to register:
- A workflow category (short code + description)
- Table relations linking the entity to
Approval EntryANDWorkflow Webhook Entry(for Power Automate integration) - A workflow template with pre-configured steps
- UpdateMatrixData — inserts
WF Event/Response Combinationrecords so the workflow editor shows all response options (Send Approval Request, Set Status to Pending Approval, Send Notification to Webhook, Cancel All Approval Requests) for the custom events. Without this, responses are missing from the workflow step configuration UI.
See: references/workflow-setup-codeunit-template.md
Step 8 — Generate Page / Page Extension(s)
For each target page (Card / List), add:
- The
Approval Statusfield in layout (withStyleExpr) - An Approval action group with Send / Cancel actions
OnAfterGetRecordtrigger logic for button enablement and page editability viaRec.ApprovalStatusAllowModify()(custom table) orRec.{Affix}ApprovalStatusAllowModify()(table extension)modify(General) { Editable = GPageEditable; }to lock the General group when status is not Open — do NOT useCurrPage.Editableas it prevents approval action buttons from being clickable- Cancel action calls
ApprovalMgmt.Set{Entity}StatusToOpen(Rec)to explicitly revert status - If sub-tables are locked:
Editablebinding on subpage parts
| Page type | Template |
|---|---|
| Custom page (you own the page object) | references/custom-page-card-template.md |
| Standard page (extend via pageextension) | references/page-extension-card-template.md |
Step 9 — Summary and next steps
After generating all files, present a summary:
Approval Workflow Generated for: [Entity Name]
Objects created:
- Enum [ID] "[Name]" — [file path]
- Table Extension [ID] "[Name]" — [file path] (if applicable)
- Codeunit [ID] "[Name]" — [file path]
- Codeunit [ID] "[Name]" — [file path]
- Page Extension [ID] "[Name]" — [file path]
- Page Extension [ID] "[Name]" — [file path]
Post-publish setup:
1. Publish the extension
2. Navigate to Workflows → create workflow from template
3. Configure Approval User Setup
4. Enable the workflow
5. Test: Send → Approve / Reject / Delegate / Cancel
Event Subscriber Reference
The approval management codeunit must subscribe to these standard events:
| Standard Codeunit | Event | Purpose |
|---|---|---|
Workflow Event Handling | OnAddWorkflowEventsToLibrary | Register send & cancel events |
Workflow Event Handling | OnAddWorkflowEventPredecessorsToLibrary | Chain events |
Approvals Mgmt. | OnPopulateApprovalEntryArgument | Fill Document No. / Table ID |
Approvals Mgmt. | OnSetStatusToPendingApproval | Status → Pending |
Approvals Mgmt. | OnApproveApprovalRequest | Status → Approved |
Approvals Mgmt. | OnRejectApprovalRequest | Status → Rejected |
Approvals Mgmt. | OnBeforeShowCommonApprovalStatus | Display message |
CRITICAL: The
OnBeforeShowCommonApprovalStatusevent parameter is namedIsHandle(NOTIsHandled). Using the wrong name causes the subscriber to silently fail to bind.
CRITICAL: Use
HasOpenOrPendingApprovalEntriesinOnApproveApprovalRequest(NOTHasOpenPendingApprovalEntrieswhich does not exist).
CRITICAL: Variable declarations must be ordered by type: Record, Report, Codeunit, XmlPort, Page, Query, then scalar types. Place
RecordRefafterCodeunitvariables.
| Standard Codeunit | Event | Purpose |
|---|---|---|
Workflow Response Handling | OnReleaseDocument | Status → Approved |
Workflow Response Handling | OnOpenDocument | Status → Open |
Workflow Response Handling | OnAddWorkflowResponsePredecessorsToLibrary | Link responses |
Page Management | OnConditionalCardPageIDNotFound | Card page mapping |
Workflow Setup | OnAddWorkflowCategoriesToLibrary | Register category |
Workflow Setup | OnAfterInsertApprovalsTableRelations | Table → Approval Entry |
Workflow Setup | OnInsertWorkflowTemplates | Create template |
Rules
- Always use the project affix for all object and field names.
- Enum values must be:
Open(0),Pending Approval(1),Rejected(2),Approved(3). - The approval status field must be
Editable = false. - Event code identifiers (
Code[128]) must be uppercase and unique. - All
Modify()calls in approval status updates must useModify(true). - The workflow category code must be ≤ 20 characters and
Locked = true. - If sub-tables need locking, bind
Editableon the subpage part to the header's approval status = Open. - Follow the instruction files for naming, code style, and performance.
- Page editability: Use
modify(General) { Editable = GPageEditable; }whereGPageEditableis set fromRec.ApprovalStatusAllowModify()inOnAfterGetRecord. Do NOT useCurrPage.Editable— it disables approval action buttons when the page is non-editable. - Variable ordering: Declare variables in AL-required order:
Record → Report → Codeunit → XmlPort → Page → Query → scalar types.
RecordRefgoes after Codeunit. - OnBeforeShowCommonApprovalStatus: The parameter is
IsHandle(NOTIsHandled). Using the wrong name silently breaks the binding. - OnApproveApprovalRequest: Use
HasOpenOrPendingApprovalEntries(NOTHasOpenPendingApprovalEntrieswhich does not exist). - UpdateMatrixData: The Workflow Setup codeunit must insert
WF Event/Response Combinationrecords so the workflow editor shows all response options for the custom events. - Workflow Webhook Entry: Register a table relation from the entity
SystemIdtoWorkflow Webhook Entry."Data ID"for Power Automate. - Cancel action: Must explicitly call a
Set{Entity}StatusToOpenprocedure on the Approval Mgmt. codeunit to revert the status.