Claude Code (Part 13)

Neil HaddleyJune 3, 2026

Building a Claude Code skill that generates Dataverse solution XML for Power Platform

AIPower Platformclaude-codeai-agentanthropicagentic-codingpowerplatformdataverseskill

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 `/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

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

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 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

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

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

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

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 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

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

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

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 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 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

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

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

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.