
Default Copilot responses in Power Apps are text. Useful for quick answers, useless when the user actually needs a card, a form, or a chart. This walkthrough shows how to build copilot custom UI widgets power apps style, so the chat renders a real component when a specific question is asked, and falls back to text otherwise. Credit to Matthew Devaney’s writeup which is the spine I am building on here.
End state: a canvas app with a Copilot chat. Ask it about an order, and a card component appears in the conversation with the order details. Ask anything else and you get normal text.
Step 1: Add the Copilot control and a component to your canvas app
In your canvas app, insert the Copilot control from the Insert panel. Place it on the right side of the screen. Keep the default width, you will want the space.
Now create the component that will act as your widget. Go to the Components tab in the tree view, create a new component called OrderCardWidget. Add an input property called OrderPayload of type Record. Inside the component, drop a container with a few labels for order ID, customer name, status, and total. Bind each label to OrderCardWidget.OrderPayload.OrderId and so on.
Add the component to the screen. Set its Visible property to false for now. You will flip this on from the Copilot response in Step 3.
Step 2: Configure the Copilot topic to return a structured response
Open the Copilot studio agent connected to this app. Create a new topic called Show Order Details. Add trigger phrases like show me order, display order, open order card.
Add a question node asking for the order ID. Then add a tool call to a Power Automate flow that returns the order as structured JSON. The flow does a Dataverse or SharePoint lookup and returns a response with this shape:
{ "widget": "OrderCard", "payload": { "orderId": "...", "customer": "...", "status": "...", "total": 0 } }
The widget field is the trigger phrase for the UI layer. The payload field is what binds into the component. Do not skip the typed schema on the flow response. If you return a generic object, the Copilot control hands you a string and you spend the next hour wondering why parsing fails. I wrote about that exact failure mode in my custom connector walkthrough.
Finish the topic with a Message node that returns the JSON as the final response. Microsoft’s docs on Copilot in canvas apps have the current control reference if your version differs.
Step 3: Bind the component to the Copilot response payload
Back in the canvas app, select the Copilot control. The control exposes a property called OnResponse that fires every time the agent sends a message. This is where you parse the JSON and route it.
Set OnResponse to:
Set(varLastResponse, ParseJSON(Self.LastMessage)); Set(varWidgetType, Text(varLastResponse.widget)); Set(varOrderPayload, varLastResponse.payload)
Now set the component’s Visible property to:
varWidgetType = "OrderCard"
And set its OrderPayload input to:
{ OrderId: Text(varOrderPayload.orderId), Customer: Text(varOrderPayload.customer), Status: Text(varOrderPayload.status), Total: Value(varOrderPayload.total) }
The widget now renders only when the agent says it should, and only with the data the agent returned. If you want to support multiple widgets later, swap the boolean for a switch on varWidgetType and toggle different components. If you are thinking about when that kind of routing logic warrants a dedicated agent per task, single agent vs multi-agent is worth reading before you scale this pattern.
Step 4: Test the widget end to end and handle the fallback
Run the app. Open the Copilot chat. Type show me order 1042. The topic should trigger, the flow should fire, and the card should appear with the right data.
Now test the fallback. Ask something unrelated like what is the weather. The card must disappear. If varWidgetType is not reset between turns, the old widget hangs around showing stale data. Add a default branch to OnResponse:
If(IsBlank(varLastResponse.widget), Set(varWidgetType, ""))
Also test a malformed response. Force the flow to return invalid JSON once and confirm the app does not crash. ParseJSON returns blank on failure, your visibility check handles it, the chat continues as text. That is the behavior you want.
Final state and one pitfall to watch for
You now have a Copilot chat that renders a real Power Apps component when the topic returns the right payload, and text the rest of the time. Same pattern works for forms, charts, anything you can build as a component. If you want the agent behind this to pull from richer knowledge sources or close feedback loops on responses, the Dataverse agent data platform new features are worth wiring up alongside this.
The pitfall: the Copilot control reads the raw message text. If your topic adds any conversational wrapper around the JSON, like Sure, here is your order: {...}, ParseJSON fails silently and the widget never appears. Return pure JSON from the final Message node, nothing else. That one rule will save you an afternoon of debugging. More notes on this kind of integration thinking on my LinkedIn.
Frequently Asked Questions
How do I build copilot custom UI widgets in Power Apps?
You add a Copilot control to your canvas app, create a component to act as the widget, and configure a Copilot Studio topic to return a structured JSON response that includes a widget identifier and a data payload. The canvas app then reads the response, checks the widget field, and toggles the component visible while binding the payload to its input properties.
Why does my Copilot control return a string instead of a parsed object in Power Apps?
This usually happens when the Power Automate flow response does not have a typed schema defined. Without a typed schema, the Copilot control treats the response as plain text, which causes JSON parsing to fail. Always define the response schema explicitly in your flow to ensure a structured object is passed back.
When should I use a custom widget instead of a default text response in Copilot?
Use a custom widget when the user needs to view or interact with structured data, such as order details, forms, or charts, rather than a plain text answer. Text responses work for simple questions, but components give users a far more usable experience when the data has multiple fields or requires visual formatting.
How do I connect a Power Automate flow to a Copilot Studio topic?
Inside your Copilot Studio topic, add a tool call node and select your Power Automate flow as the action. The flow handles the data lookup and returns a structured JSON response back to the topic. Make sure the flow output schema is typed so the Copilot control can pass the data correctly to your canvas app.
This post was inspired by How To Create Copilot Custom UI Widgets In Power Apps via Matthew Devaney (Power Apps).








