Finance and Operations apps allow the development of two types of custom plugins: Client Plugins and AI Plugins. Client Plugins are context-bound and limited in scope, while AI Plugins can be used in any context. AI Plugins are headless operations; they don’t require a specific context in the Finance and Operations client. AI Plugins can be used in copilots across Microsoft products.In this post, we will develop a custom AI plugin to extend the chat experience. We will use the example of the vendor/supplier balance. The standard copilot chat experience cannot provide this information. What I want is to know the vendor balance without navigating through the application.Prerequisites- You must have a unified developer environment.
- You must be on version 10.0.40+PQ1 (at a minimum).
- The following solutions must be available/installed in your Dataverse environment:
Copilot for Finance and Operations Apps- Copilot for Finance and Operations Generation Solution
- Copilot for Finance and Operations Anchor Solution
- Finance and Operations Virtual Entity
- The (Preview) Custom API Generation feature must be enabled in Feature Management. (Microsoft may enable this preview feature by default in the future).
Once you have all the prerequisites, you can proceed for the development.
In the Dynamics, you will create one class (at least) that would define your AI plugin. The X++ class would consist of following.- Input/Request Parameter (to record the user input/question’s data). You can have one or more input parameters.
- Output/Response Parameter/properties (to provide the answer to user). You can have one or more output parameters.
- Operation method (to define the code that runs when the operation is invoked. This code is the business logic that defines the action that is run for the AI operation.)
The new class must implement the ICustomAPI class
Example:
public final class CustomCopilotAIPlugInVendBalanceSCV01 implements ICustomAPI
You must decorate the new class with the AIPluginOperationAttribute attribute to define it as an AI operation. Classes that are decorated with this attribute are registered in the Plugin Registration Service in Dataverse during synchronization of Dataverse custom APIs. As a result, a record for the plugin operation is created in the AIPlugin and AIPluginOperation tables in Dataverse.
Example:
[AIPluginOperationAttribute]
public final class CustomCopilotAIPlugInVendBalanceSCV01 implements ICustomAPI
You must decorate the method with the DataContract attribute to define it as a data contract. This is for supporting of Complex data types.
Example:
[AIPluginOperationAttribute]
[DataContract]
public final class CustomCopilotAIPlugInVendBalanceSCV01 implements ICustomAPI
You must decorate your class with the CustomAPIAttribute. This attribute takes two parameters.
CustomAPIName: Type String; The natural language name of the action (your plugin)
CustomAPIDescription: Type String; A natural language definition of what the action(your AI plugin) does
Your class decoration would look like this.
Example:
[AIPluginOperationAttribute]
[DataContract]
[CustomAPI('inquire Vendor Balance','Calcualte the vendor balance for the provided vendor and return the balance.')]
public final class CustomCopilotAIPlugInVendBalanceSCV01 implements ICustomAPI
Now as we have class ready, we can implement the three needed components in the class.
- Request parameters (Input Parameters):
These are used to collect user’s input.
You must use CustomAPIRequestParameter to define a request parameter.
CustomAPIRequestParameter would consider two parameters. First is the natural language description of the parameter, second is Boolean. The second parameter defines whether this input is mandatory or not. Finally the parameter must be decorated as DataMember.
[CustomAPIRequestParameter('vendor account number',true),DataMember('accountnumber')]
public VendAccount parmVendAccount(VendAccount _vendAccount=varVendAccount)
{
varVendAccount = _vendAccount;
return varVendAccount;
}
- Response Parameters (Output Paremters):
This parameter provides the output for your custom API plugin. You must use CustomAPIResponseProperty to define q response parameter.
The CustomAPIResponseProperty takes one parameter i.e. The natural language description of the response property.
[CustomAPIResponseProperty('vendor account balance'),DataMember('balance')]
public AmountCur parmBalance(AmountCur _amountCur=varVendBalance)
{
varVendBalance = _amountCur;
return varVendBalance;
}
- Operations (Invoke Business Logic):
Use the run method of the ICustomAPI interface to define the code that runs when the operation is invoked. This code is the business logic that defines the action that is run for the AI operation.
public void run(Args _args)
{
if(this.parmVendAccount())
{
vendTable = vendTable::find(this.parmVendAccount());
if(vendTable)
{
this.parmBalance(VendTable.balanceAllCurrency());
}
}
}
Our business logic is ready. Now we need to deploy it, before we can use it.
#1, we must define the AI Plugin security. Our AI plugin is an X++ class. Every such class mut be accompanied by a security role.
You must assign each plugin operation to a security role that grants user access to perform the operation from Copilot. When finance and operations apps synchronize the AIPlugin and AIPluginOperation records in Dataverse, the records are grouped based on the security role that the operation is assigned to. The operation is generated in Dataverse only if it's assigned to a security role.
For the X++ class, create an action menu item. Add the action menu item to a security privilege. Add the privilege to a security duty. Add the security duty to a security role. Once you have security role, your security for the AI Plugin is ready.
#2, Deploy your X++ code (Class and security objects) to your Dynamics environments.
#3, Flush your Application Object Data by opening following URL
https://<environment>.operations.dynamics.com/?cmp=usmf&mi=SysClassRunner&cls=SysFlushAOD
#4, Open the Synchronize Dataverse Custom APIs page (System administration > Setup > Synchronize Dataverse Custom APIs)
Ensure that on the list page your class is included in the grid. Select the Synchronize action/button.
The synchronization process synchronizes all listed classes with Dataverse and adds them to the Dynamics 365 ERP Virtual Entities solution. For each class that also contains the [AIPluginOperationAttribute] attribute, a record for the AI plugin is created in the same solution. An AIPlugin record is created for each security role that is configured in finance and operations apps. This record contains an assigned class that has the [AIPluginOperationAttribute] attribute. The associated AIPluginOperaton records are linked to the plugin.

Now is the time to expand/extend Dynamics 365 ERP copilot chat experience. We have defined a custom AI plugin using X++ class. We will add this AI plugin as an action.
- Open the Copilot studio (https://copilotstudio.microsoft.com/)
- Make sure you are connected to the correct environment.
- As you have many copilots, open Copilot for finance and operations apps
- On the Actions page, select Add an action
- In the Search field, search for the name of your AI plugin operation. Select your plugin.
- Follow the steps in the wizard. Select the inputs and outputs from your custom API. Select Finish
Adding custom action (AI plugin)
Our last step is to Configure the copilot to invoke the action. This last step depends upon whether you have enabled Generative AI Orchestration or not. If you are on classical mode, you will need to use topics, as I am doing here.
- Open the copilot studio and Copilot for Finance and Operations apps.
- On the Topics tab, select Add a topic > From blank. On the Trigger node, edit the trigger phrases to provide the types of user prompts that should trigger the action.
- Add a Plugin action node to the topic for your action:
- Select Add node (+). Choose Add an action and choose your custom action, using search.
Link Topic with the custom action (plugin)
That’s it, our custom AI copilot is ready for testing.
As you can see, using X++, we can extend the copilot for any business logic. It can interact with your data without the data being stored anywhere outside of Dynamics. The storage cost in Dataverse is high, so it is best to avoid saving data there. Direct interaction with data from a single source is cheaper than duplicating it across multiple data sources.