Summary
When a custom lookup column is created against Dataverse via the Web API( in my case) CreateAttribute metadata endpoint (e.g. through an MCP tool, script, or any non-maker-portal path) with SchemaName set equal to the all lowercase LogicalName (instead of the classic PascalCase-after-underscore convention the maker portal UI normally produces, ex: new_Account), the Code Apps data-source code generator still emits the classic PascalCase form for the corresponding @odata.bind key in the generated TypeScript model (ex: "trd_Order@odata.bind"), instead of introspecting the actual live metadata casing ("trd_order@odata.bind") in my concret case.
Because the generated interface is just a TypeScript type (no runtime validation), the project compiles and builds successfully, but any create()/update() call that populates the mis-cased bind property fails at runtime against the live Dataverse Web API with an OData "undeclared property" error, since Dataverse's OData endpoint is case-sensitive for navigation property names and only recognizes the real SchemaName casing.
Steps to Reproduce
- In a Dataverse environment, create a custom table with a custom lookup column using the Web API metadata endpoint directly (ex:
POST .../EntityDefinitions(...)/Attributes) rather than the maker portal's column designer, specifying a display name like Order and letting SchemaName default to the same casing as LogicalName (all lowercase, e.g. trd_order) instead of the portal's usual trd_Order.
- In a Power Apps Code App project (
@microsoft/power-apps-cli), add/refresh this table as a data source (pa app run / data source add & codegen).
- Open the generated model file, e.g.
src/generated/models/<Table>Model.ts.
- Observe the generated interface emits the bind property using the classic PascalCase-after-underscore convention, e.g.:
"trd_Order@odata.bind"?: string;
"trd_Menu_Item@odata.bind"?: string;
- Call the generated service's
create() (or update()) with that property populated, e.g.:
Trd_orderlineitemsService.create({
...,
'trd_Order@odata.bind': `/trd_orders(${orderId})`,
});
- Observe the request fails against the live Dataverse Web API with an OData error similar to:
Undeclared property 'trd_Order' found in JSON.
Expected Behavior
The generated @odata.bind key should match the lookup attribute's actual SchemaName/navigation-property as reported by Dataverse metadata ($metadata, or the associatednavigationproperty OData annotation returned on the corresponding _value field), regardless of the casing convention used when the attribute was created : in this repro case, "trd_order@odata.bind" / "trd_menu_item@odata.bind" (lowercase), which is what the live endpoint actually accepts.
Actual Behavior
The generator assumes the classic maker-portal casing convention (capitalize the segment after each underscore) rather than reading the real navigation-property metadata, producing a bind key the live OData endpoint rejects. This silently passes TypeScript compilation and only fails at runtime, with no design-time warning.
Supporting Evidence
A live GET against trd_orderlineitems({id}) returns the following OData annotations, retrieved directly from Dataverse metadata for the record:
| Attribute Name |
Value |
_trd_order_value@Microsoft.Dynamics.CRM.associatednavigationproperty |
trd_order |
_trd_order_value@Microsoft.Dynamics.CRM.lookuplogicalname |
trd_order |
_trd_menu_item_value@Microsoft.Dynamics.CRM.associatednavigationproperty |
trd_menu_item |
_trd_menu_item_value@Microsoft.Dynamics.CRM.lookuplogicalname |
trd_menuitem |
The associatednavigationproperty value is the exact key Dataverse expects for @odata.bind on create/update : both confirmed lowercase (trd_order, trd_menu_item), never the PascalCase form (trd_Order, trd_Menu_Item) emitted by the generated model. (Note lookuplogicalname is a distinct annotation : it identifies the target table's logical name, e.g. trd_menuitem, not the attribute/nav-property name used for binding.) This proves the generator should read associatednavigationproperty (or the equivalent SchemaName from EntityDefinitions...Attributes) rather than deriving the bind key heuristically from the display/logical name.
Environment
@microsoft/power-apps-cli: 1.0.1
@microsoft/power-apps: 1.2.5
@microsoft/power-apps-vite: 1.0.2
- Dataverse online environment
- Affected columns: custom lookup attributes created via Dataverse Web API metadata calls (in this case through an MCP-based Dataverse tool), not the maker-portal column designer
Suggested Fix / Workaround
- Codegen should resolve
@odata.bind navigation-property names from the live $metadata (or the associatednavigationproperty annotation / EntityDefinitions...Attributes SchemaName) rather than deriving them heuristically from the display/logical name.
- Workaround (used in this repro): manually patch the generated model file(s) to lowercase the affected
@odata.bind keys to match the real schema, and keep call sites in sync.
Summary
When a custom lookup column is created against Dataverse via the Web API( in my case)
CreateAttributemetadata endpoint (e.g. through an MCP tool, script, or any non-maker-portal path) withSchemaNameset equal to the all lowercaseLogicalName(instead of the classic PascalCase-after-underscore convention the maker portal UI normally produces, ex:new_Account), the Code Apps data-source code generator still emits the classic PascalCase form for the corresponding@odata.bindkey in the generated TypeScript model (ex:"trd_Order@odata.bind"), instead of introspecting the actual live metadata casing ("trd_order@odata.bind") in my concret case.Because the generated interface is just a TypeScript type (no runtime validation), the project compiles and builds successfully, but any
create()/update()call that populates the mis-cased bind property fails at runtime against the live Dataverse Web API with an OData "undeclared property" error, since Dataverse's OData endpoint is case-sensitive for navigation property names and only recognizes the real SchemaName casing.Steps to Reproduce
POST .../EntityDefinitions(...)/Attributes) rather than the maker portal's column designer, specifying a display name likeOrderand lettingSchemaNamedefault to the same casing asLogicalName(all lowercase, e.g.trd_order) instead of the portal's usualtrd_Order.@microsoft/power-apps-cli), add/refresh this table as a data source (pa app run/ data source add & codegen).src/generated/models/<Table>Model.ts.create()(orupdate()) with that property populated, e.g.:Expected Behavior
The generated
@odata.bindkey should match the lookup attribute's actualSchemaName/navigation-property as reported by Dataverse metadata ($metadata, or theassociatednavigationpropertyOData annotation returned on the corresponding_valuefield), regardless of the casing convention used when the attribute was created : in this repro case,"trd_order@odata.bind"/"trd_menu_item@odata.bind"(lowercase), which is what the live endpoint actually accepts.Actual Behavior
The generator assumes the classic maker-portal casing convention (capitalize the segment after each underscore) rather than reading the real navigation-property metadata, producing a bind key the live OData endpoint rejects. This silently passes TypeScript compilation and only fails at runtime, with no design-time warning.
Supporting Evidence
A live
GETagainsttrd_orderlineitems({id})returns the following OData annotations, retrieved directly from Dataverse metadata for the record:_trd_order_value@Microsoft.Dynamics.CRM.associatednavigationpropertytrd_order_trd_order_value@Microsoft.Dynamics.CRM.lookuplogicalnametrd_order_trd_menu_item_value@Microsoft.Dynamics.CRM.associatednavigationpropertytrd_menu_item_trd_menu_item_value@Microsoft.Dynamics.CRM.lookuplogicalnametrd_menuitemThe
associatednavigationpropertyvalue is the exact key Dataverse expects for@odata.bindon create/update : both confirmed lowercase (trd_order,trd_menu_item), never the PascalCase form (trd_Order,trd_Menu_Item) emitted by the generated model. (Notelookuplogicalnameis a distinct annotation : it identifies the target table's logical name, e.g.trd_menuitem, not the attribute/nav-property name used for binding.) This proves the generator should readassociatednavigationproperty(or the equivalentSchemaNamefromEntityDefinitions...Attributes) rather than deriving the bind key heuristically from the display/logical name.Environment
@microsoft/power-apps-cli: 1.0.1@microsoft/power-apps: 1.2.5@microsoft/power-apps-vite: 1.0.2Suggested Fix / Workaround
@odata.bindnavigation-property names from the live$metadata(or theassociatednavigationpropertyannotation /EntityDefinitions...AttributesSchemaName) rather than deriving them heuristically from the display/logical name.@odata.bindkeys to match the real schema, and keep call sites in sync.