ribbon-edit
Edit Dataverse ribbon customizations via solution export/import
Files1
View on GitHubSKILL.md.claude/skills/ribbon-edit
GitHubRibbon Edit
Category: Development Last Reviewed: 2026-05-16 Reviewed By: ai-procedure-quality-r1 (Phase 2b Wave 2b-A — normalized minimal frontmatter) Exemplar rationale: Ribbon XML edits are project-specific and platform-dependent (RibbonDiffXml schema varies per entity).
Purpose
Automate editing Dataverse Application Ribbon customizations by exporting solutions, modifying the RibbonDiffXml in customizations.xml, and re-importing. Bypasses the complex XrmToolBox Ribbon Workbench visual editor.
Applies When
- User requests ribbon customization (flyout menus, buttons, commands)
- Adding JavaScript-triggered ribbon actions
- Modifying Application Ribbon (global ribbon visible on all forms/views)
- Entity-specific ribbon changes
- Trigger phrases: "edit ribbon", "add ribbon button", "ribbon customization", "command bar button"
Prerequisites
- PAC CLI authenticated: Run
pac auth listto verify connection - Dedicated ribbon solution exists (see Solution Setup below)
- Web resources deployed: Any JS libraries and icons must be deployed before ribbon import
Critical Requirements
The Alt Attribute
CRITICAL: The Alt attribute is REQUIRED on all Button and FlyoutAnchor elements. Without it, the solution may fail to publish.
<!-- ✅ Correct -->
<Button Alt="$LocLabels:sprk.Feature.Button.Alt" Id="sprk.Feature.Button" ... />
<!-- ❌ Wrong - missing Alt -->
<Button Id="sprk.Feature.Button" ... />
Icon Attributes for Modern UCI
For icons to display correctly in the modern command bar:
| Attribute | Format | Notes |
|---|---|---|
Image16by16 |
$webresource:prefix_Icon16.svg |
Use .svg extension |
Image32by32 |
$webresource:prefix_Icon32.svg |
Use .svg extension |
ModernImage |
$webresource:prefix_Icon32.svg |
Required for UCI - use 32px SVG |
Example:
<FlyoutAnchor
Alt="$LocLabels:sprk.ThemeMenu.Alt"
Image16by16="$webresource:sprk_ThemeMenu16.svg"
Image32by32="$webresource:sprk_ThemeMenu32.svg"
ModernImage="$webresource:sprk_ThemeMenu32.svg"
... />
SVG Best Practices
Recommended SVG structure for ribbon icons:
<!-- sprk_IconName.svg - Use this structure -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="..."/>
</svg>
Requirements:
- ✅ Use
fill="currentColor"- adapts to light/dark mode automatically - ✅ Use
viewBox="0 0 20 20"- Modern UI standard size - ✅ Keep paths simple - complex SVGs render poorly at small sizes
- ❌ Do NOT include
widthorheightattributes (causes icon not to show) - ❌ Do NOT use hardcoded colors (breaks dark mode)
MenuSection DisplayMode
Always include DisplayMode="Menu16" on MenuSection elements:
<MenuSection Id="sprk.Feature.MenuSection"
Title="$LocLabels:sprk.Feature.Title"
Sequence="10"
DisplayMode="Menu16">
Solution Setup (One-Time)
Ribbon Workbench and solution import require a dedicated solution containing ONLY the ribbon component - no other entities, web resources, or components.
For Application Ribbon (Global - all forms/views)
Create new unmanaged solution in Power Apps maker portal:
- Name:
ApplicationRibbon(or similar) - Publisher: Spaarke (default publisher - required)
- Version:
1.0.0.0 - Must be unmanaged (not managed)
- Name:
Add Application Ribbon component:
- In the solution, click Add existing → More → Other → Application Ribbons
- Select "Application Ribbon" and add it
- Do NOT add any other components (entities, web resources, etc.)
Publish the solution
For Entity-Specific Ribbon
Create new unmanaged solution:
- Name:
{EntityName}Ribbon(e.g.,DocumentRibbons,MatterRibbons) - Publisher: Spaarke (default publisher - required)
- Must be unmanaged (not managed)
- Name:
Add ONLY the entity:
- Click Add existing → Table
- Select the entity → Add (select "Include table metadata" only, no subcomponents)
- Do NOT add forms, views, or other components
Publish the solution
Why dedicated solutions? Ribbon Workbench and solution import merge RibbonDiffXml. Including other components can cause conflicts or unintended overwrites.
Why Spaarke publisher? Using the default "Spaarke" publisher ensures consistent prefixes (
sprk_) across all components and avoids conflicts when deploying between environments.
Workflow
Step 1: Export Dedicated Ribbon Solution
Export the dedicated ribbon solution created in Solution Setup (not a solution with other components):
# Create temp directory
New-Item -ItemType Directory -Force -Path "infrastructure\dataverse\ribbon\temp"
# List solutions to find exact name
pac solution list | Select-String -Pattern "ribbon" -CaseSensitive:$false
# Export the dedicated ribbon solution
pac solution export --name {RibbonSolutionName} --path "infrastructure\dataverse\ribbon\temp\{RibbonSolutionName}.zip" --managed false
Common solution names:
ApplicationRibbon- Global ribbon (all forms/views)DocumentRibbons- sprk_document entity ribbonMatterRibbons- sprk_matter entity ribbon
Step 2: Extract Solution
# Extract the solution zip
Expand-Archive -Path "infrastructure\dataverse\ribbon\temp\{SolutionName}.zip" -DestinationPath "infrastructure\dataverse\ribbon\temp\{SolutionName}_extracted" -Force
Step 3: Edit customizations.xml
Locate {SolutionName}_extracted/customizations.xml and find the <RibbonDiffXml> section.
Structure:
<RibbonDiffXml>
<CustomActions>
<!-- UI elements (FlyoutAnchor, Button, etc.) -->
</CustomActions>
<Templates>
<RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
</Templates>
<CommandDefinitions>
<!-- Command definitions with JavaScriptFunction actions -->
</CommandDefinitions>
<RuleDefinitions>
<TabDisplayRules />
<DisplayRules />
<EnableRules>
<!-- Enable rules for commands -->
</EnableRules>
</RuleDefinitions>
<LocLabels>
<!-- Localized labels - REQUIRED for Alt attributes -->
</LocLabels>
</RibbonDiffXml>
Step 4: Re-package Solution
# Re-package the modified solution
Compress-Archive -Path "infrastructure\dataverse\ribbon\temp\{SolutionName}_extracted\*" -DestinationPath "infrastructure\dataverse\ribbon\temp\{SolutionName}_modified.zip" -Force
Step 5: Import Solution
pwsh scripts/Import-SolutionScoped.ps1 -EnvironmentUrl <url> -ZipPath "infrastructure\dataverse\ribbon\temp\{SolutionName}_modified.zip" -SolutionUniqueName {SolutionName} # publishes the ribbon's entity only; the effective ribbon can lag the publish by up to about 75 s
Step 6: Clean Up
# Option A: Archive the original export for rollback (recommended)
Move-Item "infrastructure\dataverse\ribbon\temp\{SolutionName}.zip" "infrastructure\dataverse\ribbon\{SolutionName}\{SolutionName}_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').zip"
# Option B: Delete temp folder entirely (only after confirming import worked)
Remove-Item -Path "infrastructure\dataverse\ribbon\temp" -Recurse -Force
Tip: Keep at least one backup of the original export before cleaning up. This allows rollback if issues are discovered later.
Conventions
Ribbon Element IDs
Use publisher prefix and descriptive names:
{prefix}.{Feature}.{ElementType}- e.g.,sprk.ThemeMenu.FlyoutAnchor
CustomAction Locations
| Location | Description |
|---|---|
Mscrm.GlobalTab.MainTab.More._children |
"More Commands" overflow menu |
Mscrm.HomepageGrid.{entity}.MainTab.Actions.Controls._children |
Entity homepage grid actions |
Mscrm.SubGrid.{entity}.MainTab.Management.Controls._children |
Subgrid management buttons |
Mscrm.Form.{entity}.MainTab.Save.Controls._children |
Entity form save group |
Note:
{entity}must be lowercase logical name (e.g.,sprk_document, notsprk_Document)
Web Resource References
Image16by16="$webresource:prefix_IconName16.svg"
Image32by32="$webresource:prefix_IconName32.svg"
ModernImage="$webresource:prefix_IconName32.svg"
Library="$webresource:prefix_ScriptName.js"
JavaScript Function Pattern
<JavaScriptFunction Library="$webresource:prefix_Script.js"
FunctionName="Namespace.Function.name">
<StringParameter Value="paramValue" />
<CrmParameter Value="PrimaryControl" />
</JavaScriptFunction>
CrmParameter Reference (CRITICAL)
Only use values from this table. Invalid values corrupt the entity's active ribbon — the only fix is deleting and recreating the entity.
| CrmParameter Value | Context | JS Receives | Use For |
|---|---|---|---|
PrimaryControl |
Form | formContext (Xrm.Controls.FormContext) |
Form command bar buttons |
SelectedControl |
Grid/Subgrid | gridControl (grid context object) |
Grid/subgrid buttons — extract rows via gridControl.getGrid().getSelectedRows() |
SelectedControlSelectedItemReferences |
Grid/Subgrid | selectedItems[] (array of {Id, Name, TypeId}) |
Grid buttons needing item references directly (less common) |
PrimaryItemIds |
Grid | selectedIds[] (array of GUIDs) |
Bulk operations on IDs only |
FirstPrimaryItemId |
Grid | firstId (single GUID string) |
Single-selection grid actions |
CommandValueId |
Any | Command value | Advanced scenarios |
OrgName |
Any | Organization name | Multi-org scenarios |
OrgLcid |
Any | Language code | Localization |
Grid Button Pattern (Recommended):
<!-- Command: pass SelectedControl to JS -->
<JavaScriptFunction Library="$webresource:sprk_script.js" FunctionName="Sprk.Feature.gridAction">
<CrmParameter Value="SelectedControl" />
</JavaScriptFunction>
<!-- Enable Rule: use SelectionCountRule (native XML, no JS needed) -->
<EnableRule Id="Sprk.Feature.Grid.EnableRule">
<SelectionCountRule AppliesTo="SelectedEntity" Minimum="1" Maximum="100" />
</EnableRule>
Form Button Pattern (Recommended):
<!-- Command: pass PrimaryControl to JS -->
<JavaScriptFunction Library="$webresource:sprk_script.js" FunctionName="Sprk.Feature.formAction">
<CrmParameter Value="PrimaryControl" />
</JavaScriptFunction>
<!-- Enable Rule: use CustomRule with Default="true" -->
<EnableRule Id="Sprk.Feature.Form.EnableRule">
<CustomRule Library="$webresource:sprk_script.js" FunctionName="Sprk.Feature.enableButton" Default="true">
<CrmParameter Value="PrimaryControl" />
</CustomRule>
</EnableRule>
WARNING:
SelectedItemReferences(without theSelectedControlprefix) is NOT a valid CrmParameter value. Using it will corrupt the entity ribbon. Always useSelectedControlSelectedItemReferencesif you need item references, or preferably useSelectedControland extract rows in JS.
Resources
| Resource | Purpose |
|---|---|
docs/_archive/articles/RIBBON-COMMAND-BAR-MODIFICATIONS.md |
Full templates and location reference |
docs/_archive/articles/document-ribbon-example.xml |
Working Dark Mode menu example |
infrastructure/dataverse/ribbon/ |
Ribbon XML templates and documentation |
src/client/webresources/js/ |
JavaScript web resources |
src/client/assets/icons/ |
SVG icons for ribbon buttons |
Output Format
## Ribbon Edit Results
**Solution**: {SolutionName}
**Action**: {Added|Modified|Removed} {ElementType}
**Elements**:
- {Element1 Id}
- {Element2 Id}
**Status**: ✅ Imported and published successfully
Examples
Adding a Flyout Menu
Input: "Add a Theme flyout menu to the Entity Ribbon"
Steps:
- Export entity ribbon solution
- Add FlyoutAnchor with:
Altattribute (required)Image16by16,Image32by32,ModernImage(all SVG)- Menu/MenuSection with
DisplayMode="Menu16" - Buttons with
Altattribute
- Add CommandDefinitions with JavaScriptFunction actions
- Add EnableRules
- Add all required LocLabels
- Re-import solution
Error Handling
| Situation | Response |
|---|---|
pac auth list shows no connections |
Run pac auth create to authenticate to Dataverse |
| Solution export fails with "not found" | Verify exact solution name with pac solution list |
| Import fails with "web resource not found" | Deploy missing JS/icon web resources first, then retry import |
| Import fails with "duplicate ID" | Check for conflicting ribbon elements; use unique sprk. prefixed IDs |
| Import fails with validation error | Ensure all Alt attributes are present on buttons/flyouts |
| Icons show as placeholder (jigsaw) | Use SVG web resources with .svg extension in ModernImage |
| Icon not showing at all | Remove width/height from SVG; use only viewBox |
| Icon wrong color in dark mode | Use fill="currentColor" instead of hardcoded colors |
| Ribbon not appearing after import | Re-run scripts/Import-SolutionScoped.ps1 (publishes the ribbon's entity only), then wait up to about 75 s; never a tenant-wide publish |
| XML parsing error | Validate customizations.xml structure; check for unclosed tags |
| Changes not visible in app | Clear browser cache; try Ctrl+F5 hard refresh |
| Subgrid button not appearing | Use Mscrm.SubGrid.{entity} location, not Mscrm.HomepageGrid |
| Subgrid JS error: undefined | Use SelectedControl CrmParameter, not PrimaryControl |
| HideCustomAction not working | Verify HideActionId matches exact original <CustomAction> Id |
| Invalid CrmParameter corrupted ribbon | Entity ribbon is unrecoverable — must delete entity, recreate via script, and reimport clean ribbon solution |
| Grid button: "Selected items: undefined" | JS function received grid context but expected array — use gridControl.getGrid().getSelectedRows() to extract items |
| Grid refresh error after action | Use gridControl.refresh() not Xrm.Utility.refreshParentGrid() |
SelectedControlSelectedItemReferences not recognized |
Older Dataverse environments may not support this CrmParameter — use SelectedControl instead and extract rows in JS (recommended approach) |
| Full solution import locked system | NEVER import full SpaarkeCore solution for ribbon work — always use a dedicated small solution containing only the entity |
| Import hangs / system locked | Wait for import to complete (can take 10+ minutes for large solutions); cannot cancel mid-import |
Related Skills
adr-aware- Automatically loads ADR-006 (PCF over webresources) when working with ribbon JSspaarke-conventions- Naming conventions for ribbon element IDs and web resources
Tips for AI
- Always include
Altattribute on all buttons and flyout anchors - Always use SVG for
ModernImageattribute with.svgextension - Use
ModernImagewith 32.svg for FlyoutAnchor, 16.svg for menu buttons - Include
DisplayMode="Menu16"on MenuSection elements - Always verify web resources exist before importing ribbon (icons, JS files)
- Use
pac solution listto find exact solution names - Keep a backup of the original exported solution
- Test ribbon changes in DEV before promoting to higher environments
- The
Sequenceattribute controls button order (lower = earlier) - Use
Default="true"on EnableRules to show button while JS loads - When duplicating ribbon elements for multiple entities, use pattern:
sprk.{Feature}.{EntityName}.{ElementType} TemplateAlias="o1"is required for buttons to appear in the overflow menu- For subgrids, use
SelectedControlCrmParameter instead ofPrimaryControl - NEVER use
SelectedItemReferences— it is NOT a valid CrmParameter value and will corrupt the entity ribbon - Prefer
SelectedControloverSelectedControlSelectedItemReferences—SelectedControlSelectedItemReferencesis not recognized by older Dataverse environments;SelectedControlworks everywhere - Grid enable rules: prefer
SelectionCountRule(native XML) overCustomRule(JS-dependent) for grid buttons - NEVER import full SpaarkeCore for ribbon work — use a dedicated small solution containing only the target entity
- Always validate CrmParameter values against the reference table in this skill before importing
- Reference
docs/_archive/articles/RIBBON-COMMAND-BAR-MODIFICATIONS.mdfor HideCustomAction and subgrid details - Reference RIBBON-COMMAND-BAR-MODIFICATIONS.md for templates
- Reference RIBBON-WORKBENCH-HOW-TO-ADD-BUTTON.md for full button guide with examples
Failure Modes & Recovery
| Failure | Cause | Prevention / Recovery |
|---|---|---|
| Imported full SpaarkeCore solution for a ribbon tweak — replaced unrelated customizations | Operator skipped the "use a dedicated small solution" Critical Requirement | NEVER edit ribbons via SpaarkeCore. Create a small solution containing ONLY the target entity. Export, edit, import. |
| Ribbon import succeeds but button doesn't appear | RibbonDiffXml syntax was accepted but failed to resolve a reference (e.g., wrong LocId, LabelText, or CrmParameter) |
Validate CrmParameter values BEFORE import (reference table in this skill). Test in a sandbox environment first. |
| Button appears but click does nothing | JavaScript function name in CommandDefinition doesn't match the actual function in the web resource |
After import, hard-refresh browser. If still broken, verify the FunctionName attribute matches a real export in your JS web resource (case-sensitive). |
Ribbon edit lost on next pac solution export |
Edits were made in Customizations.xml AFTER export but never re-imported |
Treat the imported solution as the source of truth. Re-export AFTER importing edits if you want to capture them in your solution folder. |
| Solution import fails with "RibbonDiffXml syntax error" | XML editor corrupted whitespace, comments, or encoded characters | Use a validating XML editor. Verify well-formedness with xmllint --noout before import. |