Package Exports
- @brunsforge/dataverse-custom-api
- @brunsforge/dataverse-custom-api/cli
Readme
Dataverse Custom API CLI
A Node.js/TypeScript CLI for managing Dataverse Custom APIs. It enables reading, exporting, editing, validating, diffing, planning, and synchronizing Custom API definitions between local JSON files and Dataverse environments.
The GitHub repository contains the TypeScript src/ source code. The compiled dist/ output is generated during build and included together with src/ in the published npm package.
Before you begin
What is a Dataverse Custom API? Custom APIs in Microsoft Dataverse are custom messages (actions or functions) that can be called from Power Automate, Power Apps, or code. They are defined by metadata: a name, optional request parameters, and optional response properties.
What does this CLI do? This tool manages that metadata. You export a Custom API definition as a local JSON file, edit it, validate it, and sync the changes back to Dataverse — all from the command line without touching the Maker Portal directly.
What do you need to get started?
- Node.js 16+ installed
- Access to a Dataverse environment
- An Azure AD / Microsoft Entra ID App Registration with the correct permissions (see Authentication below)
What do you want to do?
Jump directly to your scenario:
| Goal | Where to go |
|---|---|
| Connect to a Dataverse environment for the first time | Connect to an environment |
| Create a new Custom API | Scenario: Create a new Custom API |
| Edit an existing Custom API | Scenario: Edit an existing Custom API |
| Check why local and remote metadata differ | Scenario: Check metadata problems |
Installation
npm install -g @brunsforge/dataverse-custom-apiOr from source:
git clone https://github.com/brunsforge/dataverse-custom-api.git
cd dataverse-custom-api
npm install
npm run build
npm linkFeatures
- Connect to Dataverse environments: multiple authentication methods (Device Code, Client Secret, Interactive Browser)
- Environment management: save, list, switch, and remove environments
- Custom API management:
- list available Custom APIs
- export Custom APIs as local JSON artifacts
- edit Custom API definitions locally
- validate local definitions before syncing — catches type errors, missing fields, and invalid combinations with structured diagnostics
- compare local definitions with Dataverse metadata
- create sync plans (validation runs automatically; plan is blocked if errors are found)
- execute single sync operations
- execute full sync plans
- create, update, or delete Custom API metadata in Dataverse
- validate privileges — check which Dataverse privileges the configured App User holds and which features are available or restricted
- list publishers — show all registered publishers with their customization prefixes
- Graceful privilege fallback: if the App User lacks
prvAppendToPluginType,createCustomApiautomatically retries without the PluginType binding and records awarningin the sync result instead of failing - Simulation mode: preview operations with
--simulatebefore applying changes - JSON and human-readable output: every command supports
--json - Structured diagnostics:
api validate --jsonreturns aCcdvCommandResultenvelope for VS Code extension integration
Warning: Commands that execute without
--simulatemay create, update, or delete Custom API metadata in the connected Dataverse environment.
Authentication and connection
Authentication methods
The CLI supports three authentication methods:
- Device Code Flow (recommended for interactive use)
- Client Secret (for automation / app-only access)
- Interactive Browser (for browser-based login)
Configuration
Create an auth.json file in the repository root based on one of the sample files:
auth.devicecode.example.json→ Device Code authauth.clientsecret.example.json→ Client Secret authauth.interactivebrowser.example.json→ Interactive Browser auth
Device Code auth
{
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"authMode": "deviceCode"
}App registration setup:
- Create an Azure AD / Microsoft Entra ID App Registration
- Add API permissions: Dynamics CRM > user_impersonation
- Add a redirect URI:
https://login.microsoftonline.com/common/oauth2/nativeclient - No client secret is required
Client Secret auth
{
"tenantId": "your-tenant-id",
"clientId": "your-app-only-client-id",
"clientSecret": "your-client-secret",
"authMode": "clientSecret"
}App registration setup:
- Create an Azure AD / Microsoft Entra ID App Registration
- Create a client secret
- Add API permissions: Dynamics CRM > user_impersonation (or Dataverse application permissions for app-only access)
- No redirect URI is required
Interactive Browser auth
{
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"authMode": "interactiveBrowser"
}Connect to an environment
dvc connect -u "https://your-org.crm.dynamics.com"Output:
Connected and cached: https://your-org.crm.dynamics.com
Auth mode: deviceCode
Cache file: /path/to/cache/environment.jsonEnvironment management
List environments
dvc env listOutput:
* env-001 (Production) -> https://prod.crm.dynamics.com
- env-002 (Development) -> https://dev.crm.dynamics.comShow current environment
dvc env currentOutput:
Active environment: env-001
Display name: Production
URL: https://prod.crm.dynamics.com
Auth mode: deviceCodeSwitch environment
dvc env use -i env-002Remove environment
dvc env remove -i env-001Custom API management
List Custom APIs
dvc api listOutput:
* ccsm_MyCustomApi
- ccsm_AnotherApi
- sample_CustomFunctionShow active Custom API
dvc api currentOutput:
Active API: ccsm_MyCustomApi
Cache file: /path/to/cache/active-api.jsonSet active Custom API
dvc api use -n ccsm_MyCustomApiExport Custom API
Exports the current Dataverse definition to a local JSON catalog file.
dvc api export
# or with explicit name:
dvc api export -n ccsm_MyCustomApiOutput:
Exported: /path/to/output/ccsm_MyCustomApi.jsonValidate local definition
Validates the local JSON file against Dataverse field rules. Returns structured diagnostics with error codes, field paths, and suggested fixes. No network connection is required.
dvc api validate
# or with explicit name:
dvc api validate -n ccsm_MyCustomApiOutput — no issues:
Validation for: ccsm_MyCustomApi
Status: succeeded
No issues found.Output — with diagnostics:
Validation for: ccsm_MyCustomApi
Status: validationFailed
ERROR [CCDV_PARAM_LOGICAL_ENTITY_NAME_NOT_ALLOWED] [ccsm_MyCustomApi > NewParameter].logicalEntityName
Request parameter 'NewParameter' has type 'EntityCollection' which does not support logicalEntityName. Only Entity and EntityReference are allowed.
Fix: Remove logicalEntityName or change the parameter type to Entity or EntityReference.
WARN [CCDV_PARAM_NAME_RECOMMENDED_FORMAT] [ccsm_MyCustomApi > InputParam].name
Request parameter 'InputParam' name 'InputParam' does not follow the recommended format 'ccsm_MyCustomApi.InputParam'.
Fix: Set name to 'ccsm_MyCustomApi.InputParam'.
Summary: 1 error(s), 1 warning(s), 0 info(s)JSON output returns a full CcdvCommandResult envelope:
dvc api validate --json{
"schemaVersion": "1.0.0",
"status": "validationFailed",
"command": "api validate",
"startedAtUtc": "2025-01-15T10:00:00.000Z",
"finishedAtUtc": "2025-01-15T10:00:00.001Z",
"durationMs": 1,
"diagnostics": [
{
"id": "diag-0001",
"code": "CCDV_PARAM_LOGICAL_ENTITY_NAME_NOT_ALLOWED",
"severity": "error",
"category": "validation",
"message": "Request parameter 'NewParameter' has type 'EntityCollection' which does not support logicalEntityName.",
"entityKind": "requestParameter",
"parentUniqueName": "ccsm_MyCustomApi",
"uniqueName": "NewParameter",
"field": "logicalEntityName",
"jsonPath": "$.requestParameters[?(@.uniqueName=='NewParameter')].logicalEntityName",
"blocking": true,
"suggestedFix": {
"kind": "removeField",
"field": "logicalEntityName",
"message": "Remove logicalEntityName or change the parameter type to Entity or EntityReference."
}
}
]
}Diagnostic severity levels:
| Severity | Meaning |
|---|---|
error |
Blocking — Dataverse will reject the payload or the value is structurally invalid. api plan will not proceed. |
warning |
Non-blocking — the definition is accepted but deviates from recommended practice (e.g. name format). |
info |
Informational — e.g. an open Entity type without logicalEntityName, which is valid but may be unintentional. |
Validated rules include:
uniqueNamerequired, max 128 characters, must contain publisher prefix (e.g.ccsm_), only letters/digits/underscoresnameanddisplayNamerequired, max 100 charactersdescriptionmax 300 charactersbindingTypemust beGlobal,Entity, orEntityCollectionboundEntityLogicalNamerequired whenbindingTypeisEntityorEntityCollection; must be absent forGlobalallowedCustomProcessingStepTypemust beNone,AsyncOnly, orSyncAndAsync- Functions (
isFunction: true) must have at least one response property - Functions must not use open Entity/EntityCollection request parameters (without
logicalEntityName) logicalEntityNameonly allowed on parameters/properties with typeEntityorEntityReference; automatically flagged for all other typesnameformat recommendation:{customApiUniqueName}.{childUniqueName}
Note:
api planruns the same validation internally. If blocking errors are found, the plan is not created and the error message referencesapi validatefor the full report. Always runapi validatefirst to see all diagnostics before planning.
Compare local definition with Dataverse
Compares the local JSON file against the current Dataverse metadata and shows field-level differences.
dvc api diff
# or with explicit name:
dvc api diff -n ccsm_MyCustomApiOutput:
Diff for: ccsm_MyCustomApi
Differences found: yes
Custom API: update
Request parameter summary:
none=1, create=1, update=0, delete=0, recreate=0
- create: NewParameter
Response property summary:
none=2, create=0, update=0, delete=0, recreate=0Use --json for the full machine-readable diff output.
Create sync plan
Compares local vs. Dataverse, generates an ordered operation list, and writes plan and state files. Validation runs automatically — if blocking errors exist, the plan is not created.
dvc api plan
# or with explicit name:
dvc api plan -n ccsm_MyCustomApiOutput:
Plan created: /path/to/output/ccsm_MyCustomApi.syncplan.json
State file: /path/to/output/ccsm_MyCustomApi.syncstate.json
Operations: 2
Destructive changes required: no
- [10] createRequestParameter NewParameter (new)
- [20] updateCustomApi ccsm_MyCustomApi (changed)If validation fails:
Error: Sync plan blocked by 1 validation error(s). Run 'api validate' for the full report.
First error: [CCDV_PARAM_LOGICAL_ENTITY_NAME_NOT_ALLOWED] Request parameter 'NewParameter' has type 'EntityCollection' which does not support logicalEntityName.Execute a single operation
# dry run:
dvc api exec-op -o "op-0010-createRequestParameter-NewParameter-<uuid>" --simulate
# live:
dvc api exec-op -o "op-0010-createRequestParameter-NewParameter-<uuid>"Output:
Operation: op-0010-createRequestParameter-NewParameter-<uuid>
Status: succeeded
Message: createRequestParameter for NewParameter completed successfully.
Simulated: no
State file: /path/to/output/ccsm_MyCustomApi.syncstate.jsonWarning: Without
--simulate, the operation is executed against the connected Dataverse environment.
Execute full plan
# dry run:
dvc api exec-plan --simulate
# live:
dvc api exec-planOutput:
Plan executed for: ccsm_MyCustomApi
Status: succeeded
State file: /path/to/output/ccsm_MyCustomApi.syncstate.json
- [10] createRequestParameter NewParameter: succeeded
- [20] updateCustomApi ccsm_MyCustomApi: succeededWarning: Without
--simulate, all operations in the sync plan are executed in sequence against the connected Dataverse environment.
Check metadata consistency
Compares the local definition with current Dataverse metadata and reports field-level mismatches. Requires a network connection.
dvc api check-metadata
# or with explicit name:
dvc api check-metadata -n ccsm_MyCustomApi
# machine-readable output:
dvc api check-metadata --jsonOutput — no mismatches:
Metadata check for: ccsm_MyCustomApi
Status: ok
No metadata mismatches found.Output — with mismatches:
Metadata check for: ccsm_MyCustomApi
Status: warning
2 metadata mismatches found.
- customApi:ccsm_MyCustomApi | description | local="Updated description" | remote="Old description" | recreate=no
- requestParameter:RecipientId | displayName | local="Recipient ID" | remote="Recipient" | recreate=noEach mismatch line shows the object type, the affected field, the local and remote value, and whether the field is immutable (recreate=yes means the object must be deleted and re-created to apply the change).
Validate Dataverse privileges
Checks which relevant Dataverse privileges the configured App User holds and reports which features are available or missing. Requires a network connection.
dvc api validate-privileges
# show all discovered privilege IDs:
dvc api validate-privileges --verbose
# machine-readable output:
dvc api validate-privileges --jsonOutput:
Privilege validation for: orgf707a816.crm16.dynamics.com
App user: DVC Custom API CLI - AppOnly (6dcbb78d-...)
FEATURE PRIVILEGE STATUS
──────────────────────────────────────────────────────────────────────────────
Read Custom API prvReadCustomAPI ✓ Available
Create Custom API prvCreateCustomAPI ✓ Available
Update Custom API prvWriteCustomAPI ✓ Available
Delete Custom API prvDeleteCustomAPI ✓ Available
PluginType Binding prvAppendToPluginType ✗ Missing
Create Plugin Steps prvCreateSdkMessageProcessingStep ✗ Missing
HINTS:
• Custom APIs will be created without a plugin type link.
Grant 'prvAppendToPluginType' (AppendTo, plugintype, organisation scope) to the app user's security role.
• Plugin step registrations cannot be created automatically.
Grant 'prvCreateSdkMessageProcessingStep' (Create, sdkmessageprocessingstep, organisation scope) to the app user's security role.Checked privileges:
| Feature | Privilege | Known ID |
|---|---|---|
| Read Custom APIs | prvReadCustomAPI |
resolved via API |
| Create Custom APIs | prvCreateCustomAPI |
resolved via API |
| Update Custom APIs | prvWriteCustomAPI |
resolved via API |
| Delete Custom APIs | prvDeleteCustomAPI |
resolved via API |
| PluginType binding on create | prvAppendToPluginType |
574c053e-6488-4bfb-832a-cbc47aff8b32 |
| Create Plugin Steps | prvCreateSdkMessageProcessingStep |
resolved via API |
Graceful fallback for prvAppendToPluginType:
When createCustomApi is executed and the App User lacks prvAppendToPluginType, the CLI automatically retries without the PluginTypeId@odata.bind field. The Custom API is created successfully but without the Plugin binding. The sync result (exec-op, exec-plan) contains a warning field explaining what was skipped and how to remediate:
- assign
prvAppendToPluginType(AppendTo, plugintype, Org level) to the App User's security role, or - link the Plugin manually in the Maker Portal after creation.
List publishers
Lists all publishers registered in the active Dataverse environment. The publisher's customizationPrefix must be used as the prefix of a Custom API uniqueName (e.g. myprefix_MyAction).
dvc api list-publishers
# machine-readable output:
dvc api list-publishers --jsonOutput:
PREFIX FRIENDLY NAME UNIQUE NAME
───────────────────────────────────────────────────────────────────────────────
myprefix My Company Publisher mycompany_publisher
contoso Contoso Default Publisher contoso_defaultJSON output:
{
"publishers": [
{
"publisherId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"uniqueName": "mycompany_publisher",
"friendlyName": "My Company Publisher",
"customizationPrefix": "myprefix"
}
]
}Note: The Microsoft system publisher (
mscrm) is excluded from the list — its prefix cannot be used for custom solutions.
Remove local artifacts
Removes local cached and exported files. Does not delete the Custom API from Dataverse.
dvc api remove
# or with explicit name:
dvc api remove -n ccsm_MyCustomApiTypical workflows
1. Setup and connect
# Create auth configuration
cp auth.devicecode.example.json auth.json
# Edit auth.json with your tenant ID and client ID
# Connect to an environment
dvc connect -u "https://your-org.crm.dynamics.com"
# Optional: verify that the App User has the required privileges
dvc api validate-privileges2. Edit an existing Custom API
# List available APIs
dvc api list
# Set the API as active
dvc api use -n ccsm_ExistingApi
# Export the current Dataverse definition
dvc api export
# Edit the local JSON file
# e.g. add a request parameter, change a description
# Validate the local definition — fix any errors before proceeding
dvc api validate
# Review what changed vs. Dataverse
dvc api diff
# Create a sync plan (also validates internally)
dvc api plan
# Dry run first
dvc api exec-plan --simulate
# Apply after reviewing
dvc api exec-plan3. Create a new Custom API
# Optional: check App User privileges before creating
# If prvAppendToPluginType is missing, the PluginType binding will be skipped on create
dvc api validate-privileges
# List publishers to find the correct prefix for your uniqueName
dvc api list-publishers
# Create a new JSON catalog file manually (see structure below)
# e.g. .cache/customapis/myprefix_NewApi.json
# Set the API as active even before it exists in Dataverse
dvc api use -n ccsm_NewApi
# Validate the local definition before planning
dvc api validate
# Create a sync plan
dvc api plan
# Dry run first
dvc api exec-plan --simulate
# Apply after reviewing
dvc api exec-plan
# If the App User lacked prvAppendToPluginType, the createCustomApi result
# will contain a warning — check the output and link the Plugin manually if needed.Scenario: Check metadata problems
Use this when you suspect the local JSON file and Dataverse have drifted apart — for example after a manual change in the Maker Portal.
# Ensure the correct API is active
dvc api use -n ccsm_MyCustomApi
# Compare local vs. remote field-by-field
dvc api check-metadata
# If differences are found, export the current remote definition
# (overwrites the local file with what Dataverse has):
dvc api export
# Or: edit the local file to match your intent, then re-validate and sync:
dvc api validate
dvc api plan
dvc api exec-plan --simulate
dvc api exec-planWhy validate before plan?
api validate is a local-only check — no network call, instant feedback. It catches issues that Dataverse would reject with a 400 error (e.g. logicalEntityName on an EntityCollection parameter, missing publisher prefix, invalid type values).
api plan runs the same validation internally and will block if blocking errors are found. Running api validate first gives you the full structured diagnostic report — including non-blocking warnings and informational hints — before attempting to build the plan.
Custom API JSON structure
The catalog file written by api export and read by api plan / api validate:
{
"schemaVersion": "1.0",
"source": {
"exportedAtUtc": "2025-01-15T10:00:00.000Z",
"environmentUrl": "https://your-org.crm.dynamics.com"
},
"customApis": [
{
"uniqueName": "ccsm_MyCustomApi",
"name": "ccsm_MyCustomApi",
"displayName": "My Custom API",
"description": "Processes a survey recipient record.",
"bindingType": "Global",
"isFunction": false,
"isPrivate": false,
"workflowSdkStepEnabled": false,
"allowedCustomProcessingStepType": "None",
"requestParameters": [
{
"uniqueName": "RecipientId",
"name": "ccsm_MyCustomApi.RecipientId",
"displayName": "Recipient ID",
"description": "ID of the recipient record.",
"type": "EntityReference",
"logicalEntityName": "ccsm_surveyrecipient",
"isOptional": false
}
],
"responseProperties": [
{
"uniqueName": "Success",
"name": "ccsm_MyCustomApi.Success",
"displayName": "Success",
"description": "Whether the operation succeeded.",
"type": "Boolean"
}
]
}
]
}Field notes:
| Field | Values / constraints |
|---|---|
bindingType |
"Global", "Entity", "EntityCollection" |
allowedCustomProcessingStepType |
"None", "AsyncOnly", "SyncAndAsync" |
type (parameter/property) |
"Boolean", "DateTime", "Decimal", "Entity", "EntityCollection", "EntityReference", "Float", "Integer", "Money", "Picklist", "String", "StringArray", "Guid" |
logicalEntityName |
Only set for Entity or EntityReference; must be absent for all other types |
name (parameter/property) |
Recommended format: {customApiUniqueName}.{childUniqueName} |
uniqueName (Custom API) |
Must include publisher prefix, e.g. ccsm_MyApi; only letters, digits, underscores |
Publishing notes
The package is built from TypeScript. The repository does not need committed dist/ files; they are generated during build and included in the published npm package.
Before publishing, verify the package contents:
npm run build
npm pack --dry-runThe dry run should include at least:
dist/
src/
README.md
LICENSE
package.jsonFor scoped public packages:
npm publish --access publicRequirements
- Node.js 16+
- npm
- Access to a Dataverse environment
- Azure AD / Microsoft Entra ID App Registration with the correct permissions
- Sufficient Dataverse privileges to read or modify Custom API metadata — run
dvc api validate-privilegesafter connecting to verify which privileges are available
License
MIT
Author and contact
Andreas Brunsmann
- Email: oss@andreasbrunsmann.de
- GitHub: https://github.com/brunsforge
- Repository: https://github.com/brunsforge/dataverse-custom-api
Dataverse Custom API CLI
Eine Node.js/TypeScript-basierte CLI zum Verwalten von Dataverse Custom APIs. Sie ermöglicht das Lesen, Exportieren, Bearbeiten, Validieren, Vergleichen, Planen und Synchronisieren von Custom-API-Definitionen zwischen lokalen JSON-Dateien und Dataverse-Umgebungen.
Das GitHub-Repository enthält den TypeScript-Quellcode im Ordner src/. Das kompilierte dist/-Verzeichnis wird beim Build erzeugt und zusammen mit src/ im veröffentlichten npm-Paket ausgeliefert.
Vor dem Einstieg
Was ist eine Dataverse Custom API? Custom APIs in Microsoft Dataverse sind benutzerdefinierte Nachrichten (Actions oder Functions), die aus Power Automate, Power Apps oder Code aufgerufen werden können. Sie werden durch Metadaten definiert: einen Namen, optionale Request-Parameter und optionale Response-Properties.
Was macht diese CLI? Das Tool verwaltet genau diese Metadaten. Du exportierst eine Custom-API-Definition als lokale JSON-Datei, bearbeitest sie, validierst sie und synchronisierst die Änderungen zurück nach Dataverse — alles über die Kommandozeile, ohne das Maker Portal direkt zu verwenden.
Was benötigst du zum Einstieg?
- Node.js 16+ installiert
- Zugriff auf eine Dataverse-Umgebung
- Eine Azure AD / Microsoft Entra ID App Registration mit den richtigen Berechtigungen (siehe Authentifizierung unten)
Was möchtest du tun?
Springe direkt zu deinem Szenario:
| Ziel | Direkt zum Abschnitt |
|---|---|
| Zum ersten Mal mit einer Dataverse-Umgebung verbinden | Zu einem Environment connecten |
| Eine neue Custom API anlegen | Szenario: Neue Custom API anlegen |
| Eine vorhandene Custom API bearbeiten | Szenario: Vorhandene Custom API bearbeiten |
| Prüfen, warum lokale und remote Metadaten abweichen | Szenario: Metadaten-Probleme prüfen |
Installation
npm install -g @brunsforge/dataverse-custom-apiÜbersicht der Features
- Verbindung zu Dataverse-Umgebungen: Device Code, Client Secret, Interactive Browser
- Environment-Management: speichern, auflisten, wechseln, entfernen
- Custom API-Verwaltung:
- vorhandene Custom APIs auflisten
- als lokale JSON-Artefakte exportieren
- lokal bearbeiten
- lokale Definition validieren — erkennt Typfehler, fehlende Felder und ungültige Kombinationen mit strukturierten Diagnostics
- mit Dataverse-Metadaten vergleichen
- Sync-Pläne erstellen (Validierung läuft automatisch; Plan wird bei Fehlern blockiert)
- einzelne Sync-Operationen ausführen
- vollständige Sync-Pläne ausführen
- Privileges prüfen — prüft welche Dataverse-Privileges der konfigurierte App-User besitzt und welche Features verfügbar oder eingeschränkt sind
- Publisher auflisten — zeigt alle registrierten Publisher mit ihren Customization-Prefixen an
- Graceful Privilege-Fallback: fehlt dem App-User
prvAppendToPluginType, wirdcreateCustomApiautomatisch ohne das PluginType-Binding wiederholt und einewarningim Sync-Ergebnis eingetragen — statt mit einem Fehler abzubrechen - Simulationsmodus: Operationen mit
--simulatevorab prüfen - JSON und menschenlesbare Ausgaben: alle Befehle unterstützen
--json
Achtung: Befehle, die ohne
--simulateausgeführt werden, können Custom-API-Metadaten in der verbundenen Dataverse-Umgebung erstellen, ändern oder löschen.
Authentifizierung und Verbindung
Authentifizierungsmethoden
Die CLI unterstützt drei Authentifizierungsmethoden:
- Device Code Flow (empfohlen für interaktive Nutzung)
- Client Secret (für Automatisierung / App-only-Zugriff)
- Interactive Browser (browserbasierter Login)
Konfiguration
Erstelle eine auth.json-Datei im Repository-Stammverzeichnis auf Basis einer der mitgelieferten Beispieldateien:
auth.devicecode.example.json→ Device Codeauth.clientsecret.example.json→ Client Secretauth.interactivebrowser.example.json→ Interactive Browser
Device Code
{
"tenantId": "deine-tenant-id",
"clientId": "deine-client-id",
"authMode": "deviceCode"
}App-Registration-Setup:
- Azure AD / Microsoft Entra ID App Registration anlegen
- API-Berechtigung hinzufügen: Dynamics CRM > user_impersonation
- Redirect-URI hinzufügen:
https://login.microsoftonline.com/common/oauth2/nativeclient - Kein Client Secret erforderlich
Client Secret
{
"tenantId": "deine-tenant-id",
"clientId": "deine-app-only-client-id",
"clientSecret": "dein-client-secret",
"authMode": "clientSecret"
}App-Registration-Setup:
- Azure AD / Microsoft Entra ID App Registration anlegen
- Client Secret erstellen
- API-Berechtigung hinzufügen: Dynamics CRM > user_impersonation (oder Dataverse-Anwendungsberechtigungen für App-only-Zugriff)
- Kein Redirect-URI erforderlich
Interactive Browser
{
"tenantId": "deine-tenant-id",
"clientId": "deine-client-id",
"authMode": "interactiveBrowser"
}Zu einem Environment connecten
dvc connect -u "https://deine-org.crm.dynamics.com"Ausgabe:
Connected and cached: https://deine-org.crm.dynamics.com
Auth mode: deviceCode
Cache file: /pfad/zum/cache/environment.jsonEnvironment-Management
dvc env list # alle gespeicherten Environments auflisten
dvc env current # aktives Environment anzeigen
dvc env use -i <id> # Environment wechseln
dvc env remove -i <id> # Environment entfernenAusgabe von dvc env list:
* env-001 (Production) -> https://prod.crm.dynamics.com
- env-002 (Development) -> https://dev.crm.dynamics.comAusgabe von dvc env current:
Active environment: env-001
Display name: Production
URL: https://prod.crm.dynamics.com
Auth mode: deviceCodeCustom API-Management
Auflisten und auswählen
dvc api list # alle Custom APIs auflisten
dvc api current # aktive API anzeigen
dvc api use -n ccsm_MyApi # API als aktiv setzenAusgabe von dvc api list:
* ccsm_MyCustomApi
- ccsm_AnotherApi
- sample_CustomFunctionExportieren
Exportiert die aktuelle Dataverse-Definition in eine lokale JSON-Katalog-Datei.
dvc api export
dvc api export -n ccsm_MyApiAusgabe:
Exported: /pfad/zu/output/ccsm_MyApi.jsonLokale Definition validieren
Prüft die lokale JSON-Datei gegen Dataverse-Regeln. Keine Netzwerkverbindung erforderlich.
dvc api validate
dvc api validate -n ccsm_MyApiAusgabe ohne Probleme:
Validation for: ccsm_MyApi
Status: succeeded
No issues found.Ausgabe mit Fehlern:
Validation for: ccsm_MyApi
Status: validationFailed
ERROR [CCDV_PARAM_LOGICAL_ENTITY_NAME_NOT_ALLOWED] [ccsm_MyApi > NewParameter].logicalEntityName
Request parameter 'NewParameter' has type 'EntityCollection' which does not support logicalEntityName.
Fix: Remove logicalEntityName or change the parameter type to Entity or EntityReference.
WARN [CCDV_PARAM_NAME_RECOMMENDED_FORMAT] [ccsm_MyApi > InputParam].name
Request parameter 'InputParam' name 'InputParam' does not follow the recommended format 'ccsm_MyApi.InputParam'.
Fix: Set name to 'ccsm_MyApi.InputParam'.
Summary: 1 error(s), 1 warning(s), 0 info(s)Mit --json wird ein vollständiges CcdvCommandResult-Envelope zurückgegeben (für VS-Code-Extension-Integration).
Geprüfte Regeln (Auswahl):
uniqueName: Pflichtfeld, max. 128 Zeichen, muss Publisher-Präfix enthalten (z. B.ccsm_), nur Buchstaben/Ziffern/UnterstrichenameunddisplayName: Pflichtfeld, max. 100 Zeichendescription: max. 300 ZeichenbindingType:Global,EntityoderEntityCollectionboundEntityLogicalName: Pflicht beiEntity/EntityCollection; muss fehlen beiGloballogicalEntityNamean Parametern/Properties: nur erlaubt bei TypeEntityoderEntityReference- Functions (
isFunction: true): mindestens eine Response Property erforderlich - Functions: keine offenen Entity-/EntityCollection-Request-Parameter (ohne
logicalEntityName)
Hinweis:
api planläuft dieselbe Validierung intern ab. Bei blockierenden Fehlern wird kein Plan erstellt; die Fehlermeldung verweist aufapi validatefür den vollständigen Report.
Vergleichen
Vergleicht die lokale JSON-Datei mit den aktuellen Dataverse-Metadaten und zeigt Feldunterschiede.
dvc api diff
dvc api diff -n ccsm_MyApiAusgabe:
Diff for: ccsm_MyApi
Differences found: yes
Custom API: update
Request parameter summary:
none=1, create=1, update=0, delete=0, recreate=0
- create: NewParameter
Response property summary:
none=2, create=0, update=0, delete=0, recreate=0Mit --json für die vollständige maschinenlesbare Ausgabe.
Sync-Plan erstellen
Vergleicht lokale Definition mit Dataverse, erstellt eine geordnete Operationsliste und schreibt Plan- und Statusdateien. Validierung läuft automatisch — bei blockierenden Fehlern wird kein Plan erstellt.
dvc api plan
dvc api plan -n ccsm_MyApiAusgabe:
Plan created: .cache/customapis/ccsm_MyApi.syncplan.json
State file: .cache/customapis/ccsm_MyApi.syncstate.json
Operations: 2
Destructive changes required: no
- [10] createRequestParameter NewParameter (new)
- [20] updateCustomApi ccsm_MyApi (changed)Bei Validierungsfehler:
Error: Sync plan blocked by 1 validation error(s). Run 'api validate' for the full report.
First error: [CCDV_PARAM_LOGICAL_ENTITY_NAME_NOT_ALLOWED] Request parameter 'NewParameter' has type 'EntityCollection' which does not support logicalEntityName.Plan ausführen
dvc api exec-plan --simulate # Dry Run
dvc api exec-plan # live ausführenAusgabe:
Plan executed for: ccsm_MyApi
Status: succeeded
State file: .cache/customapis/ccsm_MyApi.syncstate.json
- [10] createRequestParameter NewParameter: succeeded
- [20] updateCustomApi ccsm_MyApi: succeededAchtung: Ohne
--simulatewerden alle Operationen nacheinander gegen die verbundene Dataverse-Umgebung ausgeführt.
Einzelne Operation ausführen
dvc api exec-op -o "<operationId>" --simulate # Dry Run
dvc api exec-op -o "<operationId>" # live ausführenAusgabe:
Operation: op-0010-createRequestParameter-NewParameter-<uuid>
Status: succeeded
Message: createRequestParameter for NewParameter completed successfully.
Simulated: no
State file: .cache/customapis/ccsm_MyApi.syncstate.jsonAchtung: Ohne
--simulatewird die Operation gegen die verbundene Dataverse-Umgebung ausgeführt.
Metadaten-Konsistenz prüfen
Vergleicht die lokale Definition mit den aktuellen Dataverse-Metadaten und meldet Feldabweichungen. Erfordert eine Netzwerkverbindung.
dvc api check-metadata
# mit explizitem Namen:
dvc api check-metadata -n ccsm_MyApi
# maschinenlesbar:
dvc api check-metadata --jsonAusgabe ohne Abweichungen:
Metadata check for: ccsm_MyApi
Status: ok
No metadata mismatches found.Ausgabe mit Abweichungen:
Metadata check for: ccsm_MyApi
Status: warning
2 metadata mismatches found.
- customApi:ccsm_MyApi | description | local="Aktualisierte Beschreibung" | remote="Alte Beschreibung" | recreate=no
- requestParameter:RecipientId | displayName | local="Empfänger-ID" | remote="Empfänger" | recreate=noJede Zeile zeigt: Objekttyp, betroffenes Feld, lokalen Wert, Remote-Wert und ob das Feld unveränderlich ist (recreate=yes bedeutet: das Objekt muss gelöscht und neu erstellt werden, um die Änderung anzuwenden).
Dataverse-Privileges prüfen
Prüft welche relevanten Dataverse-Privileges der konfigurierte App-User besitzt und gibt eine strukturierte Übersicht aus. Erfordert eine Netzwerkverbindung.
dvc api validate-privileges
# Alle gefundenen Privilege-IDs anzeigen:
dvc api validate-privileges --verbose
# Maschinenlesbare Ausgabe:
dvc api validate-privileges --jsonAusgabe:
Privilege validation for: orgf707a816.crm16.dynamics.com
App user: DVC Custom API CLI - AppOnly (6dcbb78d-...)
FEATURE PRIVILEGE STATUS
──────────────────────────────────────────────────────────────────────────────
Read Custom API prvReadCustomAPI ✓ Available
Create Custom API prvCreateCustomAPI ✓ Available
Update Custom API prvWriteCustomAPI ✓ Available
Delete Custom API prvDeleteCustomAPI ✓ Available
PluginType Binding prvAppendToPluginType ✗ Missing
Create Plugin Steps prvCreateSdkMessageProcessingStep ✗ Missing
HINTS:
• Custom APIs will be created without a plugin type link.
Grant 'prvAppendToPluginType' (AppendTo, plugintype, organisation scope) to the app user's security role.
• Plugin step registrations cannot be created automatically.
Grant 'prvCreateSdkMessageProcessingStep' (Create, sdkmessageprocessingstep, organisation scope) to the app user's security role.Geprüfte Privileges:
| Feature | Privilege | Bekannte ID |
|---|---|---|
| Custom API lesen | prvReadCustomAPI |
per API aufgelöst |
| Custom API anlegen | prvCreateCustomAPI |
per API aufgelöst |
| Custom API bearbeiten | prvWriteCustomAPI |
per API aufgelöst |
| Custom API löschen | prvDeleteCustomAPI |
per API aufgelöst |
| PluginType-Binding bei Create | prvAppendToPluginType |
574c053e-6488-4bfb-832a-cbc47aff8b32 |
| Plugin Steps anlegen | prvCreateSdkMessageProcessingStep |
per API aufgelöst |
Graceful Fallback für prvAppendToPluginType:
Fehlt dem App-User prvAppendToPluginType, wiederholt die CLI createCustomApi automatisch ohne das PluginTypeId@odata.bind-Feld. Die Custom API wird erfolgreich angelegt — jedoch ohne Plugin-Verknüpfung. Das Sync-Ergebnis (exec-op, exec-plan) enthält ein warning-Feld mit der Erklärung und den Handlungsoptionen:
prvAppendToPluginType(AppendTo, plugintype, Org-Ebene) der Sicherheitsrolle des App-Users vergeben, oder- das Plugin nach der Erstellung manuell im Maker Portal verknüpfen.
Publisher auflisten
Listet alle in der aktiven Dataverse-Umgebung registrierten Publisher auf. Der customizationPrefix eines Publishers muss als Präfix im uniqueName einer Custom API verwendet werden (z. B. meinprefix_MyAction).
dvc api list-publishers
# maschinenlesbar:
dvc api list-publishers --jsonAusgabe:
PREFIX FRIENDLY NAME UNIQUE NAME
───────────────────────────────────────────────────────────────────────────────
meinprefix Mein Firmen-Publisher meinfirma_publisher
contoso Contoso Default Publisher contoso_defaultJSON-Ausgabe:
{
"publishers": [
{
"publisherId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"uniqueName": "meinfirma_publisher",
"friendlyName": "Mein Firmen-Publisher",
"customizationPrefix": "meinprefix"
}
]
}Hinweis: Der Microsoft-Systempublisher (
mscrm) wird nicht angezeigt — sein Präfix kann nicht für eigene Lösungen verwendet werden.
Lokale Artefakte entfernen
Entfernt lokale Cache- und Export-Dateien. Löscht die Custom API nicht aus Dataverse.
dvc api remove
dvc api remove -n ccsm_MyApiTypischer Workflow
1. Verbindung herstellen und Privileges prüfen
# Auth-Konfiguration erstellen
cp auth.devicecode.example.json auth.json
# auth.json mit Tenant-ID und Client-ID befüllen
# Environment verbinden
dvc connect -u "https://deine-org.crm.dynamics.com"
# Optional: Privileges des App-Users prüfen
dvc api validate-privileges2. Vorhandene Custom API bearbeiten
# Verfügbare APIs auflisten
dvc api list
# API als aktiv setzen
dvc api use -n ccsm_ExistingApi
# Aktuelle Definition aus Dataverse holen
dvc api export
# JSON-Datei lokal bearbeiten (z. B. Parameter hinzufügen, Beschreibung ändern)
# Lokale Definition validieren — Fehler beheben, bevor der Plan erstellt wird
dvc api validate
# Diff mit Dataverse prüfen
dvc api diff
# Sync-Plan erstellen (validiert intern)
dvc api plan
# Dry Run
dvc api exec-plan --simulate
# Live ausführen nach Prüfung
dvc api exec-plan3. Neue Custom API anlegen
# Optional: Privileges prüfen — fehlt prvAppendToPluginType, wird das
# PluginType-Binding beim Erstellen automatisch weggelassen (+ Warning im Ergebnis)
dvc api validate-privileges
# Publisher auflisten, um den korrekten Präfix für den uniqueName zu ermitteln
dvc api list-publishers
# Neue JSON-Katalog-Datei erstellen (siehe Struktur unten)
# z. B. .cache/customapis/meinprefix_NewApi.json
# API als aktiv setzen (auch bevor sie in Dataverse existiert)
dvc api use -n meinprefix_NewApi
# Lokale Definition validieren, bevor der Plan erstellt wird
dvc api validate
# Sync-Plan erstellen
dvc api plan
# Dry Run
dvc api exec-plan --simulate
# Live ausführen
dvc api exec-plan
# Falls prvAppendToPluginType fehlte: das Ergebnis enthält eine warning —
# Plugin anschließend manuell im Maker Portal verknüpfen.Szenario: Metadaten-Probleme prüfen
Verwende dieses Vorgehen, wenn du vermutest, dass lokale JSON-Datei und Dataverse auseinander gelaufen sind — z. B. nach einer manuellen Änderung im Maker Portal.
# Sicherstellen, dass die richtige API aktiv ist
dvc api use -n ccsm_MyApi
# Lokale Definition feldgenau mit Dataverse vergleichen
dvc api check-metadata
# Bei gefundenen Abweichungen: aktuelle Remote-Definition exportieren
# (überschreibt die lokale Datei mit dem, was Dataverse hat):
dvc api export
# Oder: lokale Datei nach eigenem Willen anpassen, dann validieren und synchronisieren:
dvc api validate
dvc api plan
dvc api exec-plan --simulate
dvc api exec-planWarum vor dem Plan validieren?
api validate ist ein rein lokaler Check — keine Netzwerkverbindung, sofortiges Feedback. Es erkennt alle Probleme, die Dataverse mit einem 400-Fehler ablehnen würde, sowie Warnungen und Hinweise zu empfohlenen Formaten.
api plan läuft dieselbe Validierung intern ab und blockiert bei blockierenden Fehlern. Mit api validate vorab erhältst du den vollständigen strukturierten Report — inklusive Warnungen und Infos — bevor du versuchst, einen Plan zu erstellen.
JSON-Struktur der Custom API
{
"schemaVersion": "1.0",
"source": {
"exportedAtUtc": "2025-01-15T10:00:00.000Z",
"environmentUrl": "https://deine-org.crm.dynamics.com"
},
"customApis": [
{
"uniqueName": "ccsm_MyCustomApi",
"name": "ccsm_MyCustomApi",
"displayName": "My Custom API",
"description": "Verarbeitet einen Survey-Recipient-Datensatz.",
"bindingType": "Global",
"isFunction": false,
"isPrivate": false,
"workflowSdkStepEnabled": false,
"allowedCustomProcessingStepType": "None",
"requestParameters": [
{
"uniqueName": "RecipientId",
"name": "ccsm_MyCustomApi.RecipientId",
"displayName": "Recipient ID",
"description": "ID des Recipient-Datensatzes.",
"type": "EntityReference",
"logicalEntityName": "ccsm_surveyrecipient",
"isOptional": false
}
],
"responseProperties": [
{
"uniqueName": "Success",
"name": "ccsm_MyCustomApi.Success",
"displayName": "Success",
"description": "Gibt an, ob die Operation erfolgreich war.",
"type": "Boolean"
}
]
}
]
}Feldübersicht:
| Feld | Werte / Einschränkungen |
|---|---|
bindingType |
"Global", "Entity", "EntityCollection" |
allowedCustomProcessingStepType |
"None", "AsyncOnly", "SyncAndAsync" |
type (Parameter/Property) |
"Boolean", "DateTime", "Decimal", "Entity", "EntityCollection", "EntityReference", "Float", "Integer", "Money", "Picklist", "String", "StringArray", "Guid" |
logicalEntityName |
Nur bei Entity oder EntityReference setzen; bei allen anderen Types weglassen |
name (Parameter/Property) |
Empfohlenes Format: {customApiUniqueName}.{childUniqueName} |
uniqueName (Custom API) |
Muss Publisher-Präfix enthalten, z. B. ccsm_MyApi; nur Buchstaben, Ziffern, Unterstriche |
Voraussetzungen
- Node.js 16+
- npm
- Zugriff auf eine Dataverse-Umgebung
- Korrekte Azure AD / Microsoft Entra ID App Registration mit den benötigten API-Berechtigungen
- Ausreichende Dataverse-Berechtigungen — nach dem Verbinden mit
dvc api validate-privilegesprüfen, welche Privileges verfügbar sind
Lizenz
MIT
Autor und Kontakt
Andreas Brunsmann
- E-Mail: oss@andreasbrunsmann.de
- GitHub: https://github.com/brunsforge
- Repository: https://github.com/brunsforge/dataverse-custom-api