Sam's Dynamics, Power Platform & AI Blog

Monday, 14 September 2026

From Script to Agent: Wiring a Copilot Studio Action into a Dynamics 365 Ribbon Button

We've spent this series in the form: events, field logic, lookups, subgrids, and calling the Web API. This last post is where it connects to the other thing you're probably building right now — a JavaScript-driven ribbon button that hands off to a Copilot Studio agent and brings the answer back onto the form.

The Pattern: Ribbon Button → JavaScript → Agent

There's no direct client-side SDK call from form JavaScript into a Copilot Studio agent. The reliable pattern is: a ribbon button runs a JavaScript function, that function calls an HTTP-triggered Power Automate flow (or an Azure Function) that invokes the agent's topic, and the response comes back as JSON that your script uses to update the form or show a notification.

Step 1: Add the Ribbon Button

Using the modern command bar designer (Power Apps maker portal → table → Forms → Command bar) or Ribbon Workbench, add a button with a JavaScript action pointing to a function in a form library, e.g. new_/ribbon/agentActions.js, function summarizeCaseWithAgent. Pass PrimaryControl as a parameter so your function gets the form context.

Step 2: Expose the Agent as a Callable Endpoint

In Copilot Studio, publish the topic you want to trigger, then wrap it with a Power Automate flow that starts with an HTTP request trigger, calls the agent (or the underlying logic the agent uses), and responds with the result. This gives you a stable HTTPS URL your form script can call — the same pattern used for any external integration, just fronting an agent instead of a plain API.

Step 3: Call It from the Button's JavaScript

async function summarizeCaseWithAgent(primaryControl) {
    var formContext = primaryControl;
        formContext.ui.setFormNotification("Asking the agent for a summary...", "INFO", "agent_call");
        
            var caseId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                var description = formContext.getAttribute("description").getValue();
                
                    try {
                            var response = await fetch("https://your-flow-endpoint.example.com/trigger", {
                                        method: "POST",
                                                    headers: { "Content-Type": "application/json" },
                                                                body: JSON.stringify({
                                                                                caseId: caseId,
                                                                                                description: description
                                                                                                            })
                                                                                                                    });
                                                                                                                    
                                                                                                                            if (!response.ok) {
                                                                                                                                        throw new Error("Flow returned " + response.status);
                                                                                                                                                }
                                                                                                                                                
                                                                                                                                                        var result = await response.json();
                                                                                                                                                                formContext.ui.clearFormNotification("agent_call");
                                                                                                                                                                        formContext.ui.setFormNotification(result.summary, "INFO", "agent_summary");
                                                                                                                                                                            } catch (error) {
                                                                                                                                                                                    formContext.ui.clearFormNotification("agent_call");
                                                                                                                                                                                            formContext.ui.setFormNotification("Couldn't reach the agent right now.", "ERROR", "agent_error");
                                                                                                                                                                                                    console.error(error);
                                                                                                                                                                                                        }
                                                                                                                                                                                                        }

A Practical Example: "Summarize This Case" Button

The function above is a working version of this: it reads the case description, calls the flow, and shows the agent's summary as a form notification instead of navigating anywhere. You can just as easily write the result into a field, or open it in a side panel — whatever fits the workflow.

A Word on Security

The agent's flow runs under its own connection identity, not the signed-in user's — so it can potentially see more (or less) than the user calling it. That's a topic on its own, and worth checking against your Dataverse security roles before you wire up anything that touches sensitive data.

Common Pitfalls

  • No loading state. Agent calls can take a few seconds; always show a notification (or disable the button) while waiting, so users don't click twice.
  • No timeout handling. Set a reasonable timeout on the fetch call and handle it gracefully — an agent call that hangs shouldn't hang the form.
  • Trusting the response shape blindly. Validate the JSON structure before reading fields off it; an agent or flow change can silently alter the response.
  • Hardcoding the endpoint URL. Store it in an environment variable or a configuration record instead, so it survives a move between environments.

Wrapping Up the Series

Five posts in, the throughline hasn't changed: form scripting is still the layer that makes everything else — Business Rules, Power Automate, Copilot agents — actually usable inside the form users work in every day. Knowing both the fundamentals and where they connect to AI is what keeps a CRM developer's skill set current without starting over. Thanks for following along.

No comments:

Post a Comment

Packaging and Deploying Web Resources: A Practical ALM Workflow for Dynamics 365 JavaScript

You've written the script. You've unit-tested the logic and stepped through it in dev tools. It works perfectly on your form. Now co...