Configure a custom API and develop its plug-in

Completed

With the eligibility-check plug-in scoped and running efficiently, the team's next ask isn't a performance fix. It's a new capability. They want to call "check device availability and reserve it" directly, from Power Automate, a future portal, or another plug-in.

They also want a structured answer back, not just a side effect of saving a request record. This unit shifts you from reacting to table events to designing and implementing a first-class, directly callable Dataverse operation: a custom API with a plug-in behind it.

Decide what kind of operation you're building

A custom API consolidates one or more operations into a new, callable Dataverse message. Once it exists, callers can invoke it from code, and callers can also invoke action-based custom APIs from Power Automate through the Dataverse connector. That's exactly what the "check device availability and reserve it" logic needs: a name other processes can target, instead of a plug-in that only ever wakes up when someone writes to the request table.

A custom API usually includes logic implemented by a plug-in, but a plug-in isn't a requirement. Without one, a custom API still fires as an event other systems can detect and react to, which is useful when all you need is a trigger, not synchronous work. The device-reservation operation needs more than a trigger: it has to check availability and commit a reservation before it can answer the caller, so this one gets a plug-in that does the actual work.

Choose how the operation attaches to data

Every custom API has a binding type that describes what it attaches to, and that choice shapes what Dataverse automatically hands the plug-in.

Binding type What it attaches to When it fits
Global Nothing specific; callable independent of any table Operations that don't center on one record
Entity A single record of a specific table, passed automatically as a Target parameter Operations that act on or reserve against one record
EntityCollection A collection tied to a specific table Operations that process or return multiple records at once

"Check device availability and reserve it" is reserving against a specific request record, so Entity binding to the request table fits. Dataverse creates the Target EntityReference parameter automatically, which means the plug-in already knows which request it's working with before it reads a single custom parameter.

Shape the request and response contract

Binding handles the record the operation acts on, but the operation still needs its own parameters and a response shape. Request parameters and response properties each get a unique name, used as the literal name callers and the plug-in reference. Each also gets a type, such as String, EntityReference, Boolean, or Integer.

For this operation, the response needs to tell the caller which device got reserved. A response property typed as either a String (a device identifier) or an EntityReference (a direct pointer to the device record) carries that answer back.

Diagram of a custom API call passing request parameters to its main-operation plug-in and returning response properties.

One more decision shapes how callers invoke the operation: whether it's a function or an action. A function is called with an HTTP GET and must return at least one response property, which fits read-style operations well. But it carries a 32 KB URL length limit and encoding quirks for string parameters passed in the URL.

An action uses POST with a body instead, sidestepping those URL constraints entirely. Reserving a device changes data and returns a result, so it's a natural fit for an action, and when the choice isn't obvious, an action is the safer default.

Guiding question: Suppose "check device availability and reserve it" needed to accept a free-text note from the caller explaining why the reservation is urgent. Would you still choose a function, given the URL encoding quirks that come with string parameters?

Control who can customize or extend it

The Allowed Custom Processing Step Type setting decides how much room other developers have to change what the operation does. It's also one of several fields the Plug-in Registration tool doesn't let you edit later, along with Unique Name, Binding Type, Bound Entity Logical Name, and Is Function. Getting it right before creation avoids a delete-and-recreate that also wipes out the request parameters and response properties already configured.

Allowed Custom Processing Step Type What others can do Fits when...
None Nothing; only your plug-in's logic runs The operation shouldn't be customizable at all
Async Only Register async steps to detect the event, but can't cancel or change it The operation is mainly a business-event trigger
Sync and Async Register sync steps that change or cancel behavior, plus async steps that react afterward The operation represents a customizable business process

The team wants its own plug-in to own the reservation logic itself. But it also wants other processes able to react once a device is reserved—and potentially extend the behavior later. That combination points to Sync and Async rather than Async Only, which would only allow reacting, or None, which would shut out extensibility entirely.

A few more settings round out the contract:

  • Is Private controls whether the operation shows up for other developers to discover. Leaving it false during development keeps it visible in the $metadata document for SDK code generation; plan to set it true before shipping if outside callers shouldn't use it directly.
  • Execute Privilege Name can require a specific privilege to call the operation at all.
  • Is Customizable, set to false on the operation and its parameters when the custom API ships in a managed solution, keeps consumers from breaking the contract by modifying it after the fact.

Learn more about custom API design settings.

Write the plug-in behind the operation

Writing the plug-in that implements a custom API's main operation isn't fundamentally different from any other plug-in you register. Two differences stand out. First, there's no step to register through the registration tool for this plug-in, since the custom API definition itself is the registration. Second, the plug-in can't receive unsecure or secure configuration data the way a conventional step can.

What the plug-in does need is the message name and the exact names and types of the request parameters and response properties defined on the custom API. Request parameter values arrive in context.InputParameters, keyed by the unique names set during configuration, and the plug-in writes its answer back by setting matching keys on context.OutputParameters.

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    var tracing = (ITracingService)
        serviceProvider.GetService(typeof(ITracingService));

    if (context.MessageName != "sample_CheckDeviceAvailabilityAndReserve")
    {
        return;
    }

    try
    {
        var requestRef = (EntityReference)context.InputParameters["Target"];
        var deviceType = (string)context.InputParameters["DeviceType"];

        // Reservation logic reads availability and commits the reservation.
        var reservedDeviceId = ReserveAvailableDevice(requestRef, deviceType);

        context.OutputParameters["ReservedDeviceId"] = reservedDeviceId;
    }
    catch (Exception ex)
    {
        tracing.Trace("Device reservation failed: {0}", ex.Message);
        throw new InvalidPluginExecutionException(
            "Unable to reserve a device for this request.");
    }
}

The pattern mirrors any plug-in you already write: confirm the message, read the inputs, do the work, and write the outputs. Wrap all of it in a try/catch that traces what happened and throws InvalidPluginExecutionException with a message the caller can act on. The device-reservation logic itself, whatever checks against the device table it needs to run, lives inside ReserveAvailableDevice, built the same way you'd build any other plug-in method.

Learn more about creating a custom API with the Plug-in Registration tool.

Synthesis prompt: Compare "check device availability and reserve it" as a custom API against leaving the logic as a plug-in step on the request table's create event. What does the caller gain by invoking a named operation directly instead of waiting for a save to trigger it?

With the operation designed, bound, and backed by a plug-in, the next unit looks at what happens after a request gets approved. It also looks at how other processes can find out, without depending directly on the approval logic itself.