Invoice Exception Handler & Approvals
Build approval workflows and a Copilot Studio agent for invoice processing.
Introduction
30 Second Summary
Every finance team has that one invoice that sits in limbo because nobody knows who needs to approve it. The bigger the amount, the more people get involved, and the longer it takes to resolve.
In this project, you will build a complete business process layer on top of your existing invoice data. You will create approval workflows that route invoices to the right people, enforce validation rules, and embed an AI assistant that lets reviewers ask questions in plain English.
What You'll Build
A finance reviewer experience powered by Power Platform that guides invoices from receipt to payment with automated approvals and an embedded AI agent.
By the end of this project, you'll have:
- A 4-stage Business Process Flow guiding invoices through Received, Verified, Approved, and Paid stages with Business Rules enforcing field-level validation.
- An approval cloud flow that conditionally routes high-value invoices to managers with AI-generated summaries.
- A canvas app with reusable User-Defined Functions and a Copilot Studio agent embedded via PCF control for natural-language invoice queries.
- Secret Mission: Add concurrency control and Run After error handling so timed-out approvals automatically escalate to a backup approver.
Are there any prerequisites?
This project builds on top of Project 1 (Smart Invoice Intelligence Pipeline). You need the Dataverse environment and invoice table created in that project.
You also need a Power Platform premium license and access to the Azure Portal for creating an app registration in Step 5.
Before We Start
Your Smart Invoice Intelligence Pipeline from Project 1 gets invoice data into Dataverse. This project adds the business logic layer that turns raw data into a governed process with rules, approvals, and a reviewer experience.
Before building, you need to prepare your environment. In this step, you'll extend your invoice table with columns for approval tracking, create a solution to hold all your components, and enable a key environment setting.
In this step, get ready to:
- Add process columns (Approval Status, Reviewer Notes, and more) to your invoice table.
- Create a solution named "Invoice Exception Handler" to organize your project components.
- Enable PCF canvas app publishing in your environment settings.
Add process columns to your invoice table
Your invoice table from Project 1 has the basics like vendor info and line items. For approval routing and process tracking, you need additional columns that track who approved what and whether thresholds were exceeded.
- Open your browser and navigate to make.powerapps.com.
- Select Tables in the left navigation pane.
- Find and open your invoice table from Project 1.
- Select the Columns area to view all existing columns.
You will add five new columns one at a time. For each column, follow the same pattern:
- Click New column on the command bar.
- Enter the Display Name and select the Data type as specified below.
- Click Done to close the Column properties panel.
- Repeat for each column in the list.
Here are the five columns to create:
- Display Name: Approval Status. Data type: Choice. Add four options: Pending, Approved, Rejected, Escalated.
- Display Name: Reviewer Notes. Data type: Multiline Text.
- Display Name: Approval Threshold Exceeded. Data type: Yes/No.
- Display Name: Manager Approver. Data type: Single line of text (or Email if available).
- Display Name: Amount Due. Data type: Currency. Skip this one if your table already has it from Project 1.
- After adding all columns, click Save Table to persist changes to Dataverse.
What do these columns do?
These columns power the approval workflow you'll build in later steps. Approval Status tracks where each invoice sits in the process. Approval Threshold Exceeded is a flag that Business Rules will set automatically when an invoice amount is high.
Manager Approver stores who needs to sign off on expensive invoices. Reviewer Notes gives approvers a place to record their reasoning.
Don't see your invoice table?
Make sure you're in the same Power Platform environment where you completed Project 1. Check the environment selector in the top-right corner of the maker portal to confirm.
help me find my Dataverse invoice table from a previous project
Create a solution for Project 2
Solutions in Power Apps act as containers that group related components together. Creating one now keeps your BPF, Business Rules, flows, canvas app, and PCF control organized in a single package.
- Select Solutions in the left navigation pane.
- Click New solution.
- Enter Invoice Exception Handler in the Display name field.
- Select your existing publisher from the Publisher dropdown, or create a new one if needed.
- Click Create.
Now add your invoice table to the solution:
- Open the Invoice Exception Handler solution you just created.
- On the command bar, click Add existing, then select Table.
- Select your invoice table from the list and click Next.
- Select Include all objects to bring in all the table's columns, forms, and views.
- Click Add.
Why use a solution?
A solution packages all your customizations (tables, flows, apps, rules) into a single unit. You can export it, import it into another environment, or use it for source control. It also helps avoid cluttering the default solution with unrelated changes.
You should now see your invoice table listed inside the Invoice Exception Handler solution.
Enable PCF controls in your environment
In Step 5, you'll embed a Copilot Studio agent into your canvas app using a PCF (Power Apps Component Framework) code component. For this to work, your environment needs to allow canvas apps with code components.
- Open a new browser tab and navigate to the Power Platform admin center at admin.powerplatform.microsoft.com.
- Select Environments in the left navigation.
- Click on your environment name to open it.
- Click Settings in the top command bar.
- Expand Product and select Features.
- Find Allow publishing of canvas apps with code components and set it to On.
- Click Save at the bottom of the page.
Why enable this now?
The ChatControl PCF component (which embeds your Copilot Studio agent) is a code component. Without this setting enabled, Power Apps blocks you from publishing any canvas app that contains PCF controls. Enabling it early avoids a blocker in Step 5.
Don't see the Features page?
Make sure you're in the Power Platform admin center (not the Power Apps maker portal). You also need environment admin or system admin permissions to access environment settings.
help me find the Features settings in Power Platform admin center
Your environment is ready. Next up, you'll create a Business Process Flow that guides invoices through four stages and add Business Rules that enforce validation automatically.
Build the Business Process Flow and Business Rules
Your Dataverse table now has all the columns needed to track invoice approvals. The next challenge is ensuring every invoice follows a consistent path from receipt to payment.
A Business Process Flow gives reviewers a visual stage bar that guides them through each phase. Business Rules add automatic field-level logic so the form enforces your organization's policies without custom code. Together, they create a governed process layer on top of raw data.
In this step, get ready to:
- Create a 4-stage Business Process Flow guiding invoices from Received to Paid.
- Configure three Business Rules that lock fields, show errors, and enforce requirements based on invoice amount.
- Test both the BPF and rules in a model-driven app.
Create the Business Process Flow
The BPF creates a visual stage bar at the top of invoice records. Each stage collects specific data before the reviewer can advance to the next one.
- In the Power Apps maker portal, open Solutions from the left navigation.
- Open your Invoice Exception Handler solution.
- Select New > Automation > Process > Business process flow.
- Enter the display name Invoice Processing Flow.
- In the Table dropdown, select your invoice table.
- Select Create. The BPF designer opens with a single default stage.
Now configure the four stages and their data steps.
- Select the first stage and set its Display Name to Received.
- In the Details panel, add three data steps: Vendor Name (set as Required), Amount Due (Required), and Invoice Date (Required).
- Select Add on the designer canvas to add a second stage.
- Name the second stage Verified.
- Add two data steps: Line Items Confirmed (Yes/No, Required) and Reviewer Notes (not required).
- Add a third stage named Approved.
- Add one data step: Approval Status (Required). This marks the step as required, which enables stage-gating. The record cannot advance past this stage until Approval Status is filled.
- Add a fourth stage named Paid.
- Add one data step: Payment Date (Required).
What is stage-gating?
Making a data step Required prevents users from advancing to the next stage until data is entered for the corresponding column. This is how you enforce that the Approved stage cannot be skipped without setting the Approval Status.
- Select Validate on the action bar to check for errors.
- If no errors appear, select Activate on the action bar to make the BPF available.
- Confirm the activation when prompted.
Seeing validation errors?
Ensure each data step maps to a column that exists on your invoice table. If you renamed columns during Step 1, the display names in the BPF designer must match exactly. Double-check that the invoice table is selected as the primary table.
Help me fix my BPF validation errors.
Create Business Rules
Business Rules run server-side logic on your table without writing code. You will create three rules that enforce amount-based policies on every invoice record.
- In your solution, open your invoice table.
- Select the Business rules tab.
- Select + New business rule to open the rule designer.
Configure Rule 1: Lock Status on High Value.
- Set the rule name to Lock Status on High Value.
- In the Scope dropdown at the top right, select Entity.
- Select the condition component on the canvas and set: Field = Amount Due, Operator = is greater than, Value = 10000.
- On the true (checkmark) side, add a Lock action on the Approval Status field.
- Add a Set Field Value action: set Approval Threshold Exceeded to Yes.
- On the false (X) side, add an Unlock action on the Approval Status field.
- Add a Set Field Value action: set Approval Threshold Exceeded to No.
- Select Validate on the action bar, then Save.
Why Entity scope?
Entity scope means the rule runs on the server and applies to all forms and programmatic updates. This ensures high-value invoices are locked regardless of which app or API modifies the record.
Configure Rule 2: Require Manager for High Value.
- Return to the Business rules tab and select + New business rule.
- Name the rule Require Manager for High Value.
- Set Scope to Entity.
- Set the condition: Field = Approval Threshold Exceeded, Operator = equals, Value = Yes.
- On the true side, add a Set Business Required action on the Manager Approver field.
- On the false side, add a Set Business Required action on the Manager Approver field, and set the status to Not Business Required.
- Select Validate, then Save.
Configure Rule 3: Show Error on Missing Verification.
- Return to the Business rules tab and select + New business rule.
- Name the rule Show Error on Missing Verification.
- Set Scope to All Forms.
- Set the condition with two clauses (AND logic): Field 1 = Active Stage equals Approved, AND Field 2 = Line Items Confirmed equals No.
- On the true side, add a Show Error Message action on the Approval Status field.
- Set the error message to: Line items must be confirmed before approval.
- Select Validate, then Save.
Now activate all three rules.
- Return to the Business rules list view for your invoice table.
- Select each rule and click Activate on the command bar.
- Confirm activation for each rule when prompted.
What do these rules do together?
Rule 1 locks the Approval Status field on high-value invoices so reviewers cannot manually override it. It also flags the record for the approval flow you will build next.
Rule 2 forces a Manager Approver to be specified when the threshold is exceeded. Rule 3 prevents anyone from reaching the Approved stage without first confirming line items.
Rules not activating?
Ensure all fields referenced in conditions and actions exist on your invoice table. If a field was deleted or renamed since Step 1, the rule cannot resolve it. Also verify that the scope is set correctly for each rule.
Help me debug my Business Rule activation issue.
Test BPF and Business Rules in a model-driven app
Business Process Flows and Business Rules with Entity scope run inside model-driven apps. You need a quick model-driven app to verify everything works before building the canvas app.
- In your Invoice Exception Handler solution, select New > App > Model-driven app.
- Enter the name Invoice Test App and select Create.
- In the app designer, select Add page on the command bar, then select Dataverse table.
- Select your invoice table and click Add.
- Select Save, then Publish.
- Select Play to open the app in a new browser tab.
What is a model-driven app?
A model-driven app automatically generates its UI from your Dataverse table structure, forms, and views. It is the primary app type that supports Business Process Flows and entity-scoped Business Rules.
Now create a test invoice and walk through the BPF stages.
- Select + New to create a new invoice record.
- You should see the BPF stage bar at the top with four stages: Received, Verified, Approved, and Paid.
- In the Received stage, enter a Vendor Name, set Amount Due to 15000 (above the threshold), and enter an Invoice Date.
- Select Next Stage to advance to Verified.
- In the Verified stage, leave Line Items Confirmed set to No and try to advance.
- Verify that the Approval Status field is locked (read-only) because your amount exceeds 10000.
- Verify that Manager Approver is now a required field.
- Try to reach the Approved stage. You should see the error message on the Approval Status field confirming that line items must be confirmed first.
Test both branches
Create a second test invoice with Amount Due set to 5000 (below 10000). Verify that Approval Status is editable and Manager Approver is not required. This confirms the false branch of your Business Rules works correctly.
Your process layer is live. Every invoice record now follows a governed path with automatic field enforcement. Next, you will build an approval cloud flow that routes high-value invoices to managers with AI-generated context.
Create the Approval Cloud Flow
Your Business Process Flow now guides reviewers through each stage, and your Business Rules enforce field-level validation. But when an invoice hits the Approved stage, someone still needs to explicitly sign off on it.
In this step, you'll build a Power Automate cloud flow that watches for invoices entering the approval queue. The flow routes them through different approval paths based on their dollar amount, with AI-generated summaries giving approvers instant context.
In this step, get ready to:
- Create an automated cloud flow with conditional routing by invoice amount.
- Configure approval outcomes that update the Dataverse row automatically.
- Add AI-generated summaries so approvers see instant context before deciding.
Create the approval flow
You need a flow that automatically fires when an invoice's Approval Status changes to Pending. The flow will split into two paths: one for high-value invoices (over $10,000) that require manager sign-off, and one for standard invoices that any reviewer can handle.
- In the Power Apps maker portal, open your Invoice Exception Handler solution.
- Select New > Automation > Cloud flow > Automated.
- Enter Invoice Approval Flow as the flow name.
- In the trigger search field, type Microsoft Dataverse and select When a row is added, modified or deleted.
- Select Create.
What is this trigger?
The Dataverse connector's When a row is added, modified or deleted trigger monitors your table for changes. You configure it with a change type, table name, scope, and optional filter expression so the flow only fires under the exact conditions you specify.
- Configure the trigger with these settings:
- Set Change type to Modified.
- Set Table name to your invoice table.
- Set Scope to Organization.
- Expand Advanced parameters (or select Show advanced options in the classic designer).
- In the Select columns field, enter the unique name of your Approval Status column. This ensures the flow only fires when that specific column changes.
- In the Filter rows field, enter an OData expression that matches Approval Status equals Pending (for example, your choice column's integer value for Pending).
Finding your column's unique name
To find the unique name of your Approval Status column, go to your invoice table's Columns list in the maker portal. The unique name (also called logical name) appears in the column details. Use this exact string in the Select columns and Filter rows fields.
- Below the trigger, add a Condition action.
- Set the condition to: Amount Due is greater than 10000.
Now configure the approval action in each branch.
- In the If yes branch, add a Start and wait for an approval action with these settings:
- Set Approval type to Approve/Reject - Everyone must approve.
- Set Title to: type High-Value Invoice: then insert the Vendor Name and Amount Due dynamic content tokens separated by a dash.
- Set Assigned to to the Manager Approver dynamic content from the trigger.
- In the If no branch, add another Start and wait for an approval action with these settings:
- Set Approval type to Approve/Reject - First to respond.
- Set Title to: type Standard Invoice: then insert the Vendor Name and Amount Due dynamic content tokens separated by a dash.
- Set Assigned to to a default reviewer email address (your own email works for testing).
Why two different approval types?
Approve/Reject - Everyone must approve requires all assigned approvers to respond before the flow continues. This is the right fit for high-value invoices where multiple stakeholders must sign off.
Approve/Reject - First to respond lets the first person who responds move the process forward. Standard invoices don't need multiple layers of approval.
Handle approval outcomes
Each approval action produces an Outcome (Approve or Reject). You need to check that outcome and update the invoice record accordingly.
- After the approval action in the If yes branch, add a new Condition action.
- Set the condition to: Outcome (from dynamic content) is equal to Approve.
- In the If yes path of this condition, add an Update a row action (Microsoft Dataverse connector).
- Set Table name to your invoice table.
- Set Row ID to the record's unique ID from the trigger's dynamic content.
- Set Approval Status to Approved.
- In the If no path (rejection), add another Update a row action.
- Set Approval Status to Rejected.
- Set Reviewer Notes to the Comments dynamic content from the approval response. This captures the approver's reasoning on the record.
- Repeat the same outcome-handling pattern for the If no branch's approval action (the standard invoice branch):
- Add a Condition checking Outcome equals Approve.
- If approved: Update row with Approval Status set to Approved.
- If rejected: Update row with Approval Status set to Rejected and Reviewer Notes set to Comments.
What does this flow do so far?
When an invoice's Approval Status changes to Pending, the flow checks whether the Amount Due exceeds $10,000. High-value invoices go to the Manager Approver and require everyone to approve. Standard invoices go to a default reviewer where the first response wins.
After the approver responds, the flow writes the outcome back to Dataverse automatically. This closes the loop so the Business Process Flow can advance to the next stage.
- Save the flow by selecting Save in the command bar.
Add AI-generated summary to approval details
Approvers should not have to dig through raw data to make a decision. You'll add an AI Builder action before each approval that generates a quick summary, giving approvers instant context in the approval request.
- In the If yes branch, add a new action before the existing Start and wait for an approval action.
- Search for Run a prompt and select the AI Builder action.
- In the Prompt field, select New custom prompt from the dropdown.
What is AI Builder?
AI Builder is Power Platform's built-in AI layer. The Run a prompt action lets you send a natural-language instruction to a GPT model and get generated text back as a flow variable. You can use this output in any downstream action.
- In the prompt builder, write your instruction. Type the static text and use the / key or Add content button to insert dynamic inputs for your invoice fields:
- Static instruction: "Summarize this invoice for an approver in 2-3 sentences."
- Add a Text input for Vendor Name.
- Add a Text input for Amount Due.
- Add a Text input for Line Items.
- Add a Text input for Risk Classification (from Project 1).
- The full prompt should read like: "Summarize this invoice for an approver in 2-3 sentences. Vendor: [Vendor input], Amount: [Amount input], Line Items: [Line Items input], Risk Flag: [Risk input]."
- Select Test to verify the prompt produces a sensible summary, then select Save.
- Back in the flow, map each prompt input to the corresponding dynamic content from the trigger (Vendor Name, Amount Due, Line Items, Risk Classification).
- Open the Start and wait for an approval action in the same branch.
- In the Details field, insert the output text from the Run a prompt action. This is the AI-generated summary that approvers will see in their approval request.
- Repeat the same pattern for the If no branch (standard invoices):
- Add a Run a prompt action before the standard approval action.
- Select the same prompt you just created.
- Map the inputs to the trigger's dynamic content.
- Insert the prompt output into the approval action's Details field.
- Save the flow and turn it on by selecting Save, then confirming the flow is toggled On.
- Verify: In your solution's Cloud flows list, confirm Invoice Approval Flow appears with a status of On.
Don't see the Run a prompt action?
The Run a prompt action requires AI Builder capacity in your environment. If you cannot find it, confirm your environment has AI Builder credits allocated in the Power Platform admin center under Resources > Capacity > Add-ons.
help me find the Run a prompt action in my Power Automate flow
Your approval flow is live. When an invoice's status flips to Pending, it automatically routes through the right approval channel with an AI summary. Next up, you'll build the canvas app where finance reviewers actually work with these invoices day-to-day.
Build the Canvas App with UDFs
Your approval flow is active and ready to route invoices to the right people. Now you need a place for finance reviewers to actually see and act on those flagged invoices.
In this step, you will build a canvas app that serves as the reviewer dashboard. You will define reusable validation logic using User-Defined Functions (UDFs) and Named Formulas, then wire up a gallery with filtering, conditional formatting, and approve/reject actions.
In this step, get ready to:
- Create a canvas app with UDFs and Named Formulas for reusable logic.
- Build a gallery-based reviewer interface with conditional formatting.
- Implement state management with variables, collections, and filter buttons.
Create the canvas app and define UDFs
UDFs let you write a formula once and call it anywhere in your app. Named Formulas give you lazily-evaluated values that stay up-to-date automatically without cluttering App.OnStart.
- In the Power Apps maker portal, open your Invoice Exception Handler solution.
- Click New > App > Canvas app.
- Select Blank app with Tablet layout.
- Name the app Invoice Exception Handler App.
- Connect your Dataverse invoice table as a data source by clicking Data in the left panel, then + Add data, and selecting your invoice table.
Now define your UDFs and Named Formulas in the App.Formulas property.
- In the left Tree view, select the App object.
- In the property dropdown above the formula bar, select Formulas.
- Paste the following UDF definitions into the formula bar:
// User-Defined Functions
IsHighValue(amount: Number): Boolean = amount > 10000;
GetApprovalRoute(amount: Number): Text =
If(
amount > 50000, "CFO + Director",
If(amount > 10000, "Manager Required", "Standard Review")
);
FormatCurrency(amount: Number): Text = Text(amount, "$#,###.00");
ValidateForApproval(status: Text, lineItemsConfirmed: Boolean): Text =
If(
Not(lineItemsConfirmed), "Line items must be confirmed before approval",
If(status = "Approved", "Invoice already approved", "")
);
What do these UDFs do?
- IsHighValue returns true when an invoice exceeds the 10,000 threshold. You will use this for conditional formatting.
- GetApprovalRoute returns a text label describing who needs to approve based on the amount tier.
- FormatCurrency turns a number into a readable dollar string like $12,500.00.
- ValidateForApproval checks pre-conditions before an invoice can be approved, returning an error message or an empty string.
Why UDFs instead of repeating formulas?
Without UDFs, you would copy the same If(amount > 10000, ...) logic into every control that needs it. If the threshold changes, you would need to find and update every copy.
UDFs let you define the logic once and call it by name. Change the threshold in one place and every control updates automatically.
- Below your UDF definitions (still in the same Formulas property), add these Named Formulas:
// Named Formulas
PendingInvoices = Filter(Invoices, ApprovalStatus = "Pending");
TotalPendingValue = Sum(PendingInvoices, AmountDue);
HighValueCount = CountRows(Filter(PendingInvoices, AmountDue > 10000));
CurrentReviewerEmail = User().Email;
How are Named Formulas different from UDFs?
UDFs accept parameters and return a value based on those inputs. Named Formulas take no parameters. They are declarative expressions that Power Apps re-evaluates lazily whenever their underlying data changes.
Think of Named Formulas as live-updating calculated fields. PendingInvoices always reflects the current filtered state of your table without you needing to refresh it manually.
- Confirm that no errors appear in the formula bar. A red underline means a syntax or type issue.
Seeing red underlines in your formulas?
- Make sure your invoice table is connected as a data source. The Invoices reference in PendingInvoices must match your table's display name exactly.
- Check that ApprovalStatus and AmountDue match the column names in your Dataverse table. Column names are case-sensitive in Power Fx.
- Each formula definition must end with a semicolon.
Help me fix formula errors in my App.Formulas property.
Build the reviewer interface
With your formulas defined, you can now build the screen that finance reviewers will use. The gallery displays pending invoices, and each row calls your UDFs to show formatted amounts and routing labels.
- Select Screen1 in the Tree view.
- Click Insert in the top menu, then select Vertical gallery.
- With the gallery selected, set its Items property in the formula bar to:
PendingInvoices
Why start with PendingInvoices?
This Named Formula automatically filters your invoice table to show only records with Approval Status set to Pending. You will upgrade this to a dynamic Switch formula in the next substep when you add filter buttons.
Now configure the labels inside the gallery template to display invoice details using your UDFs.
- Inside the gallery template, select the first label (or insert a new one) and rename it to lblAmount using the Tree view.
- Set lblAmount's Text property to:
FormatCurrency(ThisItem.AmountDue)
- Set lblAmount's Color property to:
If(IsHighValue(ThisItem.AmountDue), Color.Red, Color.Black)
- Insert another label inside the gallery template and rename it to lblRoute.
- Set lblRoute's Text property to:
GetApprovalRoute(ThisItem.AmountDue)
What does the conditional coloring do?
Invoices above 10,000 appear in red text so reviewers can instantly spot high-value items. The IsHighValue UDF keeps the threshold logic in one place. If your organization changes the threshold, you update the UDF and every gallery row responds.
Next, add a summary label at the top of the screen so reviewers see totals at a glance.
- Click outside the gallery to deselect it, then click Insert > Text label.
- Position the label at the top of Screen1 and rename it to lblSummary.
- Set lblSummary's Text property to:
"Pending: " & CountRows(PendingInvoices) & " | Total: " & FormatCurrency(TotalPendingValue) & " | High Value: " & HighValueCount
What does this summary show?
This label displays three key metrics in a single line: the count of pending invoices, the total dollar value of pending items, and how many exceed the high-value threshold. It references both a UDF (FormatCurrency) and Named Formulas (PendingInvoices, TotalPendingValue, HighValueCount).
- Add an Edit form control (Insert > Edit form) and set its DataSource to your invoice table.
- Set the form's Item property to Gallery1.Selected so it shows detail for the selected invoice.
- Save the app by pressing Ctrl+S (Windows) or Cmd+S (macOS).
Your gallery should now show a list of pending invoices with dollar amounts in red for high-value items and routing text beside each row.
Gallery showing blank or no records?
- Check that you have at least one invoice record in your Dataverse table with Approval Status set to Pending. The PendingInvoices formula filters on that value.
- Make sure your data source name matches the table reference in your Named Formulas. If your table is named differently than Invoices, update the Named Formulas to match.
- Verify labels inside the gallery reference ThisItem, not Gallery1.Selected. Inside a gallery template, you must use ThisItem.
Help me fix my gallery not showing invoice data.
Implement state management with variables and collections
Your gallery works, but reviewers need a way to filter invoices and record their approve/reject actions. You will use Set() for single-value state, Collect() for tabular state, and filter buttons to control what the gallery shows.
- Select the App object in the Tree view.
- Select the OnStart property from the property dropdown.
- Paste the following initialization logic:
Set(varCurrentReviewer, User().Email);
Set(varFilterMode, "All");
Collect(colReviewHistory, Blank());
What does App.OnStart do?
- varCurrentReviewer stores the signed-in user's email so you can stamp who approved or rejected each invoice.
- varFilterMode starts at "All" and controls which invoices the gallery displays.
- colReviewHistory initializes an empty collection that records every approve/reject action during this session.
Now upgrade the gallery's Items property to respond to the filter mode variable.
- Select Gallery1 in the Tree view.
- Find the Items property (currently set to PendingInvoices).
- Replace it with this Switch formula:
Switch(
varFilterMode,
"High Value", Filter(PendingInvoices, AmountDue > 10000),
"Standard", Filter(PendingInvoices, AmountDue <= 10000),
PendingInvoices
)
How does this Switch formula work?
Switch evaluates varFilterMode and returns a different filtered dataset for each case. When the variable is "High Value", only invoices above 10,000 appear. When "Standard", only invoices at or below 10,000 appear. The default case (no match) returns all pending invoices.
Add three filter buttons that update varFilterMode.
- Click Insert > Button three times to add three buttons to Screen1.
- Rename them to btnFilterAll, btnFilterHighValue, and btnFilterStandard in the Tree view.
- Set btnFilterAll's Text to "All" and its OnSelect to:
Set(varFilterMode, "All")
- Set btnFilterHighValue's Text to "High Value" and its OnSelect to:
Set(varFilterMode, "High Value")
- Set btnFilterStandard's Text to "Standard" and its OnSelect to:
Set(varFilterMode, "Standard")
Finally, add approve and reject buttons so reviewers can act on invoices directly from the app.
- Insert two more buttons below the gallery or beside the detail form.
- Rename them to btnApprove and btnReject.
- Set btnApprove's Text to "Approve" and its OnSelect to:
Patch(Invoices, Gallery1.Selected, {ApprovalStatus: "Approved"});
Collect(
colReviewHistory,
{
Invoice: Gallery1.Selected.InvoiceName,
Action: "Approved",
Timestamp: Now(),
Reviewer: varCurrentReviewer
}
);
Notify("Invoice approved: " & Gallery1.Selected.InvoiceName, NotificationType.Success);
What does the approve button do?
- Patch writes the Approved status directly to the Dataverse row for the selected invoice.
- Collect adds an entry to colReviewHistory so you have an in-session audit trail of every action this reviewer takes.
- Notify shows a green success banner at the top of the app confirming the action.
- Set btnReject's Text to "Reject" and its OnSelect to:
Patch(Invoices, Gallery1.Selected, {ApprovalStatus: "Rejected"});
Collect(
colReviewHistory,
{
Invoice: Gallery1.Selected.InvoiceName,
Action: "Rejected",
Timestamp: Now(),
Reviewer: varCurrentReviewer
}
);
Notify("Invoice rejected: " & Gallery1.Selected.InvoiceName, NotificationType.Warning);
- Add a second blank screen (Insert > New screen > Blank). This will hold the Copilot Studio chat control in the next step.
- Save the app (Ctrl+S or Cmd+S).
- Publish the app by clicking Publish in the top right, then confirm by clicking Publish this version.
Click the play button (triangle icon in the top right) to preview your app. Select a pending invoice in the gallery, then click btnFilterHighValue to confirm the gallery filters down to only high-value items.
Approve button not updating the record?
- Make sure Gallery1.Selected references a valid record. If no item is selected in the gallery, Patch has nothing to write to.
- Check that the column name ApprovalStatus matches your Dataverse column's schema name exactly. Open the table in the maker portal to confirm.
- If you see a permissions error, verify your security role grants Write access to the invoice table.
Help me debug my approve/reject buttons not writing to Dataverse.
✔️ Awesome, I've got everything!
Great. Double check you have saved and published your app.
ⓧ I'd like to double check the full code
Here is the complete code for each property in your canvas app. Compare yours against these:
// User-Defined Functions
IsHighValue(amount: Number): Boolean = amount > 10000;
GetApprovalRoute(amount: Number): Text =
If(
amount > 50000, "CFO + Director",
If(amount > 10000, "Manager Required", "Standard Review")
);
FormatCurrency(amount: Number): Text = Text(amount, "$#,###.00");
ValidateForApproval(status: Text, lineItemsConfirmed: Boolean): Text =
If(
Not(lineItemsConfirmed), "Line items must be confirmed before approval",
If(status = "Approved", "Invoice already approved", "")
);
// Named Formulas
PendingInvoices = Filter(Invoices, ApprovalStatus = "Pending");
TotalPendingValue = Sum(PendingInvoices, AmountDue);
HighValueCount = CountRows(Filter(PendingInvoices, AmountDue > 10000));
CurrentReviewerEmail = User().Email;
Set(varCurrentReviewer, User().Email);
Set(varFilterMode, "All");
Collect(colReviewHistory, Blank());
Switch(
varFilterMode,
"High Value", Filter(PendingInvoices, AmountDue > 10000),
"Standard", Filter(PendingInvoices, AmountDue <= 10000),
PendingInvoices
)
FormatCurrency(ThisItem.AmountDue)
If(IsHighValue(ThisItem.AmountDue), Color.Red, Color.Black)
GetApprovalRoute(ThisItem.AmountDue)
"Pending: " & CountRows(PendingInvoices) & " | Total: " & FormatCurrency(TotalPendingValue) & " | High Value: " & HighValueCount
Patch(Invoices, Gallery1.Selected, {ApprovalStatus: "Approved"});
Collect(
colReviewHistory,
{
Invoice: Gallery1.Selected.InvoiceName,
Action: "Approved",
Timestamp: Now(),
Reviewer: varCurrentReviewer
}
);
Notify("Invoice approved: " & Gallery1.Selected.InvoiceName, NotificationType.Success);
Patch(Invoices, Gallery1.Selected, {ApprovalStatus: "Rejected"});
Collect(
colReviewHistory,
{
Invoice: Gallery1.Selected.InvoiceName,
Action: "Rejected",
Timestamp: Now(),
Reviewer: varCurrentReviewer
}
);
Notify("Invoice rejected: " & Gallery1.Selected.InvoiceName, NotificationType.Warning);
Set(varFilterMode, "All")
Set(varFilterMode, "High Value")
Set(varFilterMode, "Standard")
Your canvas app is published and ready for reviewers. Next up, you will create a Copilot Studio agent and embed it directly into this app so reviewers can ask natural-language questions about invoice history.
Embed the Copilot Studio Agent via PCF
Your canvas app gives reviewers a dashboard for filtering and approving invoices. The missing piece is letting them ask natural-language questions about invoice history without leaving the app.
In this step, you will create a Copilot Studio agent connected to your Dataverse invoice data, register it in Microsoft Entra ID, and embed it into your canvas app using the ChatControl PCF component.
In this step, get ready to:
- Create and publish a Copilot Studio agent with Dataverse knowledge.
- Register an Azure app for PCF authentication.
- Import the ChatControl PCF solution and embed the agent in your canvas app.
Create and publish the Copilot Studio agent
Copilot Studio agents use knowledge sources to ground their answers in real data. You will connect the agent directly to your Dataverse invoice table so it can answer questions about vendors, amounts, and approval history.
- Open a new browser tab and navigate to copilotstudio.microsoft.com.
- Sign in with the same account you use for Power Platform.
- Select Create in the left navigation, then select New agent.
- Enter the name Invoice Assistant and provide a short description such as "Answers questions about invoice data for finance reviewers."
- Select Create to generate the agent.
Once the agent is created, you need to connect it to your invoice data.
- Select Add knowledge from either the Overview or Knowledge page.
- Select Dataverse as the knowledge source type.
- Search for and select your invoice table from Project 1.
- Select Add to agent to finish adding the knowledge source.
What does adding Dataverse knowledge do?
When you add a Dataverse table as knowledge, Copilot Studio uses a retrieval-augmented generative technique to search your table data at runtime. The agent can then answer questions like "How much have we spent with Acme Corp this quarter?" using your actual invoice records.
Dataverse knowledge requires the agent's authentication to be set to "Authenticate with Microsoft." You will configure this next.
Now test and configure authentication before publishing.
- In the agent's test panel, type a question like "Show me all rejected invoices" to verify it returns data from your table.
- Select Settings (gear icon) in the top-right area of Copilot Studio.
- Select Security, then Authentication.
- Select Authenticate with Microsoft and save.
- Select Publish in the top-right corner to make the agent available.
Finally, note the agent's Schema name. You will need this identifier when configuring the PCF control.
- In the left navigation, select Channels.
- Select Web app.
- Copy the agent's Schema name (format: cr###_invoiceAssistant). Save this value. You will use it later.
Why PCF instead of the built-in Copilot control?
Starting February 2, 2026, adding the built-in Copilot control to new canvas apps was discontinued. The ChatControl PCF component from Microsoft's CopilotStudioSamples repository is the recommended replacement.
This approach also teaches you a transferable skill. PCF components can embed any custom UI into canvas apps, not just chat interfaces.
Agent not returning data from your table?
Ensure Dataverse search is enabled in your environment. Ask your admin to turn it on in the Power Platform admin center if it is not active.
Also verify that authentication is set to "Authenticate with Microsoft." The other two options (No authentication and Authenticate manually) are not supported for Dataverse knowledge sources.
Why can't my Copilot Studio agent access Dataverse invoice data?
Create the Azure app registration
The ChatControl PCF component needs an app registration in Microsoft Entra ID to authenticate users. This uses a public client (native) registration with no client secret.
- Open a new browser tab and navigate to the Azure Portal at portal.azure.com.
- In the left navigation, select Microsoft Entra ID.
- Select App registrations in the side menu.
- Select + New registration.
- Enter the name Invoice Assistant PCF.
- Under Supported account types, select Accounts in this organizational directory only.
- Select Register.
After registration, configure the platform and redirect URIs.
- On the app's Overview page, select Authentication in the left menu.
- Select + Add a platform.
- Select Mobile and desktop applications.
- Add the following redirect URIs one at a time:
https://runtime-app.powerplatform.com
https://runtime-app.powerplatform.com/control
https://apps.powerapps.com/
- Add one more redirect URI for your specific environment instance URL (e.g., https://org12345.crm.dynamics.com/). Find this URL in the Power Platform admin center under your environment's details.
- Select Save at the bottom of the Authentication page.
Why no client secret?
The ChatControl PCF uses a public client (native) flow. This means authentication happens directly in the user's browser session. No server-side secret is needed because the user's own Microsoft Entra token is used to authenticate with the Copilot Studio agent.
Now note the two IDs you will need for the PCF control configuration.
- Return to the app registration's Overview page.
- Copy the Application (client) ID value and save it.
- Copy the Directory (tenant) ID value and save it.
You should now have three values saved: the agent Schema name, the Application (client) ID, and the Directory (tenant) ID.
Can't find Microsoft Entra ID in Azure Portal?
If you do not see Microsoft Entra ID in the left navigation, use the search bar at the top of the Azure Portal and type "Microsoft Entra ID" to navigate directly.
Help me find Microsoft Entra ID in the Azure Portal to create my app registration.
Import the PCF solution and embed in your canvas app
With the agent published and the app registration ready, you can now import the ChatControl PCF component and wire it into Screen2 of your canvas app.
- Download the ChatControl PCF solution .zip file from the Microsoft CopilotStudioSamples repository on GitHub:
- github.com/microsoft/CopilotStudioSamples (navigate to the ui/embed/pcf-canvas-app path and download the solution zip file).
Now import the solution into your Power Platform environment.
- Switch back to the Power Apps maker portal.
- Select Solutions in the left navigation.
- Select Import solution on the command bar.
- Select Browse, locate the ChatControl .zip file you downloaded, and select it.
- Select Next, then select Import.
- Wait for the import to complete. You will see a success notification on the Solutions page.
Solution import failed?
Ensure that "Allow publishing of canvas apps with code components" is enabled in your environment. This was configured in Step 1 of this project. If you skipped it, go to Power Platform admin center, select your environment, then navigate to Settings > Product > Features and turn the setting on.
Help me troubleshoot the ChatControl PCF solution import failure.
Now open your canvas app and add the ChatControl component.
- In Solutions, open your Invoice Exception Handler solution.
- Select your canvas app Invoice Exception Handler App and choose Edit to open it in Power Apps Studio.
- Navigate to Screen2 in the tree view on the left.
- On the left pane, select the + (Add) button, then select Get more components.
- Select the Code tab, find ChatControl in the list, and select Import.
- On the left pane, select +, expand Code components, and select ChatControl to add it to Screen2.
- Resize the ChatControl to fill Screen2.
Configure the ChatControl properties using the values you saved earlier.
- Select the ChatControl on Screen2 and open the Properties tab on the right pane.
- Set the following property values:
agentTitle: "Invoice Assistant"
appClientId: "[[APP_CLIENT_ID="your-azure-app-client-id-here"]]"
tenantId: "[[TENANT_ID="your-azure-tenant-id-here"]]"
environmentId: "[[ENVIRONMENT_ID="your-power-platform-environment-id-here"]]"
agentIdentifier: "[[AGENT_IDENTIFIER="your-agent-schema-name-here"]]"
username: User().Email
Where do these values come from?
- agentTitle is the display name shown in the chat header.
- appClientId is the Application (client) ID from your Azure app registration.
- tenantId is the Directory (tenant) ID from the same registration.
- environmentId is your Power Platform environment ID, found in the admin center under your environment's details.
- agentIdentifier is the agent Schema name you noted from Copilot Studio (format: cr###_invoiceAssistant).
- username passes the current user's email to the agent for personalized context.
- Select File > Save to save the app.
- Select Publish to make the updated app available.
Test the embedded agent in the published app.
- Select Play (preview icon) or open the published app from the Apps list.
- Navigate to Screen2 where the ChatControl is displayed.
- Type a question such as "How much have we spent with our top vendor this quarter?" and verify the agent responds with data from your invoice table.
ChatControl not loading or showing authentication errors?
Double-check that all four redirect URIs are entered in your Azure app registration, including your environment instance URL.
Verify the agent's authentication is set to "Authenticate with Microsoft" in Copilot Studio. Also confirm you published the agent after changing the authentication setting.
Help me debug the ChatControl PCF component in my canvas app.
✔️ Awesome, I've got everything!
Great. Double-check you have saved and published your canvas app with the ChatControl configured on Screen2.
ⓧ I'd like to double check the full code
Here are the ChatControl properties that should be set on Screen2:
agentTitle: "Invoice Assistant"
appClientId: "your-azure-app-client-id-here"
tenantId: "your-azure-tenant-id-here"
environmentId: "your-power-platform-environment-id-here"
agentIdentifier: "your-agent-schema-name-here"
username: User().Email
Secret mission
Concurrency Control and Error Handling
Your approval flow works, but what happens when two invoices trigger simultaneously? Or when an approver goes on vacation and never responds? Add production-grade resilience to your flow with concurrency control and timeout escalation.
Clean Up Your Resources
Clean Up Your Resources
All components in this project run within your existing Power Platform premium license. The approval flow only triggers when invoice records change, and the Copilot Studio agent consumes messages from your existing message capacity.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Solution "Invoice Exception Handler" (contains Business Process Flow, three Business Rules, cloud flow, and canvas app).
- Copilot Studio agent "Invoice Assistant".
- Azure app registration "Invoice Assistant PCF" (in Microsoft Entra ID).
- ChatControl PCF solution (imported separately).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the project further.
- All components run within your existing premium license at no additional cost.
- The approval flow only triggers when an invoice record's Approval Status changes to Pending. It will not run on its own.
- Monitor your Copilot Studio message usage in the Power Platform admin center to stay within your capacity.
Pause - I'll come back to this later
Shut down running processes to free up flow run capacity while keeping your configuration intact.
- Open your approval flow in Power Automate, select the flow, then select Turn off from the command bar. This prevents any new runs while preserving your flow configuration.
- The Business Process Flow and Business Rules remain active but are passive. They only apply when a user opens a form or modifies a record.
- Your canvas app and Copilot Studio agent stay published but consume no resources unless someone opens them.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to fully clean up.
- Delete the "Invoice Exception Handler" solution to remove the BPF, Business Rules, cloud flow, and canvas app at once. In the Power Apps maker portal, select Solutions in the left navigation. Select (do not open) the Invoice Exception Handler solution, then select Delete on the command bar. Confirm when prompted.
- Delete the ChatControl PCF solution. In the same Solutions list, select the imported ChatControl solution and select Delete on the command bar.
- Delete the Copilot Studio agent. Go to copilotstudio.microsoft.com, open the Agents page, and select Invoice Assistant. On the top menu bar, select the three dots (...) next to Settings, then select Delete. Enter the agent name to confirm and select Delete agent.
- Delete the Azure app registration. Go to the Azure Portal, navigate to Microsoft Entra ID > App registrations. Select Invoice Assistant PCF. On the Overview page, select Delete and confirm when prompted.
Nice Work!
Nice Work!
You just built a complete business process layer on top of your invoice data. From raw Dataverse records to governed approvals and an AI-powered reviewer experience, your finance workflow is now production-ready.
You've learned how to:
- Configure a 4-stage Business Process Flow with stage-gating and Business Rules that lock fields, show error messages, and enforce requirement levels based on invoice amount thresholds.
- Build an approval cloud flow with conditional routing (Everyone must approve for high-value, First to respond for standard) and AI-generated summaries providing approvers with instant context.
- Create a canvas app with User-Defined Functions, named formulas, and variables/collections for state management, plus an embedded Copilot Studio agent via the ChatControl PCF component.
- Secret Mission: Added concurrency control (Degree of Parallelism = 1) and Run After timeout handling with escalation branching for production-grade flow resilience.
Ready to quiz yourself?