MS Dynamics Web API Quirks: The Strange Case of Polymorphic Fields
Source: Dev.to
If you’ve ever integrated with Microsoft Dynamics 365 / Dataverse, you probably noticed something odd almost immediately.
When you read a record you see fields like this:
{
"_ownerid_value": "151F639c-1c73-eb11-b1ab-000d3a253b40"
}But when creating or updating records, the API suddenly expects something like:
{
"ownerid@odata.bind": "/systemusers(151F639c-1c73-eb11-b1ab-000d3a253b40)"
}And if the field is polymorphic, sometimes the property name changes again:
{
"parentcustomerid_account@odata.bind": "/accounts(ce9eaaef-f718-ed11-b83e-00224837179f)"
}Welcome to one of the most confusing parts of the Dynamics Web API: lookup and polymorphic fields. Let’s unpack what’s going on.
What are polymorphic fields in Dynamics?
Some relationships in Dynamics can point to multiple entity types.
Example metadata:
{
"LogicalName": "ownerid",
"Targets": [
"systemuser",
"team"
]
}This means a record owner can be:
- A system user
- A team
Other examples of polymorphic lookups include:
| Field | Possible Targets |
|---|---|
ownerid | systemuser, team |
customerid | account, contact |
regardingobjectid | Many entities |
Conceptually this is powerful: the schema allows one relationship field to point to multiple tables. But it introduces some interesting API behavior.
The 3 different names for the same field
1️⃣ Metadata name
In the schema or metadata: ownerid
2️⃣ Reading records
When retrieving records, the field becomes __value.
Example response:
{
"_ownerid_value": "151F639c-1c73-eb11-b1ab-000d3a253b40"
}Additional annotations often appear:
{
"_ownerid_value": "GUID",
"_ownerid_value@Microsoft.Dynamics.CRM.lookuplogicalname": "systemuser",
"_ownerid_value@OData.Community.Display.V1.FormattedValue": "John Smith"
}Meaning:
| Property | Meaning |
|---|---|
_ownerid_value | GUID |
lookuplogicalname (annotation) | Entity type |
FormattedValue (annotation) | Display value |
So when reading data, you need to interpret three separate pieces of information to understand the relationship.
3️⃣ Writing records
When creating or updating records, Dynamics expects OData binding: @odata.bind.
Example:
{
"ownerid@odata.bind": "/systemusers(151F639c-1c73-eb11-b1ab-000d3a253b40)"
}Pattern:
@odata.bind : "/<entityset>(GUID)"(Where the entity set name is pluralized.)
When polymorphic fields get even stranger
Some polymorphic lookups require the entity name to be embedded in the property.
Examples
{
"parentcustomerid_account@odata.bind": "/accounts(GUID)"
}{
"parentcustomerid_contact@odata.bind": "/contacts(GUID)"
}Pattern:
<field>_<entity>_@odata.bindThe property name itself changes depending on the target entity, which can be particularly confusing.
The special case: ownerid
ownerid behaves slightly differently. Unlike other polymorphic fields, you do not include the entity suffix. Both of these work:
{
"ownerid@odata.bind": "/systemusers(GUID)"
}{
"ownerid@odata.bind": "/teams(GUID)"
}The entity type is inferred from the entity set in the URL, not from the property name. This exception is one of the reasons developers often find Dynamics integrations unpredictable.
What developers say about this
If you search StackOverflow or Dynamics forums, you’ll see the same questions repeatedly:
- Why does the API return
_ownerid_valueinstead ofownerid? - Why do some lookups require
@odata.bindwhile others requirefield_entity@odata.bind? - Why does the field name change depending on whether you’re reading or writing?
A common pattern in discussions is developers discovering the correct format through trial and error rather than clear documentation. The API reflects years of evolving platform architecture, and that complexity leaks into the integration layer.
Why this matters for integrations
If you’re building integrations with Dynamics — especially sync engines, SaaS connectors, or data pipelines — this complexity adds up quickly. Your integration code ends up handling:
- Polymorphic lookup detection
- Entity‑type resolution
- OData binding formats
- Multiple naming conventions
- Response annotations
A simpler approach: Canonical CRM models
At Aurinko, we’re finalizing MS Dynamics support in our canonical CRM API. One of the main goals is to remove these platform‑specific quirks from the developer experience.
Instead of dealing with _ownerid_value or ownerid@odata.bind, developers simply work with:
owner.idowner.typeowner.name
Aurinko handles the heavy lifting behind the scenes:
- Polymorphic lookup resolution
- Entity type detection
- OData binding logic
- Dynamics naming conventions
- Schema inconsistencies
The result is a clean, consistent model across CRM platforms, so your integration code doesn’t need to understand the quirks of each individual API.
Final thoughts
Microsoft Dynamics is an incredibly capable platform, but its API reflects the complexity of a large enterprise system.
| Context | Field Name |
|---|---|
| Metadata | ownerid |
| Read | _ownerid_value |
| Write | ownerid@odata.bind |
Add polymorphic suffix rules and annotations, and it’s easy to see why developers often feel overwhelmed. By abstracting these details away, you can focus on business logic rather than plumbing.
Our goal with Aurinko is simple: make CRM integrations feel predictable again—and sometimes that starts by hiding _ownerid_value forever.