If you’ve ever integrated with Microsoft Dynamics 365 / Dataverse, you probably noticed something odd almost immediately. You read a record and 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
One of the first confusing things developers notice is that the same lookup field has different names depending on context.
1️⃣ Metadata name
In the schema or metadata: ownerid
2️⃣ Reading records
When retrieving records, the field becomes: _<field>_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 | Entity type |
| FormattedValue | 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: <lookup>@odata.bind.
Example:
{
"ownerid@odata.bind": "/systemusers(151F639c-1c73-eb11-b1ab-000d3a253b40)"
}
Pattern:
<lookup>@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:
<lookup>_<entity>@odata.bind
This is where things become particularly confusing because the property name itself changes depending on the target entity.
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 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 again and again:
- 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 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 currently 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 spend time decoding the API instead of building features. Our goal with Aurinko is simple: make CRM integrations feel predictable again.
And sometimes that starts by hiding _ownerid_value forever.
Top comments (0)