MS Dynamics Web API Quirks: The Strange Case of Polymorphic Fields

Published: (March 15, 2026 at 06:43 PM EDT)
4 min read
Source: Dev.to

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:

FieldPossible Targets
owneridsystemuser, team
customeridaccount, contact
regardingobjectidMany 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:

PropertyMeaning
_ownerid_valueGUID
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.bind

The 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_value instead of ownerid?
  • Why do some lookups require @odata.bind while others require field_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.id
  • owner.type
  • owner.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.

ContextField Name
Metadataownerid
Read_ownerid_value
Writeownerid@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.

0 views
Back to Blog

Related posts

Read more »