Skip to content

Quickstart — From Zero to a Deployed Plugin

The whole server-side happy path on one page. Each step links to the chapter with the full story and the why; this page is just the what, in order. It assumes the toolchain (.NET SDK, Node, pac) is installed.

Read once, then bookmark

New to a DIGITALL Power Platform project? Skim this top to bottom to see how the pieces fit, then follow the linked chapters for the detail. Returning? Use it as a command cheat-sheet.

1. Install dgtp and connect

dotnet tool install -g dgt.power
dgtp profile create dev "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;AppId=...;RedirectUri=...;LoginPrompt=Auto"
dgtp profile select dev

dgtp CLI for token-based/CI profiles and the xrm:connection alternative.

2. Create the plugin project

Plugin packages (NuGet dependent assemblies) are the standard — scaffold one with pac, then add the two DIGITALL packages:

pac plugin init --outputDirectory ./src/Plugins/Contacts --skip-signing
cd ./src/Plugins/Contacts
dotnet add package Digitall.Plugins                # Executor base class
dotnet add package Digitall.Plugins.Registration   # declarative step registration

--skip-signing is correct here — packages aren't signed. → Plugin Packages (why packages, not ILMerge or classic single-assembly) and Project Setup for the exact .csproj.

3. Generate the early-bound model

dgtp codegeneration ./Model --config ./modelconfig.json

Early-Bound Models for the config shape. Generated code is not committed — it's reproduced on every build.

4. Write the plugin

Inherit Executor, work against the generated model (Contact, SdkMessageNames), return an ExecutionResult, and register the step with a [PluginRegistration] attribute — no Plugin Registration Tool:

ContactValidationPlugin.cs
using System;
using Digitall.Plugins;                // package: Digitall.Plugins
using Digitall.Dataverse.Model;        // generated early-bound model
using Digitall.Plugins.Registration;
using Microsoft.Xrm.Sdk;

[PluginRegistration(PluginExecutionMode.Synchronous, SdkMessageNames.Create, PluginExecutionStage.PreValidation,
    PrimaryEntityName = Contact.EntityLogicalName)]
public class ContactValidationPlugin : Executor
{
    protected override ExecutionResult Execute()
    {
        var contact = Entity.ToEntity<Contact>();

        if (!contact.BirthDate.HasValue)
        {
            return ExecutionResult.Skipped;
        }

        if (contact.BirthDate.Value > DateTime.UtcNow)
        {
            throw new InvalidPluginExecutionException("Birth date cannot be in the future.");
        }

        return ExecutionResult.Ok;
    }
}

5. Build, push, done

dotnet build -c Release
dgtp push ./bin/Release/Contacts.1.0.0.nupkg --solution dgt_myproject_plugins --publish

dgtp push creates or updates the plugin package (matched by name + version) and reconciles its [PluginRegistration] steps into Dataverse — add an attribute and push to register a step; remove it and push to unregister it. The package version must increase on every push, including local dev-loop builds. → dgtp push in depth.

6. Test without a live environment

var builder = new FakePluginContextBuilder();
var target = new Contact { BirthDate = new DateTime(1990, 1, 1) };
var sp = builder.WithMessageName(SdkMessageNames.Create).WithTarget(target).BuildServiceProvider();

var plugin = new ContactValidationPlugin();
plugin.Execute(sp);

Assert.That(plugin.Result, Is.EqualTo(ExecutionResult.Ok));

Server-side Unit Testing with Digitall.Dataverse.Testing.

Where to go from here

You also need to… Go to
Configure tables, forms, security (low-code) Customizing
Write a form/ribbon script TypeScript Web Resources
Automate with a cloud flow Power Automate Flows
Wire this into a build/release pipeline ALM & Deployment
Look up a dgtp command dgtp Command Reference