SSS DocGen — Plugin Registration (sss_GenerateDocument)
How to register SssDocGen.Plugins.dll and bind it to the sss_GenerateDocument custom API.
The plugin is a thin HTTP proxy: it acquires an Entra client-credentials token and forwards
the request to the DocGen API's POST /render endpoint. All business logic lives in the API.
Prerequisites
- Built, strong-name-signed assembly:
src/SssDocGen.Plugins/bin/Release/net462/SssDocGen.Plugins.dll(dotnet build -c Release src/SssDocGen.Plugins) - Power Platform CLI (
pac) authenticated against the target environment (pac auth create --environment <env-url>), or the classic Plugin Registration Tool (PRT) - The
sss_GenerateDocumentcustom API already exists in the environment (created by the solution) - The five environment variables below exist and have values
1. Register the plugin assembly (sandbox isolation)
With pac:
pac plugin push --pluginFile src/SssDocGen.Plugins/bin/Release/net462/SssDocGen.Plugins.dll
Or with PRT: Register > Register New Assembly, select SssDocGen.Plugins.dll,
isolation mode Sandbox, location Database. Both are mandatory for online environments.
Either route creates a plugintype record for SssDocGen.Plugins.GenerateDocumentPlugin.
2. Bind the plugin type to the custom API
Custom APIs are not bound with a plugin step. Instead, set the Plugin Type
(plugintypeid) on the customapi record for sss_GenerateDocument:
- make.powerapps.com: open the solution > Custom API
sss_GenerateDocument> edit > set Plugin Type toSssDocGen.Plugins.GenerateDocumentPlugin> save. - PRT: locate the custom API under the message browser is not supported; use the maker portal or Web API instead:
# Web API alternative (ids retrieved via GET on customapis / plugintypes)
PATCH {env-url}/api/data/v9.2/customapis(<customapi-id>)
{ "PluginTypeId@odata.bind": "/plugintypes(<plugintype-id>)" }
No SdkMessageProcessingStep registration is needed; Dataverse invokes the bound plugin type on the main operation of the custom API message.
3. Required environment variables
All five are read at runtime by the plugin via the RetrieveEnvironmentVariableValue
request (current value overrides default).
The secret must be a
Textvariable, not a Key Vault-backed one.RetrieveEnvironmentVariableValuedoes not resolve Key Vault-backed secret variables — that needsRetrieveEnvironmentVariableSecretValue, which the plugin does not call — so a secret-typed variable comes back empty. The env-var type is immutable once created; if yours is Secret, delete and recreate it as Text under the same schema name. See the deployment runbook; the security & compliance review (available on request) covers the risk this carries and how to mitigate it.
| Schema name | Type | Purpose | Example |
|---|---|---|---|
sss_DocGenApiBaseUrl |
Text | Base URL of the DocGen API (ACA app), no trailing slash needed | https://sss-docgen.<region>.azurecontainerapps.io |
sss_DocGenClientSecret |
Text (see note above) | Client secret of the app registration used for the client-credentials flow | the secret value itself, retrieved from Key Vault |
sss_DocGenTenantId |
Text | Entra tenant (directory) ID | 2f4a...-.... |
sss_DocGenClientId |
Text | Application (client) ID of the app registration | 9b1c...-.... |
sss_DocGenScope |
Text | OAuth scope requested for the token | api://<app-id>/.default |
Solution note (orchestrator):
sss_DocGenTenantId,sss_DocGenClientIdandsss_DocGenScopemust be added to the solution alongside the existingsss_DocGenApiBaseUrlandsss_DocGenClientSecretdefinitions. This document does not modifysolution/; the orchestrator owns that change.
App registration note: the same Entra app registration serves three roles — (1) the token audience configured on the DocGen API (ACA) for JWT validation, (2) the identity in the client-credentials flow (
sss_DocGenClientId+ secret), and (3) the Dataverse application user the API uses to call Dataverse. Keep them in sync.
4. Custom API contract (for reference)
Message: sss_GenerateDocument (bound plugin: SssDocGen.Plugins.GenerateDocumentPlugin)
| Direction | Parameter | Type | Notes |
|---|---|---|---|
| In | Code |
String | Template/document code; required, non-empty |
| In | EntityId |
String | Target record id (GUID as string); required, non-empty |
| In | Attach |
Boolean | Optional, default false. Forwarded as attach; when true the API stores the PDF as an annotation on the record |
| In | Output |
String | Optional, default pdf. Forwarded as output — pdf, html or data. Omitted from the request body when null, so existing callers are unaffected |
| Out | FileBase64 |
String | Generated file content, base64. Null unless Output is pdf |
| Out | FileName |
String | Suggested file name |
| Out | AnnotationId |
String | Annotation id if the API stored the file; null unless Attach was true |
| Out | Content |
String | Rendered HTML (Output=html) or the enriched data tree as JSON (Output=data); null when Output is pdf |
| Out | Warnings |
String | Non-fatal problems, newline-joined; null when there were none. The API returns these as a JSON array, which the plugin joins because a custom API response property is a String |
The full semantics of Attach, Output and Warnings — including which combinations return
400 — are in Usage & Customization §2.
This table is the plugin's view of the same contract.
Errors from the API (problem+json title/detail), timeouts and network failures surface
as InvalidPluginExecutionException messages to the caller. Warnings is the exception: it is
non-fatal by definition — the document was still produced — so it never throws. Plugin trace logs
record URL, HTTP status and duration — never the secret or the access token.
5. Smoke test
POST {env-url}/api/data/v9.2/sss_GenerateDocument
{ "Code": "<template-code>", "EntityId": "<record-guid>" }
Expect FileBase64, FileName and (with the defaults above) a null AnnotationId, Content
and Warnings in the response. A populated Warnings means the document was produced but may be
wrong — do not treat the 200 as proof of correctness. To exercise note creation, repeat with
"Attach": true and expect an AnnotationId; that is the only path needing the
prvAppendNote / prvAppendTo<target> privilege pair. On failure, check the Plugin Trace Log
(enable via Settings > Administration > System Settings > Customization).