Claude-skill-registry dataverse-deploy
Deploy solutions, PCF controls, and web resources to Dataverse using PAC CLI
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dataverse-deploy" ~/.claude/skills/majiayu000-claude-skill-registry-dataverse-deploy && rm -rf "$T"
skills/data/dataverse-deploy/SKILL.mdDataverse Deploy
Category: Operations Last Updated: January 19, 2026 Primary Guide:
docs/guides/PCF-DEPLOYMENT-GUIDE.md
Critical Rules
These rules are MANDATORY. See PCF-DEPLOYMENT-GUIDE.md for full details.
MUST:
- ✅ MUST use unmanaged solution unless explicitly told to use managed (ADR-022)
- ✅ MUST use Dataverse publisher
with prefixSpaarkesprk_ - ✅ MUST rebuild fresh every deployment (
)npm run build:prod - ✅ MUST copy ALL 3 files to Solution folder (bundle.js, ControlManifest.xml, styles.css)
- ✅ MUST update version in ALL 5 locations
- ✅ MUST include
and.js
entries in.css[Content_Types].xml - ✅ MUST use
script (creates forward slashes in ZIP)pack.ps1 - ✅ MUST disable/restore CPM around PAC commands
🚨 CRITICAL - Control Version is the Cache Key:
- ✅ MUST update
version FIRST - this is what Dataverse uses to detect updatesControlManifest.Input.xml - ✅ MUST increment the control version for EVERY deployment, not just the solution version
- ⚠️ If you only update
but notsolution.xml
, Dataverse will NOT update the controlControlManifest.Input.xml
NEVER:
- ❌ NEVER use managed solution unless explicitly told - unmanaged is the default
- ❌ NEVER use or create a new publisher - always use
(Spaarke
)sprk_ - ❌ NEVER reuse old solution ZIPs - always pack fresh
- ❌ NEVER use
- creates backslashes, breaks importCompress-Archive - ❌ NEVER skip copying files - stale bundles cause silent failures
⚠️ AVOID (but valid fallback):
- ⚠️ AVOID
for production - rebuilds in dev mode, larger bundlespac pcf push - ✅ BUT use
when solution imports empty (see Troubleshooting)pac pcf push --publisher-prefix sprk
Deployment Workflow
Follow PCF-DEPLOYMENT-GUIDE.md for complete details.
Step 1: Build Fresh
cd src/client/pcf/{ControlName} rm -rf out/ bin/ npm run build:prod # Verify size (~200-400KB, NOT 8MB) ls -la out/controls/control/bundle.js
Step 2: Update Version (5 Locations)
| # | File | Update |
|---|---|---|
| 1 | | |
| 2 | | UI version footer |
| 3 | | |
| 4 | | |
| 5 | | |
Step 3: Copy Fresh Build to Solution
cp out/controls/control/bundle.js \ out/controls/control/ControlManifest.xml \ out/controls/control/styles.css \ Solution/Controls/sprk_Spaarke.Controls.{ControlName}/
Step 4: Pack and Import
# Disable CPM mv /c/code_files/spaarke-wt-ai-rag-pipeline/Directory.Packages.props{,.disabled} # Pack (creates fresh ZIP with forward slashes) cd Solution && powershell -ExecutionPolicy Bypass -File pack.ps1 # Import pac solution import --path bin/{SolutionName}_vX.Y.Z.zip --publish-changes # Restore CPM mv /c/code_files/spaarke-wt-ai-rag-pipeline/Directory.Packages.props{.disabled,}
Step 5: Verify
pac solution list | grep -i "{SolutionName}"
Hard refresh browser (
Ctrl+Shift+R) and verify version footer.
⚠️ Why avoid
for production? It rebuilds in development mode, ignoring production optimizations. Bundle size increases from ~300KB to 8MB. Tree-shaking is lost. However, it is a valid fallback whenpac pcf pushimports an empty solution (see "Solution Imports But Is Empty" in Troubleshooting).pac solution pack
🚨 Dataverse Control Caching (READ THIS)
Dataverse caches PCF controls aggressively. If your deployment seems to succeed but the browser still shows old behavior, the cache wasn't busted.
Root Cause
Dataverse determines whether to update a control based on the control manifest version (
ControlManifest.Input.xml), NOT just the solution version. If you:
- ✅ Update
versionsolution.xml - ❌ Forget to update
versionControlManifest.Input.xml
...then Dataverse sees the same control version and silently keeps the old bundle.
The Fix: Version Order Matters
Always update versions in this order:
- FIRST:
→control/ControlManifest.Input.xml
(THIS IS THE KEY)version="X.Y.Z" - Then rebuild (this propagates to
)out/controls/control/ControlManifest.xml - Copy all 3 files to Solution folder
- Update
→Solution/solution.xml<Version>X.Y.Z</Version> - Update
→Solution/pack.ps1$version = "X.Y.Z"
Nuclear Option: Delete and Reimport
If you've deployed but the control is still cached:
# Delete the solution completely from Dataverse pac solution delete --solution-name {SolutionName} # Reimport fresh pac solution import --path bin/{SolutionName}_vX.Y.Z.zip --publish-changes # Verify pac solution list | grep -i "{SolutionName}"
Then hard refresh browser (
Ctrl+Shift+R).
Symptoms of Caching Issue
| Symptom | Likely Cause |
|---|---|
succeeds but UI unchanged | Control version not incremented |
shows new version but old behavior | Control manifest version mismatch |
| Hard refresh doesn't help | Need delete + reimport |
| Same control works in one browser but not another | Browser cache - try incognito |
Decision Tree: Which Workflow?
Is the PCF embedded in a Custom Page? ├── YES → See "Custom Page Deployment" section in guide └── NO → Use "Deployment Workflow" above (build → copy → pack.ps1 → import)
Primary Guide:
- Complete workflow with version management and troubleshooting.docs/guides/PCF-DEPLOYMENT-GUIDE.md
Purpose
Deploy Dataverse components using PAC CLI following the PCF-DEPLOYMENT-GUIDE.md. This skill handles authentication, Central Package Management conflicts, and solution import issues.
Best Practices
| Practice | Implementation |
|---|---|
| Always use unmanaged | Never export/pack as managed unless user explicitly requests |
| Always use Spaarke publisher | Never create a new publisher - use () |
| Always build fresh | Run , never reuse old artifacts |
| Always use pack.ps1 | Never use (creates backslashes) |
| Version footer | Every PCF MUST display in the UI |
| Version bumping | Increment version in ALL 5 locations |
| Verify deployment | ALWAYS run after import to confirm version |
| Use React 16 APIs | Dataverse provides React 16.14.0 - see ADR-022 |
Key Guidance
- Prefer pack.ps1 workflow for production - build → copy files → pack.ps1 → import
- Use
as fallback when solution imports empty (Customizations.xml issue)pac pcf push - Version Locations: Update ALL 5: (1) ControlManifest.Input.xml, (2) UI footer, (3) Solution.xml, (4) extracted ControlManifest.xml, (5) pack.ps1
- Full Guide: See
for complete workflowdocs/guides/PCF-DEPLOYMENT-GUIDE.md
Prerequisites Check
Before ANY deployment operation:
# 1. Check PAC CLI is installed pac --version # 2. Check authentication status pac auth list # 3. Verify active connection points to correct environment # Look for: Active = *, Environment URL matches expected target
Expected Output
Index Active Kind Name Cloud Type Environment Environment Url [1] UNIVERSAL dev Public User HIPC DEV 2 https://hipc-dev-2.crm.dynamics.com/ [2] * UNIVERSAL prod Public User SPAARKE DEV 1 https://spaarkedev1.crm.dynamics.com/
If No Active Auth
# Create new authentication pac auth create --environment "https://YOUR-ENV.crm.dynamics.com" # Or select existing profile pac auth select --index 1
Additional Scenarios
Scenario 1: PCF Control Deployment
Use the "Deployment Workflow" section above. Follow PCF-DEPLOYMENT-GUIDE.md for the complete workflow:
- Build fresh (
)npm run build:prod - Update version in ALL 5 locations
- Copy ALL 3 files to Solution folder
- Pack with
(NOTpack.ps1
)Compress-Archive - Import with
pac solution import - Verify with
pac solution list
Scenario 1b: PCF Control with Platform Libraries (Large Controls)
Use when: PCF bundle exceeds 5MB due to bundled React/Fluent UI.
If your bundle is > 1MB, you're likely bundling React and Fluent UI. Use platform libraries so Dataverse provides these at runtime.
Check Bundle Size
ls -la out/controls/*/bundle.js # Should be 300-500KB with platform libraries, 5MB+ without
Fix ControlManifest.Input.xml
Add
<platform-library> elements:
<resources> <code path="index.ts" order="1" /> <!-- Host-provided: DO NOT bundle React/Fluent --> <platform-library name="React" version="16.14.0" /> <platform-library name="Fluent" version="9.46.2" /> </resources>
Create featureconfig.json (CRITICAL)
pcf-scripts requires a feature flag to externalize ReactDOM:
{ "pcfReactPlatformLibraries": "on" }
Without this file, React is externalized but ReactDOM is still bundled, causing React version mismatch errors at runtime.
Enable Custom Webpack for Icon Tree-Shaking (CRITICAL for large bundles)
If your bundle is still large (>500KB) after adding platform libraries,
@fluentui/react-icons is likely not tree-shaking. The full icon library is ~6.8MB.
Solution: Enable custom webpack with
sideEffects: false for icons:
- Update featureconfig.json (add
):pcfAllowCustomWebpack
{ "pcfReactPlatformLibraries": "on", "pcfAllowCustomWebpack": "on" }
- Create webpack.config.js in control root:
// Custom webpack configuration for PCF // Enables tree-shaking for @fluentui/react-icons module.exports = { optimization: { usedExports: true, sideEffects: true, innerGraph: true, providedExports: true }, module: { rules: [ { // Mark @fluentui/react-icons as side-effect-free test: /[\\/]node_modules[\\/]@fluentui[\\/]react-icons[\\/]/, sideEffects: false } ] } };
Result: Bundle size typically drops from 5-9MB to 200-400KB.
⚠️ NOTE:
rebuilds in development mode, ignoring these optimizations. Use the pack.ps1 workflow instead to preserve production build.pac pcf push
Fix package.json
Move React/Fluent to
devDependencies (type-checking only):
{ "devDependencies": { "@types/react": "^16.14.0", "@types/react-dom": "^16.9.0", "react": "^16.14.0", "react-dom": "^16.14.0", "@fluentui/react-components": "^9.46.0" } }
Note: Use React 16 types to match the platform runtime. See ADR-022.
Remove from
dependencies: any react, react-dom, or @fluentui/react-* packages (keep in devDependencies only).
Full details: See
docs/guides/PCF-DEPLOYMENT-GUIDE.md
Scenario 1c: PCF Control in Custom Page (COMPLEX)
Use when: PCF control is embedded in a Canvas App Custom Page.
⚠️ WARNING: This is the most complex deployment scenario. When a PCF is used inside a Custom Page, THREE version locations must stay synchronized.
See detailed guide:
(Custom Page section)docs/guides/PCF-DEPLOYMENT-GUIDE.md
Quick Summary
| Location | Purpose | Updated By |
|---|---|---|
| Dataverse Registry | Master version | or Solution Import |
| Solution Controls Folder | Exported artifact | Manual copy |
| Canvas App Embedded | Runtime bundle | Manual copy + |
The Problem
When you open a Custom Page in Power Apps Studio, it may downgrade your PCF if the Dataverse Registry has an older version.
Key Rules
- ALWAYS update Dataverse Registry FIRST before opening Power Apps Studio
- ALWAYS copy bundle to BOTH locations (Controls folder AND Canvas App embedded)
- NEVER open Power Apps Studio until all three locations are synchronized
Scenario 1d: PCF Production Release (Full Solution Workflow)
Use when: Deploying a PCF update with proper version tracking for production.
⚠️ CRITICAL:
does NOT update your named solution's version. Use this workflow for production releases.pac pcf push
See detailed guide: docs/guides/PCF-DEPLOYMENT-GUIDE.md
Why This Workflow?
| Method | Updates Dataverse Control | Updates Named Solution Version | Best For |
|---|---|---|---|
| ✅ Yes | ❌ No (creates temp solution) | Dev testing |
| Solution Import | ✅ Yes | ✅ Yes | Production releases |
Quick Steps
# 1. Build cd src/client/pcf/{ControlName} npm run build:prod # 2. Update version in 4 locations (manual) # 3. Copy bundle to solution folder cp out/controls/control/bundle.js \ infrastructure/dataverse/solutions/{Solution}/Controls/{namespace}.{ControlName}/ # 4. Pack and import pac solution pack --zipfile Solution_vX.Y.Z.zip --folder {Solution} pac solution import --path Solution_vX.Y.Z.zip --publish-changes # 5. Verify pac solution list | grep -i "{SolutionName}"
Scenario 2: Solution Export
Use when: Backing up or extracting a solution for modification.
# List available solutions pac solution list # Export unmanaged solution (ALWAYS use unmanaged) pac solution export --name "{SolutionName}" --path "./{SolutionName}.zip" --managed false
⚠️ NEVER export as managed unless the user explicitly requests it. Managed solutions have caused issues in past projects.
Scenario 3: Solution Import
Use when: Deploying a solution package to an environment.
# Import and publish in one step pac solution import --path "./{SolutionName}.zip" --publish-changes # Force import (overwrites conflicts) pac solution import --path "./{SolutionName}.zip" --force-overwrite --publish-changes
Post-Import Verification
# Check solution was imported pac solution list | grep -i "{SolutionName}" # If not auto-published, publish manually pac solution publish
Scenario 4: Web Resource Deployment
Use when: Deploying JavaScript, CSS, or image files.
Include web resources in a solution and use Scenario 3.
# Export solution containing web resources pac solution export --name "SpaarkeCore" --path "./SpaarkeCore.zip" --managed false # Extract, modify, repack, import unzip SpaarkeCore.zip -d SpaarkeCore_extracted # ... modify files in WebResources folder ... pac solution pack --zipfile SpaarkeCore_modified.zip --folder SpaarkeCore_extracted pac solution import --path SpaarkeCore_modified.zip --publish-changes
Scenario 5: Publish Customizations
Use when: Making customizations visible to users.
# Publish all customizations pac solution publish # Or use publish-all for everything pac solution publish-all
Conventions
Publisher
Always use
publisher with prefix Spaarke
- NEVER create a new publisher.sprk_
| Setting | Value |
|---|---|
| Unique Name | |
| Prefix | |
| Option Value Prefix | |
Naming examples:
- PCF controls:
sprk_Spaarke.Controls.{ControlName} - Web resources:
sprk_FileName.js - Entities:
sprk_entityname - Fields:
sprk_fieldname
Working Directories
| Component Type | Directory |
|---|---|
| PCF Controls | |
| Web Resources JS | |
| Ribbon XML | |
| Solutions | |
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Not logged in | Run |
| Directory.Packages.props conflict | Disable CPM before PAC commands |
| File lock during cleanup | Harmless - import the packed solution directly |
| Wrong solution name | Run to find exact name |
| Wrong publisher prefix | Use |
| Missing dependent solution | Import dependencies first |
| React/Fluent bundled | Use platform libraries (Scenario 1b) |
| PCF version regresses in Custom Page | Registry has older version | Update registry FIRST (Scenario 1c) |
solutions appear | Created by | Delete after deployment if needed |
| Using React 18 APIs with React 16 runtime | Use , not - see ADR-022 |
| Importing from | Import from instead |
| Solution zip not created | pack.ps1 failed | Check pack.ps1 script exists and run manually |
| Namespace changed or old controls exist | Delete orphaned controls via Web API (see below) |
| styles.css not copied to solution folder | Copy ALL 3 files to Solution folder |
| Solution import succeeds but UI shows old behavior | Control manifest version not updated | Update ControlManifest.Input.xml version FIRST, rebuild, then deploy |
| Deployment seems stuck on old version | Dataverse control cache | Delete solution with , then reimport fresh |
| Solution imports but shows 0 components | Empty | Use fallback (see below) |
🚨 Solution Imports But Is Empty (0 Components)
Symptoms:
succeedspac solution import
shows solution with correct versionpac solution list- But solution in portal shows "All: 0" - no components
- PCF control doesn't appear or doesn't update
Root Cause:
The
Customizations.xml file in your Solution folder has empty component sections:
<CustomControls /> <WebResources />
When you run
pac solution pack, it packs exactly what's in Customizations.xml. If the component references are missing, you get an empty solution that imports but does nothing.
Solution - Use
as Fallback:pac pcf push
cd src/client/pcf/{ControlName} # pac pcf push generates proper Customizations.xml automatically pac pcf push --publisher-prefix sprk # If you get file lock error during cleanup, ignore it - solution is already packed # Import the generated solution pac solution import --path "out/PowerAppsTools_sprk/bin/Debug/PowerAppsTools_sprk.zip" --publish-changes # Verify pac solution list | grep -i "{ControlName}"
Why This Works:
pac pcf push generates a proper Customizations.xml with component references:
<CustomControls> <CustomControl> <Name>sprk_Spaarke.Controls.UniversalDocumentUpload</Name> <FileName>/Controls/sprk_Spaarke.Controls.UniversalDocumentUpload/ControlManifest.xml</FileName> </CustomControl> </CustomControls>
Prevention:
After using
pac pcf push successfully:
- Examine the generated
and update your Solution folder's version to match the structureCustomizations.xml - IMPORTANT:
bypasses the Solution folder entirely - it builds from source and deploys directly to Dataverse. You must still copy the fresh bundle.js and ControlManifest.xml to your Solution folder to keep it in sync for futurepac pcf push
deployments.pac solution pack
# After pac pcf push, sync Solution folder: cp out/controls/control/bundle.js \ out/controls/control/ControlManifest.xml \ Solution/src/WebResources/sprk_Spaarke.Controls.{ControlName}/
This prevents the Solution folder from becoming stale and ensures future solution imports work correctly.
Orphaned Control Cleanup
When namespace changes (e.g.,
Spaarke.PCF → Spaarke.Controls) or old deployments exist, orphaned controls can block new deployments.
Symptoms:
- Deployment fails with duplicate component errors
- Multiple versions of same control in solution list
- "Component with same name already exists" errors
Solution - Delete via Web API:
# 1. Find the orphaned control's ID # Use Dataverse Web API or Advanced Find # 2. Delete using PAC CLI or Web API pac org fetch --filter "customcontrolid eq 'GUID-HERE'" # 3. Or use Power Platform Admin Center: # - Go to Environments → Your Environment → Settings → Solutions # - Find and delete orphaned components
Prevention:
- Always use consistent namespace (e.g.,
)Spaarke.Controls - Delete old solutions before changing namespace
- Use
to cleanly remove old solutionspac solution delete
Quick Reference Commands
# Authentication pac auth list # Show all auth profiles pac auth create --environment "URL" # Create new profile pac auth select --index N # Switch profile # Solutions pac solution list # List all solutions pac solution export --name X --path Y # Export solution (add --managed false) pac solution import --path Y # Import solution pac solution publish # Publish customizations # PCF Controls - Use pack.ps1 workflow (see Deployment Workflow above) npm run build:prod # Build control powershell -File Solution/pack.ps1 # Pack solution (NOT Compress-Archive) pac solution import --path bin/X.zip # Import solution # Troubleshooting pac org who # Show current org info pac solution check --path Y # Validate solution before import
Related Skills
- Ribbon customizations use solution export/import workflowribbon-edit
- Naming conventions for all Dataverse componentsspaarke-conventions
- ADR-006 governs PCF control patterns, ADR-022 governs React versionadr-aware
- CI/CD pipeline status and automated deployment workflowsci-cd
CI/CD Integration
Automated Plugin Deployment via GitHub Actions
Plugin deployments can be automated via the
deploy-staging.yml workflow:
| Workflow | Trigger | What It Deploys |
|---|---|---|
| Auto (after CI passes on master) or Manual | Dataverse plugins via PAC CLI |
Workflow Plugin Deployment
The
deploy-plugins job in deploy-staging.yml:
- Downloads build artifacts from CI
- Authenticates with Power Platform using service principal
- Deploys plugin assembly via PAC CLI
pac auth create --url $POWER_PLATFORM_URL --applicationId $CLIENT_ID --clientSecret $SECRET pac plugin push --path ./artifacts/publish/plugins/Spaarke.Plugins.dll
When to Use Manual vs Automated
| Scenario | Use |
|---|---|
| Plugin code changes merged to master | Automated () |
| PCF control iterative development | Manual (this skill - Quick Dev Deploy) |
| Production solution release | Manual (this skill - Scenario 1d) |
| Custom Page updates | Manual (this skill - Scenario 1c) |
| Emergency hotfix | Manual (this skill) |
Required Secrets for Automated Deployment
| Secret | Purpose |
|---|---|
| Dataverse environment URL |
| Service principal app ID |
| Service principal secret |
Monitor Automated Deployments
# View staging deployment status gh run list --workflow=deploy-staging.yml # View specific deployment run gh run view {run-id} # Check deploy-plugins job gh run view {run-id} --log --job=deploy-plugins
Manual Trigger of Plugin Deployment
# Trigger staging deployment with plugins gh workflow run deploy-staging.yml -f deploy_plugins=true
Related ADRs
| ADR | Relevance |
|---|---|
| ADR-006 | PCF over legacy webresources |
| ADR-022 | React 16 compatibility (CRITICAL) |
| ADR-021 | Fluent UI v9 design system |
Resources
| Resource | Purpose |
|---|---|
| Primary guide - Consolidated PCF deployment workflow |
Tips for AI
🚨 CRITICAL: Control Manifest Version is the Cache Key
When deploying PCF updates, the #1 cause of "deployment succeeded but nothing changed" is forgetting to update the control manifest version. Follow this exact order:
- FIRST: Update
version attributecontrol/ControlManifest.Input.xml - THEN: Rebuild with
(ornpm run build:prod
)pcf-scripts build --buildMode production - THEN: Copy ALL 3 files to Solution folder
- THEN: Update
andsolution.xml
versionspack.ps1 - THEN: Pack and import
If the user reports the control isn't updating after deployment:
- Check if
version was incrementedControlManifest.Input.xml - If not, increment it, rebuild, and redeploy
- If still stuck, use the nuclear option:
then reimportpac solution delete
Never assume that updating
solution.xml alone will bust the cache. The control manifest version is what Dataverse checks.
🚨 CRITICAL: Solution Imports But Shows 0 Components
If
pac solution pack + pac solution import succeeds but the solution is empty in the portal:
- Root cause:
has emptyCustomizations.xml
section<CustomControls /> - Fix: Use
as fallbackpac pcf push --publisher-prefix sprk - Why it works:
auto-generates proper component referencespac pcf push - File lock error during cleanup is harmless - solution is already packed, import it directly
- CRITICAL: After
, sync the Solution folder -pac pcf push
bypasses it entirely, leaving it stale:pac pcf pushcp out/controls/control/bundle.js out/controls/control/ControlManifest.xml \ Solution/src/WebResources/sprk_Spaarke.Controls.{ControlName}/ - Prevention: Copy the generated
structure to your Solution folder for futureCustomizations.xml
deploymentspac solution pack