5.5 Custom API Messages & Plug-ins That Implement a Custom API
Key Takeaways
- A Custom API defines a brand-new, intentionally invoked SDK message with its own name and a strongly typed request/response contract, unlike a plug-in step that reacts to an existing message.
- Binding Type controls whether a Custom API is unbound (Global), bound to a single record (Entity), or bound to a collection (Entity Collection); Is Function marks it as a read-only, GET-callable operation versus a data-changing POST action.
- The Plugin Type set on the Custom API record automatically registers the implementing plug-in against the new message -- no separate step registration is needed.
- The implementing plug-in reads each declared request parameter from InputParameters and writes each declared response property to OutputParameters by name.
- Custom APIs are Microsoft's recommended, lightweight replacement for legacy Custom Process Actions, which depend on the workflow engine.
Every plug-in covered so far has hooked into a message Dataverse already defines -- Create, Update, Delete. A Custom API lets you define a message of your own: a new, strongly-typed operation with its own name, its own request parameters, and its own response, callable through the Web API or the Organization service exactly like a built-in message, without writing a classic workflow-based custom action.
What a Custom API Is
A Custom API is configured declaratively, as data -- a Custom API record plus its Custom API Request Parameter and Custom API Response Property child records -- typically authored in a solution through the maker portal's Custom API designer rather than hand-written SDK metadata calls. Key properties on the Custom API record:
| Property | Meaning |
|---|---|
| Unique Name | The message name callers use to invoke it |
| Binding Type | Global (unbound, not tied to any table), Entity (bound to a single record of a specific table), or Entity Collection (bound to a query or collection of a table) |
| Bound Entity Logical Name | Required when Binding Type is Entity or Entity Collection |
| Is Function | true = a read-only, side-effect-free operation, invoked with HTTP GET, like a query; false = an action that can change data, invoked with HTTP POST |
| Plugin Type | The IPlugin class that implements the API's logic |
| Allowed Custom Processing Step Type | Whether other plugins may additionally register pre/post-operation steps around this custom message (None, Async only, or Sync and Async) |
Request and response parameters each declare a Name and a Type drawn from the standard SDK type set -- String, Integer, Boolean, DateTime, Decimal, Float, Money, Entity, EntityCollection, EntityReference, Picklist, StringArray, Guid -- giving the message a real, discoverable contract rather than a loosely typed bag of values.
Calling a Custom API
Once published, a Custom API is callable exactly like a built-in message:
- Web API:
POST https://org.crm.dynamics.com/api/data/v9.2/new_CalculateShippingCostfor an unbound (Global) action, or.../accounts(id)/Microsoft.Dynamics.CRM.new_ApproveAccountfor a bound one. A function, bound or unbound, is called withGETinstead. - Organization service: build an
OrganizationRequest("new_CalculateShippingCost"), populate itsParameterscollection with the declared request parameters, and callservice.Execute(request)-- the same pattern used for any SDK message.
The Implementing Plug-in
Unlike a normal step, you don't separately register the plugin type against the message in the Plug-in Registration Tool -- publishing the Custom API record with its Plugin Type set does that automatically. Inside the plugin, the pattern mirrors any other Execute method, but the developer reads each declared request parameter from InputParameters by name and writes each declared response property to OutputParameters by name:
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context =
(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var accountId = (Guid)context.InputParameters["AccountId"];
var orderTotal = ((Money)context.InputParameters["OrderTotal"]).Value;
decimal shippingCost = CalculateShipping(orderTotal);
context.OutputParameters["ShippingCost"] = new Money(shippingCost);
}
Because the request and response shape is declared on the Custom API record itself, both the Web API's $metadata and SDK tooling can describe the operation's contract to callers automatically -- a JavaScript form script (4.3) or a Power Automate flow can discover and call it like any native message.
Custom API vs. Custom Process Action vs. a Plain Plug-in Step
Two comparisons the exam likes to draw:
Custom API vs. classic Custom Process Action. A Custom Process Action, sometimes called a "classic Action," is defined through the legacy process/workflow designer, stored as a workflow definition, and executed through the workflow engine -- heavier weight, and largely a legacy pattern Microsoft has superseded. A Custom API is code-first, lightweight, natively exposed through the Web API without a workflow-engine dependency, and is the recommended approach for new development.
Custom API vs. a plug-in on a standard message. A plug-in step registered on Update subscribes to something that already happens as a side effect of ordinary data entry -- it can't be invoked deliberately on its own. A Custom API instead creates a brand-new, intentionally invoked operation with its own name and contract: use one whenever the requirement is "let a caller explicitly ask the platform to do X," such as approve a record, calculate a value, or kick off a process, rather than "react whenever a record changes." Design-wise, this is the same distinction Domain 1's technical-design guidance draws around code components (1.5): a Custom API is the modern building block for exposing custom server-side logic as its own first-class, callable operation.
A developer needs to expose a server-side calculation that any caller can explicitly invoke on demand, with strongly typed request and response parameters discoverable through the Web API's metadata -- not logic that runs automatically as a side effect of a save. Which mechanism fits best?
A Custom API's Binding Type is set to Entity and its Is Function property is set to true. How is it correctly invoked through the Web API?