Claude Code (Part 13)
Neil Haddley • June 3, 2026
Building a Claude Code skill that generates Dataverse solution XML for Power Platform
I built a Claude Code skill called dv-generate that generates Dataverse solutions for Power Platform. It's published on GitHub. The skill installs with a single curl command.
BASH
1curl -fsSL https://raw.githubusercontent.com/Haddley/dv-generate/main/install.sh | bash

I ran `/skills` in Claude Code and saw there were no skills installed yet

I ran the install script and watched it clone the dv-generate plugin and register it

After restarting Claude Code, `/skills` now showed the `dv-generate` skill listed and active
With the skill installed, I gave Claude a prompt to generate a Todo model-driven app.
PROMPT
1Create a Dataverse solution for a simple Todo model-driven app with a Todo table. The table should include the following fields: 2 3Title (required text field) 4Description (multiline text) 5Due Date (date field) 6Priority (choice field with options: Low, Medium, High) 7Completed (yes/no boolean field) 8Generate the solution as a managed solution .zip file that I can import directly into my Dataverse environment.

Claude ran my prompt and asked which publisher prefix to use for the solution
![Claude created `[Content_Types].xml` and `solution.xml` and asked me to confirm the file creation](/assets/images/claudecode13/Screenshot-2026-06-04-at-12.47.38-PM.png)
Claude created `[Content_Types].xml` and `solution.xml` and asked me to confirm the file creation

The `solution.xml` file opened in VS Code with my publisher prefix `neil` applied throughout

Claude moved on to writing the `customizations.xml` file containing the entity definitions

The `customizations.xml` opened in VS Code showing the `neil_todo` table and all its fields

Claude generated `pack.sh`, a script to zip the solution files into a ready-to-import package

Claude asked permission to run `pack.sh` to build the solution zip

The build completed — `NeilTodoApp.zip` at 5.7 KB, ready to import, with a full field schema summary
I took the zip file to Power Platform and imported it as a managed solution.

I opened the Power Platform admin center and selected `NeilTodoApp.zip` to import

The Import a solution dialog confirmed the file was selected and I clicked Next

The import details showed the solution name, managed type, publisher, and version — I clicked Import
The import completed and the model-driven app was ready to use.

The Todo App model-driven app opened in Power Apps showing an empty Active Todos view

I created a new Todo record with a title, due date, priority, and completed status

The record saved and appeared in the Active Todos list — the app worked exactly as designed
How the Skill Works
A Claude Code skill is a Markdown file placed in ~/.claude/skills/<skill-name>/. When the skill is active, its content is injected into Claude's context whenever a matching task is detected — in this case, whenever someone asks Claude to create a Dataverse solution.
The skill doesn't contain any executable code. It contains knowledge: templates, rules, and gotchas that Claude needs to generate valid Dataverse XML. The frontmatter description field tells Claude when to invoke it; the body tells Claude exactly how to do the work.
The full SKILL.md for dv-generate is below. The bulk of it is XML templates and a list of import errors I ran into while building it — things like the correct element order inside <entity>, the right casing for option set labels, and why boolean fields render read-only if you omit disabled="false" on the form control.
Skill
MARKDOWN
1--- 2name: dv-generate 3description: Generate source-controlled Dataverse solution XML files (customizations.xml, solution.xml, pack.sh) without a live environment. Use when the user wants to create a new solution from scratch, add tables/fields/views/forms, or scaffold a model-driven app with AppModule and SiteMap. Always ask for the publisher prefix before generating any XML. 4--- 5 6# Skill: Dataverse Solution Generation (Source-Controlled XML) 7 8Generate zip-importable Power Platform solutions from plain XML files on disk. No live environment required during authoring. Import via `pac solution import` or make.powerapps.com. 9 10--- 11 12## Publisher prefix — ask first 13 14**Always confirm the publisher prefix before generating any XML.** The prefix is prepended to every table, column, option set, app, and site map schema name. It is set once per publisher and cannot be changed after components are created. 15 16Ask the user: 17> "What publisher prefix should I use? (e.g., `contoso`, `sa`, `lit` — 2–8 lowercase letters, no numbers or hyphens)" 18 19Then substitute `{{prefix}}_` everywhere in the templates below. 20 21**Never use `new_`** — it is the default Microsoft prefix and causes naming collisions. 22 23## Managed vs unmanaged 24 25**Default to managed (`<Managed>1</Managed>`) unless the user explicitly asks for an unmanaged solution.** Managed solutions are the standard for distribution and production deployment. 26 27If the user asks for an unmanaged solution (e.g., "for development", "unmanaged", "I want to edit it after import"), use `<Managed>0</Managed>`. Unmanaged solutions can be re-imported as simple overwrites without the staged upgrade process, which avoids the `ImportAsHolding` / statecode deletion errors that occur when iterating during development. 28 29The publisher block in `solution.xml` also needs: 30- `<UniqueName>` — a PascalCase identifier for the publisher (e.g., `ContosoInc`) 31- `<CustomizationPrefix>` — the lowercase prefix (e.g., `contoso`) 32- `<CustomizationOptionValuePrefix>` — a unique integer (e.g., `10000`); must not clash with other publishers in the same environment 33 34--- 35 36## Skill boundaries 37 38| Need | Use instead | 39|---|---| 40| Export/import/pack/unpack via PAC CLI | **dv-solution** | 41| Query or read Dataverse records | **dv-query** | 42| Create/update records via API | **dv-data** | 43| Connect to Dataverse / configure auth | **dv-connect** | 44 45--- 46 47## File Structure 48 49Every importable solution zip contains exactly **three files at the zip root** (no subdirectory): 50 51``` 52[Content_Types].xml 53solution.xml 54customizations.xml 55``` 56 57Pack with: 58```bash 59cd MySolutionDir 60zip -j ../MySolution.zip "[Content_Types].xml" solution.xml customizations.xml 61``` 62 63### [Content_Types].xml (static — always identical) 64 65```xml 66<?xml version="1.0" encoding="utf-8"?> 67<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> 68 <Default Extension="xml" ContentType="application/octet-stream" /> 69</Types> 70``` 71 72--- 73 74## solution.xml 75 76The root `<ImportExportXml>` element **must** include all version attributes shown — omitting any causes import failure. 77 78```xml 79<ImportExportXml version="9.2.26044.164" SolutionPackageVersion="9.2" languagecode="1033" 80 generatedBy="CrmLive" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 81 OrganizationVersion="9.2.26044.164" OrganizationSchemaType="Standard" 82 CRMServerServiceabilityVersion="9.2.26044.00164"> 83 <SolutionManifest> 84 <UniqueName>{{SolutionUniqueName}}</UniqueName> 85 <LocalizedNames> 86 <LocalizedName description="{{Solution Display Name}}" languagecode="1033" /> 87 </LocalizedNames> 88 <Descriptions/> 89 <Version>1.0.0.0</Version> 90 <Managed>1</Managed> <!-- use 0 only if user explicitly requests unmanaged --> 91 <Publisher> 92 <UniqueName>{{PublisherUniqueName}}</UniqueName> 93 <LocalizedNames> 94 <LocalizedName description="{{Publisher Display Name}}" languagecode="1033" /> 95 </LocalizedNames> 96 <Descriptions/> 97 <EMailAddress>{{publisher@email.com}}</EMailAddress> 98 <SupportingWebsiteUrl xsi:nil="true"></SupportingWebsiteUrl> 99 <CustomizationPrefix>{{prefix}}</CustomizationPrefix> 100 <CustomizationOptionValuePrefix>{{OptionValuePrefix}}</CustomizationOptionValuePrefix> 101 <Addresses> 102 <Address> 103 <AddressNumber>1</AddressNumber><AddressTypeCode>1</AddressTypeCode> 104 <City xsi:nil="true"></City><County xsi:nil="true"></County> 105 <Country xsi:nil="true"></Country><Fax xsi:nil="true"></Fax> 106 <FreightTermsCode xsi:nil="true"></FreightTermsCode> 107 <ImportSequenceNumber xsi:nil="true"></ImportSequenceNumber> 108 <Latitude xsi:nil="true"></Latitude><Line1 xsi:nil="true"></Line1> 109 <Line2 xsi:nil="true"></Line2><Line3 xsi:nil="true"></Line3> 110 <Longitude xsi:nil="true"></Longitude><Name xsi:nil="true"></Name> 111 <PostalCode xsi:nil="true"></PostalCode><PostOfficeBox xsi:nil="true"></PostOfficeBox> 112 <PrimaryContactName xsi:nil="true"></PrimaryContactName> 113 <ShippingMethodCode>1</ShippingMethodCode> 114 <StateOrProvince xsi:nil="true"></StateOrProvince> 115 <Telephone1 xsi:nil="true"></Telephone1><Telephone2 xsi:nil="true"></Telephone2> 116 <Telephone3 xsi:nil="true"></Telephone3> 117 <TimeZoneRuleVersionNumber xsi:nil="true"></TimeZoneRuleVersionNumber> 118 <UPSZone xsi:nil="true"></UPSZone><UTCOffset xsi:nil="true"></UTCOffset> 119 <UTCConversionTimeZoneCode xsi:nil="true"></UTCConversionTimeZoneCode> 120 </Address> 121 <Address> 122 <AddressNumber>2</AddressNumber><AddressTypeCode>1</AddressTypeCode> 123 <City xsi:nil="true"></City><County xsi:nil="true"></County> 124 <Country xsi:nil="true"></Country><Fax xsi:nil="true"></Fax> 125 <FreightTermsCode xsi:nil="true"></FreightTermsCode> 126 <ImportSequenceNumber xsi:nil="true"></ImportSequenceNumber> 127 <Latitude xsi:nil="true"></Latitude><Line1 xsi:nil="true"></Line1> 128 <Line2 xsi:nil="true"></Line2><Line3 xsi:nil="true"></Line3> 129 <Longitude xsi:nil="true"></Longitude><Name xsi:nil="true"></Name> 130 <PostalCode xsi:nil="true"></PostalCode><PostOfficeBox xsi:nil="true"></PostOfficeBox> 131 <PrimaryContactName xsi:nil="true"></PrimaryContactName> 132 <ShippingMethodCode>1</ShippingMethodCode> 133 <StateOrProvince xsi:nil="true"></StateOrProvince> 134 <Telephone1 xsi:nil="true"></Telephone1><Telephone2 xsi:nil="true"></Telephone2> 135 <Telephone3 xsi:nil="true"></Telephone3> 136 <TimeZoneRuleVersionNumber xsi:nil="true"></TimeZoneRuleVersionNumber> 137 <UPSZone xsi:nil="true"></UPSZone><UTCOffset xsi:nil="true"></UTCOffset> 138 <UTCConversionTimeZoneCode xsi:nil="true"></UTCConversionTimeZoneCode> 139 </Address> 140 </Addresses> 141 </Publisher> 142 <RootComponents> 143 <!-- One entry per entity: type="1" --> 144 <RootComponent type="1" schemaName="{{prefix}}_{{entity}}" behavior="0" /> 145 <!-- If including a model-driven app: --> 146 <RootComponent type="62" schemaName="{{prefix}}_{{appname}}" behavior="0" /> 147 <RootComponent type="80" schemaName="{{prefix}}_{{appname}}" behavior="0" /> 148 </RootComponents> 149 <!-- Required when solution includes an AppModule --> 150 <MissingDependencies> 151 <MissingDependency> 152 <Required type="61" schemaName="msdyn_/Images/AppModule_Default_Icon.png" 153 displayName="msdyn_/Images/AppModule_Default_Icon.png" 154 solution="AppModuleWebResources (2.5)"> 155 <package appName="AppModule Web Resources Package" version="2.5">AppModuleWebResources (2.5)</package> 156 </Required> 157 <Dependent type="80" schemaName="{{prefix}}_{{appname}}" displayName="{{App Display Name}}" /> 158 </MissingDependency> 159 <MissingDependency> 160 <Required type="SettingDefinition" displayName="AppChannel" 161 solution="msdyn_AppFrameworkInfraExtensions (1.0.0.16)" 162 id.uniquename="AppChannel"> 163 <package appName="PowerAppsAppFramework Package" version="1.0.0.25">PowerAppsAppFramework_Anchor (1.0.0.25)</package> 164 </Required> 165 <Dependent type="AppSetting" 166 displayName="parentappmoduleid.uniquename={{prefix}}_{{appname}},settingdefinitionid.uniquename=AppChannel" 167 id.parentappmoduleid.uniquename="{{prefix}}_{{appname}}" 168 id.settingdefinitionid.uniquename="AppChannel" /> 169 </MissingDependency> 170 </MissingDependencies> 171 </SolutionManifest> 172</ImportExportXml> 173``` 174 175--- 176 177## customizations.xml 178 179### Root and top-level structure 180 181`<attributes>` must come **before** entity-level properties (`<EntitySetName>`, `<OwnershipTypeMask>`, etc.) inside `<entity>`. 182 183```xml 184<ImportExportXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 185 OrganizationVersion="9.2.26044.164" OrganizationSchemaType="Standard" 186 CRMServerServiceabilityVersion="9.2.26044.00164"> 187 <Entities> 188 <!-- Entity blocks go here, one per table --> 189 </Entities> 190 <optionsets /> 191 <CustomControls /> 192 <EntityDataProviders /> 193 <Languages><Language>1033</Language></Languages> 194 <!-- AppModuleSiteMaps and AppModules go here (after Languages) if including an app --> 195</ImportExportXml> 196``` 197 198### Entity block 199 200```xml 201<Entity> 202 <Name LocalizedName="{{Display Name}}" OriginalName="{{Display Name}}">{{prefix}}_{{entity}}</Name> 203 <EntityInfo> 204 <entity Name="{{prefix}}_{{entity}}"> 205 <LocalizedNames><LocalizedName description="{{Display Name}}" languagecode="1033" /></LocalizedNames> 206 <LocalizedCollectionNames><LocalizedCollectionName description="{{Plural Name}}" languagecode="1033" /></LocalizedCollectionNames> 207 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 208 <attributes> 209 <!-- ALL attribute elements go here FIRST --> 210 </attributes> 211 <EntitySetName>{{prefix}}_{{entities}}</EntitySetName> 212 <IsDuplicateCheckSupported>1</IsDuplicateCheckSupported> 213 <IsBusinessProcessEnabled>0</IsBusinessProcessEnabled> 214 <IsRequiredOffline>0</IsRequiredOffline> 215 <IsInteractionCentricEnabled>0</IsInteractionCentricEnabled> 216 <IsCollaboration>0</IsCollaboration> 217 <AutoRouteToOwnerQueue>0</AutoRouteToOwnerQueue> 218 <IsConnectionsEnabled>0</IsConnectionsEnabled> 219 <EntityColor></EntityColor> 220 <IsDocumentManagementEnabled>0</IsDocumentManagementEnabled> 221 <AutoCreateAccessTeams>0</AutoCreateAccessTeams> 222 <IsOneNoteIntegrationEnabled>0</IsOneNoteIntegrationEnabled> 223 <IsKnowledgeManagementEnabled>0</IsKnowledgeManagementEnabled> 224 <IsSLAEnabled>0</IsSLAEnabled> 225 <IsDocumentRecommendationsEnabled>0</IsDocumentRecommendationsEnabled> 226 <IsBPFEntity>0</IsBPFEntity> 227 <OwnershipTypeMask>UserOwned</OwnershipTypeMask> 228 <IsAuditEnabled>0</IsAuditEnabled> 229 <IsRetrieveAuditEnabled>0</IsRetrieveAuditEnabled> 230 <IsRetrieveMultipleAuditEnabled>0</IsRetrieveMultipleAuditEnabled> 231 <IsActivity>0</IsActivity><ActivityTypeMask></ActivityTypeMask><IsActivityParty>0</IsActivityParty> 232 <IsReplicated>0</IsReplicated><IsReplicationUserFiltered>0</IsReplicationUserFiltered> 233 <IsMailMergeEnabled>1</IsMailMergeEnabled> 234 <IsVisibleInMobile>0</IsVisibleInMobile> 235 <IsVisibleInMobileClient>0</IsVisibleInMobileClient> 236 <IsReadOnlyInMobileClient>0</IsReadOnlyInMobileClient> 237 <IsOfflineInMobileClient>1</IsOfflineInMobileClient> 238 <DaysSinceRecordLastModified>0</DaysSinceRecordLastModified> 239 <MobileOfflineFilters></MobileOfflineFilters> 240 <IsMapiGridEnabled>1</IsMapiGridEnabled> 241 <IsReadingPaneEnabled>1</IsReadingPaneEnabled> 242 <IsQuickCreateEnabled>0</IsQuickCreateEnabled> 243 <SyncToExternalSearchIndex>0</SyncToExternalSearchIndex> 244 <IntroducedVersion>1.0</IntroducedVersion> 245 <IsCustomizable>1</IsCustomizable> 246 <IsRenameable>1</IsRenameable> 247 <IsMappable>1</IsMappable> 248 <CanModifyAuditSettings>1</CanModifyAuditSettings> 249 <CanModifyMobileVisibility>1</CanModifyMobileVisibility> 250 <CanModifyMobileClientVisibility>1</CanModifyMobileClientVisibility> 251 <CanModifyMobileClientReadOnly>1</CanModifyMobileClientReadOnly> 252 <CanModifyMobileClientOffline>1</CanModifyMobileClientOffline> 253 <CanModifyConnectionSettings>1</CanModifyConnectionSettings> 254 <CanModifyDuplicateDetectionSettings>1</CanModifyDuplicateDetectionSettings> 255 <CanModifyMailMergeSettings>1</CanModifyMailMergeSettings> 256 <CanModifyQueueSettings>1</CanModifyQueueSettings> 257 <CanCreateAttributes>1</CanCreateAttributes> 258 <CanCreateForms>1</CanCreateForms> 259 <CanCreateCharts>1</CanCreateCharts> 260 <CanCreateViews>1</CanCreateViews> 261 <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings> 262 <CanEnableSyncToExternalSearchIndex>1</CanEnableSyncToExternalSearchIndex> 263 <EnforceStateTransitions>0</EnforceStateTransitions> 264 <CanChangeHierarchicalRelationship>1</CanChangeHierarchicalRelationship> 265 <EntityHelpUrlEnabled>0</EntityHelpUrlEnabled> 266 <ChangeTrackingEnabled>1</ChangeTrackingEnabled> 267 <CanChangeTrackingBeEnabled>1</CanChangeTrackingBeEnabled> 268 <IsEnabledForExternalChannels>0</IsEnabledForExternalChannels> 269 <IsMSTeamsIntegrationEnabled>0</IsMSTeamsIntegrationEnabled> 270 <IsSolutionAware>0</IsSolutionAware> 271 </entity> 272 </EntityInfo> 273 <RibbonDiffXml> 274 <CustomActions /> 275 <Templates><RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates></Templates> 276 <CommandDefinitions /> 277 <RuleDefinitions><TabDisplayRules /><DisplayRules /><EnableRules /></RuleDefinitions> 278 <LocLabels /> 279 </RibbonDiffXml> 280 <FormXml><!-- see Form Template below --></FormXml> 281 <SavedQueries><!-- see View Templates below --></SavedQueries> 282</Entity> 283``` 284 285**Do NOT include system attributes** (`statecode`, `statuscode`, `createdon`, `modifiedon`, `ownerid`, etc.) — Dataverse auto-creates them. Including them causes a duplicate key import error. 286 287--- 288 289## Attribute Templates 290 291All attributes share the same scaffold. Key rules: 292- Boolean-like properties must use **simple integers** — NOT compound `<Value>/<CanModify>` blocks (which cause "string '11' is not a valid Boolean value" errors) 293- Display labels use `<displaynames>/<displayname>` (lowercase, no "Localized" prefix) 294- `ImeMode` is a string: `auto`, `inactive`, or `disabled` (not an integer) 295 296### Text (nvarchar) — short text 297 298```xml 299<attribute PhysicalName="{{prefix}}_{{fieldname}}"> 300 <Type>nvarchar</Type><Name>{{prefix}}_{{fieldname}}</Name><LogicalName>{{prefix}}_{{fieldname}}</LogicalName> 301 <RequiredLevel>none</RequiredLevel> 302 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask> 303 <ImeMode>auto</ImeMode> 304 <ValidForUpdateApi>1</ValidForUpdateApi><ValidForReadApi>1</ValidForReadApi><ValidForCreateApi>1</ValidForCreateApi> 305 <IsCustomField>1</IsCustomField><IsAuditEnabled>1</IsAuditEnabled><IsSecured>0</IsSecured> 306 <IntroducedVersion>1.0</IntroducedVersion><IsCustomizable>1</IsCustomizable><IsRenameable>1</IsRenameable> 307 <CanModifySearchSettings>1</CanModifySearchSettings><CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings> 308 <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings><SourceType>0</SourceType> 309 <IsGlobalFilterEnabled>0</IsGlobalFilterEnabled><IsSortableEnabled>0</IsSortableEnabled> 310 <CanModifyGlobalFilterSettings>1</CanModifyGlobalFilterSettings><CanModifyIsSortableSettings>1</CanModifyIsSortableSettings> 311 <IsDataSourceSecret>0</IsDataSourceSecret><AutoNumberFormat></AutoNumberFormat> 312 <IsSearchable>0</IsSearchable><IsFilterable>0</IsFilterable><IsRetrievable>0</IsRetrievable><IsLocalizable>0</IsLocalizable> 313 <Format>text</Format><MaxLength>100</MaxLength><Length>200</Length> 314 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 315 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 316</attribute> 317``` 318 319**Primary name field** — add `PrimaryName` to `DisplayMask`: 320```xml 321<DisplayMask>PrimaryName|ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask> 322``` 323 324### Multiline text 325 326Use `<Type>nvarchar</Type>` + `<Format>textarea</Format>`. **`<Type>memo</Type>` is not recognised.** 327 328```xml 329<Format>textarea</Format><MaxLength>2000</MaxLength><Length>4000</Length> 330``` 331 332### Decimal / Currency 333 334```xml 335<attribute PhysicalName="{{prefix}}_{{fieldname}}"> 336 <Type>decimal</Type><Name>{{prefix}}_{{fieldname}}</Name><LogicalName>{{prefix}}_{{fieldname}}</LogicalName> 337 <RequiredLevel>none</RequiredLevel> 338 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask> 339 <ImeMode>inactive</ImeMode> 340 <ValidForUpdateApi>1</ValidForUpdateApi><ValidForReadApi>1</ValidForReadApi><ValidForCreateApi>1</ValidForCreateApi> 341 <IsCustomField>1</IsCustomField><IsAuditEnabled>1</IsAuditEnabled><IsSecured>0</IsSecured> 342 <IntroducedVersion>1.0</IntroducedVersion><IsCustomizable>1</IsCustomizable><IsRenameable>1</IsRenameable> 343 <CanModifySearchSettings>1</CanModifySearchSettings><CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings> 344 <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings><SourceType>0</SourceType> 345 <IsGlobalFilterEnabled>0</IsGlobalFilterEnabled><IsSortableEnabled>0</IsSortableEnabled> 346 <CanModifyGlobalFilterSettings>1</CanModifyGlobalFilterSettings><CanModifyIsSortableSettings>1</CanModifyIsSortableSettings> 347 <IsDataSourceSecret>0</IsDataSourceSecret><AutoNumberFormat></AutoNumberFormat> 348 <IsSearchable>0</IsSearchable><IsFilterable>1</IsFilterable><IsRetrievable>1</IsRetrievable><IsLocalizable>0</IsLocalizable> 349 <Precision>2</Precision><MinValue>-100000000000</MinValue><MaxValue>100000000000</MaxValue> 350 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 351 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 352</attribute> 353``` 354 355For non-negative amounts (e.g., Debit Amount): `<MinValue>0</MinValue>`. 356 357### Integer 358 359Replace the precision/min/max block with: 360```xml 361<Type>int</Type> 362... 363<MinValue>-2147483648</MinValue><MaxValue>2147483647</MaxValue> 364``` 365 366### Date / DateTime 367 368```xml 369<Type>datetime</Type> 370... 371<ImeMode>inactive</ImeMode> 372<Format>date</Format> <!-- or: datetime --> 373``` 374 375### Picklist (option set) 376 377Option values start at `100000000` (publisher option prefix `10000` × 10000). 378 379```xml 380<attribute PhysicalName="{{prefix}}_{{fieldname}}"> 381 <Type>picklist</Type><Name>{{prefix}}_{{fieldname}}</Name><LogicalName>{{prefix}}_{{fieldname}}</LogicalName> 382 <RequiredLevel>none</RequiredLevel> 383 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask> 384 <ImeMode>auto</ImeMode> 385 <ValidForUpdateApi>1</ValidForUpdateApi><ValidForReadApi>1</ValidForReadApi><ValidForCreateApi>1</ValidForCreateApi> 386 <IsCustomField>1</IsCustomField><IsAuditEnabled>1</IsAuditEnabled><IsSecured>0</IsSecured> 387 <IntroducedVersion>1.0</IntroducedVersion><IsCustomizable>1</IsCustomizable><IsRenameable>1</IsRenameable> 388 <CanModifySearchSettings>1</CanModifySearchSettings><CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings> 389 <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings><SourceType>0</SourceType> 390 <IsGlobalFilterEnabled>0</IsGlobalFilterEnabled><IsSortableEnabled>0</IsSortableEnabled> 391 <CanModifyGlobalFilterSettings>1</CanModifyGlobalFilterSettings><CanModifyIsSortableSettings>1</CanModifyIsSortableSettings> 392 <IsDataSourceSecret>0</IsDataSourceSecret><AutoNumberFormat></AutoNumberFormat> 393 <IsSearchable>0</IsSearchable><IsFilterable>1</IsFilterable><IsRetrievable>1</IsRetrievable><IsLocalizable>0</IsLocalizable> 394 <optionset Name="{{prefix}}_{{fieldname}}"> 395 <OptionSetType>picklist</OptionSetType><IsGlobal>0</IsGlobal><IsCustomizable>1</IsCustomizable> 396 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 397 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 398 <options> 399 <option value="100000000"><labels><label description="Option A" languagecode="1033" /></labels><Descriptions><Description description="" languagecode="1033" /></Descriptions></option> 400 <option value="100000001"><labels><label description="Option B" languagecode="1033" /></labels><Descriptions><Description description="" languagecode="1033" /></Descriptions></option> 401 </options> 402 </optionset> 403 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 404 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 405</attribute> 406``` 407 408**Option labels** = lowercase `<labels>/<label>`. NOT `<Labels>/<Label>`. 409 410### Boolean (Two Options) 411 412Verified from a working Dataverse export. Three things differ from other attribute types: 413 4141. `<AppDefaultValue>0</AppDefaultValue>` — NOT `<DefaultValue>` (wrong element, causes read-only rendering) 4152. Optionset name is **entity-qualified**: `{{prefix}}_{{entity}}_{{fieldname}}` — not just `{{prefix}}_{{fieldname}}` 4163. `<OptionSetType>bit</OptionSetType>` — NOT `bool` 4174. Options use `<option value="1">` / `<option value="0">` — NOT `<TrueOption>` / `<FalseOption>` 4185. Form control classid `{67FAC785-CD58-4f9f-ABB3-4B7DDC6ED5ED}` is correct — but **must include `disabled="false"`** or the control renders read-only 419 420```xml 421<attribute PhysicalName="{{prefix}}_{{FieldnameCapitalised}}"> 422 <Type>bit</Type><Name>{{prefix}}_{{fieldname}}</Name><LogicalName>{{prefix}}_{{fieldname}}</LogicalName> 423 <RequiredLevel>none</RequiredLevel> 424 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask> 425 <ImeMode>auto</ImeMode> 426 <ValidForUpdateApi>1</ValidForUpdateApi><ValidForReadApi>1</ValidForReadApi><ValidForCreateApi>1</ValidForCreateApi> 427 <IsCustomField>1</IsCustomField><IsAuditEnabled>1</IsAuditEnabled><IsSecured>0</IsSecured> 428 <IntroducedVersion>1.0</IntroducedVersion><IsCustomizable>1</IsCustomizable><IsRenameable>1</IsRenameable> 429 <CanModifySearchSettings>1</CanModifySearchSettings><CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings> 430 <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings><SourceType>0</SourceType> 431 <IsGlobalFilterEnabled>0</IsGlobalFilterEnabled><IsSortableEnabled>0</IsSortableEnabled> 432 <CanModifyGlobalFilterSettings>1</CanModifyGlobalFilterSettings><CanModifyIsSortableSettings>1</CanModifyIsSortableSettings> 433 <IsDataSourceSecret>0</IsDataSourceSecret><AutoNumberFormat></AutoNumberFormat> 434 <IsSearchable>0</IsSearchable><IsFilterable>0</IsFilterable><IsRetrievable>0</IsRetrievable><IsLocalizable>0</IsLocalizable> 435 <AppDefaultValue>0</AppDefaultValue> 436 <optionset Name="{{prefix}}_{{entity}}_{{fieldname}}"> 437 <OptionSetType>bit</OptionSetType><IsGlobal>0</IsGlobal><IsCustomizable>1</IsCustomizable> 438 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 439 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 440 <options> 441 <option value="1" ExternalValue="" IsHidden="0"><labels><label description="Yes" languagecode="1033" /></labels></option> 442 <option value="0" ExternalValue="" IsHidden="0"><labels><label description="No" languagecode="1033" /></labels></option> 443 </options> 444 </optionset> 445 <displaynames><displayname description="{{Field Label}}" languagecode="1033" /></displaynames> 446 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 447</attribute> 448``` 449 450Form control: 451```xml 452<control id="{{prefix}}_{{fieldname}}" classid="{67FAC785-CD58-4f9f-ABB3-4B7DDC6ED5ED}" datafieldname="{{prefix}}_{{fieldname}}" disabled="false" /> 453``` 454 455--- 456 457## View Templates 458 459Every entity needs at minimum: a default list view (`querytype 0`) and a Quick Find view (`querytype 4`). 460 461**`returnedtypecode`** = entity logical name string (`{{prefix}}_todo`), **not a number**. 462 463**GUIDs must be fresh per import attempt.** Failed-import GUIDs persist in the environment and cause re-import to fail. Generate: 464```bash 465python3 -c "import uuid; print(str(uuid.uuid4()).upper())" 466``` 467 468For readability, use sequential-looking hex GUIDs like `{B1000001-0000-4000-8000-000000000001}` — group entities by first digit (`B1` = entity 1, `B2` = entity 2, etc.). 469 470```xml 471<SavedQueries> 472 <!-- Default list view --> 473 <savedquery> 474 <savedqueryid>{UNIQUE-GUID-1}</savedqueryid> 475 <querytype>0</querytype> 476 <name>All {{Plural Name}}</name> 477 <returnedtypecode>{{prefix}}_{{entity}}</returnedtypecode> 478 <isdefault>1</isdefault><iscustom>1</iscustom><isquickfindquery>0</isquickfindquery> 479 <IsCustomizable>1</IsCustomizable><CanBeDeleted>1</CanBeDeleted> 480 <columnsetxml><columnset> 481 <column name="{{prefix}}_{{field1}}"/><column name="{{prefix}}_{{field2}}"/> 482 </columnset></columnsetxml> 483 <layoutxml><grid name="resultset" object="{{prefix}}_{{entity}}" jump="{{prefix}}_{{primaryfield}}" select="1" preview="0" icon="1"> 484 <row name="result" id="{{prefix}}_{{entity}}id"> 485 <cell name="{{prefix}}_{{field1}}" width="200"/> 486 <cell name="{{prefix}}_{{field2}}" width="150"/> 487 </row> 488 </grid></layoutxml> 489 <fetchxml><fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"> 490 <entity name="{{prefix}}_{{entity}}"> 491 <attribute name="{{prefix}}_{{field1}}"/> 492 <attribute name="{{prefix}}_{{field2}}"/> 493 <attribute name="{{prefix}}_{{entity}}id"/> 494 <order attribute="{{prefix}}_{{primaryfield}}" descending="false"/> 495 <filter type="and"><condition attribute="statecode" operator="eq" value="0"/></filter> 496 </entity> 497 </fetch></fetchxml> 498 <LocalizedNames><LocalizedName description="All {{Plural Name}}" languagecode="1033"/></LocalizedNames> 499 <Descriptions><Description description="" languagecode="1033"/></Descriptions> 500 </savedquery> 501 502 <!-- Quick Find view --> 503 <savedquery> 504 <savedqueryid>{UNIQUE-GUID-2}</savedqueryid> 505 <querytype>4</querytype> 506 <name>Quick Find {{Plural Name}}</name> 507 <returnedtypecode>{{prefix}}_{{entity}}</returnedtypecode> 508 <isdefault>1</isdefault><iscustom>1</iscustom><isquickfindquery>1</isquickfindquery> 509 <IsCustomizable>1</IsCustomizable><CanBeDeleted>1</CanBeDeleted> 510 <columnsetxml><columnset> 511 <column name="{{prefix}}_{{field1}}"/><column name="{{prefix}}_{{field2}}"/> 512 </columnset></columnsetxml> 513 <layoutxml><grid name="resultset" object="{{prefix}}_{{entity}}" jump="{{prefix}}_{{primaryfield}}" select="1" preview="0" icon="1"> 514 <row name="result" id="{{prefix}}_{{entity}}id"> 515 <cell name="{{prefix}}_{{field1}}" width="200"/> 516 <cell name="{{prefix}}_{{field2}}" width="150"/> 517 </row> 518 </grid></layoutxml> 519 <fetchxml><fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"> 520 <entity name="{{prefix}}_{{entity}}"> 521 <attribute name="{{prefix}}_{{field1}}"/> 522 <attribute name="{{prefix}}_{{field2}}"/> 523 <attribute name="{{prefix}}_{{entity}}id"/> 524 <order attribute="{{prefix}}_{{primaryfield}}" descending="false"/> 525 <filter type="and"><condition attribute="statecode" operator="eq" value="0"/></filter> 526 </entity> 527 </fetch></fetchxml> 528 <LocalizedNames><LocalizedName description="Quick Find {{Plural Name}}" languagecode="1033"/></LocalizedNames> 529 <Descriptions><Description description="" languagecode="1033"/></Descriptions> 530 </savedquery> 531</SavedQueries> 532``` 533 534--- 535 536## Form Template 537 538Form, tab, section, and cell GUIDs must all be unique. Use sequential GUIDs (e.g., `{A1000001-0000-4000-8000-000000000001}` for form, `{A1000002...}` for tab, etc.) — group by entity number for readability. 539 540**Control classids by field type:** 541 542| Type | classid | 543|---|---| 544| Text (nvarchar) | `{4273EDBD-AC1D-40d3-9FB2-095C621B552D}` | 545| Picklist | `{3EF39988-22BB-4f0b-BBBE-64B5A3748AEE}` | 546| Decimal / Integer | `{C3EFE0C3-0EC6-42be-8349-CBD9079C717A}` | 547| Boolean | `{67FAC785-CD58-4f9f-ABB3-4B7DDC6ED5ED}` — **must also set `disabled="false"`** on the control element or it renders read-only | 548| DateTime | `{5D68B988-0661-4db2-BC3E-17598AD3BE6C}` | 549| Owner lookup (system) | `{270BD3DB-D9AF-4782-9025-509E298DEC0A}` | 550 551```xml 552<FormXml> 553 <forms type="main"> 554 <systemform> 555 <formid>{A1000001-0000-4000-8000-000000000001}</formid> 556 <IntroducedVersion>1.0</IntroducedVersion> 557 <FormPresentation>1</FormPresentation> 558 <FormActivationState>1</FormActivationState> 559 <form> 560 <tabs> 561 <tab verticallayout="true" id="{A1000002-0000-4000-8000-000000000001}" IsUserDefined="1"> 562 <labels><label description="General" languagecode="1033" /></labels> 563 <columns><column width="100%"><sections> 564 <section showlabel="false" showbar="false" IsUserDefined="0" id="{A1000003-0000-4000-8000-000000000001}"> 565 <labels><label description="General" languagecode="1033" /></labels> 566 <rows> 567 <row><cell id="{A1000011-0000-4000-8000-000000000001}"> 568 <labels><label description="{{Label}}" languagecode="1033" /></labels> 569 <control id="{{prefix}}_{{field}}" classid="{4273EDBD-AC1D-40d3-9FB2-095C621B552D}" datafieldname="{{prefix}}_{{field}}" /> 570 </cell></row> 571 <!-- repeat <row> for each field; increment last segment: ...0011, ...0012, ... --> 572 <row><cell id="{A1000099-0000-4000-8000-000000000001}"> 573 <labels><label description="Owner" languagecode="1033" /></labels> 574 <control id="ownerid" classid="{270BD3DB-D9AF-4782-9025-509E298DEC0A}" datafieldname="ownerid" /> 575 </cell></row> 576 </rows> 577 </section> 578 </sections></column></columns> 579 </tab> 580 </tabs> 581 </form> 582 <IsCustomizable>1</IsCustomizable><CanBeDeleted>1</CanBeDeleted> 583 <LocalizedNames><LocalizedName description="Information" languagecode="1033" /></LocalizedNames> 584 <Descriptions><Description description="" languagecode="1033" /></Descriptions> 585 </systemform> 586 </forms> 587</FormXml> 588``` 589 590--- 591 592## AppModule + SiteMap 593 594Place after `<Languages>` in customizations.xml, before `</ImportExportXml>`. 595 596**Rules:** 597- `<SubArea>` is self-closing with `Icon="/_imgs/imagestrips/transparent_spacer.gif"` — no `<Titles>` child element. 598- `<WebResourceId>953b9fac-1e5e-e611-80d6-00155ded156f</WebResourceId>` — system default app icon, same GUID in every solution. 599- `<ClientType>4</ClientType>` — required for Unified Interface. 600- `<appsettings>` with `AppChannel=1` is required. 601- `<AppModuleComponents>` and `<AppModuleRoleMaps />` are child elements (not XML attributes). 602 603```xml 604<AppModuleSiteMaps> 605 <AppModuleSiteMap> 606 <SiteMapUniqueName>{{prefix}}_{{appname}}</SiteMapUniqueName> 607 <EnableCollapsibleGroups>False</EnableCollapsibleGroups> 608 <ShowHome>True</ShowHome><ShowPinned>True</ShowPinned><ShowRecents>True</ShowRecents> 609 <SiteMap IntroducedVersion="7.0.0.0"> 610 <Area Id="area_{{prefix}}_{{entity}}" ResourceId="SitemapDesigner.NewTitle" DescriptionResourceId="SitemapDesigner.NewTitle" ShowGroups="true" IntroducedVersion="7.0.0.0"> 611 <Titles><Title LCID="1033" Title="{{Area Title}}" /></Titles> 612 <Group Id="group_{{prefix}}_{{entity}}" ResourceId="SitemapDesigner.NewGroup" DescriptionResourceId="SitemapDesigner.NewGroup" IntroducedVersion="7.0.0.0" IsProfile="false" ToolTipResourseId="SitemapDesigner.Unknown"> 613 <SubArea Id="subarea_{{prefix}}_{{entity}}" Icon="/_imgs/imagestrips/transparent_spacer.gif" Entity="{{prefix}}_{{entity}}" Client="All,Outlook,OutlookLaptopClient,OutlookWorkstationClient,Web" AvailableOffline="true" PassParams="false" Sku="All,OnPremise,Live,SPLA" /> 614 </Group> 615 </Area> 616 <!-- Add more <Area> blocks for additional entity groups --> 617 </SiteMap> 618 <LocalizedNames><LocalizedName description="{{App Display Name}}" languagecode="1033" /></LocalizedNames> 619 </AppModuleSiteMap> 620</AppModuleSiteMaps> 621<AppModules> 622 <AppModule> 623 <UniqueName>{{prefix}}_{{appname}}</UniqueName> 624 <IntroducedVersion>1.0.0.0</IntroducedVersion> 625 <WebResourceId>953b9fac-1e5e-e611-80d6-00155ded156f</WebResourceId> 626 <OptimizedFor></OptimizedFor> 627 <statecode>0</statecode><statuscode>1</statuscode> 628 <FormFactor>1</FormFactor> 629 <ClientType>4</ClientType> 630 <NavigationType>0</NavigationType> 631 <AppModuleComponents> 632 <!-- type="1" for each entity, type="62" for the SiteMap itself --> 633 <AppModuleComponent type="1" schemaName="{{prefix}}_{{entity}}" /> 634 <AppModuleComponent type="62" schemaName="{{prefix}}_{{appname}}" /> 635 </AppModuleComponents> 636 <AppModuleRoleMaps /> 637 <LocalizedNames><LocalizedName description="{{App Display Name}}" languagecode="1033" /></LocalizedNames> 638 <Descriptions><Description description="{{Description}}" languagecode="1033" /></Descriptions> 639 <appsettings> 640 <appsetting settingdefinitionid.uniquename="AppChannel"> 641 <iscustomizable>1</iscustomizable> 642 <value>1</value> 643 </appsetting> 644 </appsettings> 645 </AppModule> 646</AppModules> 647``` 648 649--- 650 651## pack.sh 652 653```bash 654#!/usr/bin/env bash 655set -euo pipefail 656SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 657SOLUTION_DIR="$SCRIPT_DIR/{{SolutionName}}" 658OUTPUT="$SCRIPT_DIR/{{SolutionName}}.zip" 659echo "Packing from: $SOLUTION_DIR" 660cd "$SOLUTION_DIR" 661zip -j "$OUTPUT" "[Content_Types].xml" solution.xml customizations.xml 662echo "Created: $OUTPUT" 663echo "" 664echo "Import: pac solution import --path $OUTPUT" 665echo "Browser: make.powerapps.com → Solutions → Import Solution" 666``` 667 668Files must sit at the **zip root** — `-j` (junk paths) is required. 669 670--- 671 672## Critical Gotchas (verified from real import testing) 673 6741. **solution.xml root element** must include `OrganizationVersion`, `OrganizationSchemaType`, `CRMServerServiceabilityVersion`, and `SolutionPackageVersion="9.2"`. Copy the exact attribute set from these projects. 675 6762. **Primary name field** = `PrimaryName` flag in `<DisplayMask>`. No separate element or attribute needed. 677 6783. **No system attributes** — do not declare `statecode`, `statuscode`, `createdon`, `modifiedon`, `ownerid`, etc. Dataverse creates them automatically. Declaring them causes a duplicate key import error. 679 6804. **`<attributes>` before entity properties** — `<attributes>` must be the first child of `<entity>`. Entity-level properties (`<EntitySetName>`, `<OwnershipTypeMask>`, etc.) come after the closing `</attributes>`. 681 6825. **Attribute display labels** = `<displaynames>/<displayname>` (lowercase). NOT `<LocalizedNames>/<LocalizedName>`. 683 6846. **Option value labels** = lowercase `<labels>/<label>`. NOT `<Labels>/<Label>`. 685 6867. **Multiline text** = `<Type>nvarchar</Type>` + `<Format>textarea</Format>`. `<Type>memo</Type>` is not recognised and causes import failure. 687 6888. **View GUIDs must be fresh** on every import attempt. Failed-import GUIDs are persisted in the environment; re-using them causes the view import to be silently skipped or fail. 689 6909. **`<returnedtypecode>`** = entity logical name string (e.g., `{{prefix}}_todo`), not a numeric code. 691 69210. **ImeMode** = string: `auto`, `inactive`, or `disabled`. Not an integer. 693 69411. **Simple boolean properties** — use `<IsCustomizable>1</IsCustomizable>`, NOT `<IsCustomizable><Value>1</Value><CanModify>1</CanModify></IsCustomizable>`. The compound form triggers "string '11' is not a valid Boolean value" on import. 695 69612. **Boolean (Two Options) — verified correct structure** (reverse-engineered from working export): 697 - `<Type>bit</Type>` on the attribute 698 - `<AppDefaultValue>0</AppDefaultValue>` — NOT `<DefaultValue>` (wrong element name, causes read-only) 699 - Optionset name must be entity-qualified: `{{prefix}}_{{entity}}_{{fieldname}}` 700 - `<OptionSetType>bit</OptionSetType>` inside the optionset — NOT `bool` 701 - Options use `<option value="1">` / `<option value="0">` — NOT `<TrueOption>` / `<FalseOption>` 702 - Form control classid `{67FAC785-CD58-4f9f-ABB3-4B7DDC6ED5ED}` is correct — but **`disabled="false"` is required** on the control or it renders read-only 703 70414. **Managed solution upgrades require delete-first during active development** — managed solutions trigger "ImportAsHolding" (staged upgrade) on re-import, which diffs entity attributes and tries to delete system fields (`statecode`, `statuscode`), causing an unresolvable import failure. When iterating on a managed solution, delete the existing solution before re-importing. If the user hits repeated upgrade failures they can switch to `<Managed>0</Managed>` temporarily; re-imports of unmanaged solutions are simple overwrites with no staging. 705 70615. **`<RootComponent behavior="0">` is correct for entity components** — `behavior="1"` registers all subcomponents (including system attributes) as owned by the managed solution, which permanently breaks future upgrades. Use `behavior="0"` as shown in all Microsoft solution exports. 707 70816. **Holding solutions from failed imports block re-import** — when an ImportAsHolding attempt fails, Dataverse leaves a residual `_Upgrade` holding solution in the environment. Before re-importing, check `make.powerapps.com → Solutions` for any solution with `_Upgrade` or `(patch)` in the name and delete it alongside the main solution.