8 min readRishi

Add a New Row: The Lookup Is accounts(guid), Not the GUID

In this series: Power Automate gotchas

Add a New Row: The Lookup Is accounts(guid), Not the GUID

The order-from-warehouse flow created the contact. parentcustomerid was the account GUID from the previous action. Add a new row turned red: URL was not parsed due to an invalid URI path. The GUID was real. The account existed. The box was the wrong language. A Dataverse lookup on Add a new row is a bind to an entity set, not a primary key and not a name. You write accounts(the-guid). You do not write the GUID alone.

That is the whole property. Filter rows reads a lookup as _parentcustomerid_value. Get a row takes a raw Row ID. The create and update actions speak a third dialect: the Web API bind. Three boxes, three shapes, same GUID underneath.

Three boxes, one GUID

Get a row by ID
  Table name  Accounts
  Row ID      853b1588-f32e-eb11-a813-000d3a9b7981
→ Primary key. Bare GUID.

List rows  Filter rows
  _parentcustomerid_value eq '853b1588-f32e-eb11-a813-000d3a9b7981'
→ OData attribute. Underscores and _value.

Add a new row  (Contacts)
  Company Name (Accounts)
  accounts(853b1588-f32e-eb11-a813-000d3a9b7981)
→ Bind. Entity set name, then the GUID in parentheses.

The designer label is still "Account" or "Parent Customer." That is the form word. The payload Dataverse accepts is a navigation bind: this contact's parent is the row at this entity-set address. The address is accounts(...), not account(...), not the display name Contoso Ltd, and not _parentcustomerid_value.

Copy the GUID into the lookup field and the connector builds a path it cannot parse. Put the account name in and Dataverse will not resolve it. This is not Find. The bind has to already know which row.

concat('accounts(', variables('AccountId'), ')')

AccountId is the account's primary key from this run: the trigger accountid, a List rows first(... )?['accountid'], an Add a new row output you just created. No braces. A GUID you pasted from a model-driven URL often arrives as {853b1588-...}. Leave the braces on and the path is wrong again.

Entity set, not logical name

account is the logical name. accounts is the entity set. The bind wants the set. Standard tables are the ones people already type in URLs:

TableLogical nameBind prefix
Accountaccountaccounts(
Contactcontactcontacts(
Caseincidentincidents(
Usersystemusersystemusers(
Teamteamteams(
Opportunityopportunityopportunities(

Custom tables are not "logical name plus s." Open the table in the maker portal and read Entity set name. A project table whose logical name is cr12_project is often cr12_projects, until someone set it to cr12_projectset on day one and every bind you guess from the logical name 404s. Copy the set name. Do not pluralize in your head.

The HTTP action uses the same set, with a leading slash, as the Web API post already showed:

{
  "firstname": "Asha",
  "lastname": "Okoye",
  "parentcustomerid_account@odata.bind": "/accounts(853b1588-f32e-eb11-a813-000d3a9b7981)"
}

Designer lookup fields usually want accounts(guid) with no slash. The @odata.bind JSON wants /accounts(guid) with the slash. Same set, two spellings. Mixing them is how you get a green HTTP action in one flow and a failed Add a new row in the next.

Update a row is the same bind. The Row ID at the top of Update a row is still a bare GUID — that is which contact you are patching. The lookup columns below it are still accounts(guid). People swap those two and fail both fields.

Customer, owner, regarding: pick a side

Parent Customer on contact is not one lookup. It is a Customer column: account or contact. The connector exposes two navigations. Fill one.

Add a new row  (Contacts)
  Company Name (Accounts)
    accounts(@{variables('AccountId')})

  Company Name (Contacts)
    <leave empty>

parentcustomerid_account with accounts(guid), or parentcustomerid_contact with contacts(guid). Not parentcustomerid with a GUID and a hope. Not both sides on the same row. Dataverse will not pick the type from the id. You name the side.

Owner is the same shape with a smaller menu: systemusers(guid) or teams(guid). The form says Owner. The bind says which table. A user id in teams(...) is a row that does not exist on that set.

Regarding on an activity is the long version of Customer. Each allowed target has its own navigation: regardingobjectid_incident, regardingobjectid_account, regardingobjectid_contact. Bind the side that matches the row you have. A case GUID in regardingobjectid_account is a clean failure, not a clever fallback.

Activity party (required attendees, from, to) is not this pattern. That is a collection of party records. systemusers(guid) in a party field is a different error that looks like this one.

Blank does not clear

Update a row with the lookup left empty means the column is not in the payload. The existing parent stays. That is the same "omit, don't null" rule as Patch on a canvas app. To drop a parent, Unrelate rows on that relationship, or send an HTTP DELETE to the navigation's $ref.

Relate rows is the other direction: two existing rows, one relationship, no other columns touched. Use it when you already have both ids and you are not creating the child. Do not Relate after Add a new row if you already bound the parent on create. That is two writes to the same lookup.

N:N has no lookup box on Add a new row. The intersect is Relate rows. Putting one side's GUID into a field that is not on the table does nothing useful.

What the failures actually mean

What you put in the lookupWhat happens
Bare GUIDInvalid URI / bind path. The connector has no set name.
{guid} with bracesSame, with extra characters inside the parentheses.
account(guid)Segment not found. Logical name is not the set.
/accounts(guid) in the designer boxOften parsed as a path the connector did not expect. Use this spelling in HTTP JSON.
Account display nameNot a bind. Dataverse will not resolve it here.
_parentcustomerid_valueRead/filter attribute. Not a write field.
Both Customer sides filledType conflict. Pick account or contact.

A bind to a GUID that is well-formed and missing is a different failure: the set name was right, the row is not there. That is the Get a row miss in write form. List rows on the parent by business key, take accountid when value has one element, then build concat('accounts(', first(...)?['accountid'], ')'). Do not $filter a name into the bind.

If the interpolated id can be empty — skipped Compose, failed first() on an empty list — you send accounts() and fail. Guard with empty(...) before Add a new row. A missing parent is a Condition, not a bind you hope Dataverse treats as optional. An omitted lookup on create is optional. A broken string in the box is not omitted; it is a bad bind.

When HTTP is the clearer write

The connector is the default for one row and a handful of columns. Switch to Send an HTTP request when the designer hides the navigation, when you are clearing a lookup, or when a $batch changeset must create the account and bind the contact in one transaction. The JSON uses @odata.bind and the slashed path. The set name does not change.

{
  "firstname": "Asha",
  "lastname": "Okoye",
  "parentcustomerid_account@odata.bind": "/accounts(853b1588-f32e-eb11-a813-000d3a9b7981)"
}

Do not put _parentcustomerid_value in that JSON, and do not put parentcustomerid as a GUID string. The annotation is navigation@odata.bind. On a Customer column the navigation includes the side.

What to type in the lookup

  • entityset(guid) in Add a new row and Update a row. Set name from the table properties, GUID with no braces.
  • Row ID on Get / Update / Delete stays a bare GUID.
  • Filter rows stays _logicalname_value eq guid.
  • Customer, owner, regarding: fill the side that matches the row you have. Leave the other side empty.
  • Unrelate to clear. Omit to leave alone. A blank box is not a null.
  • HTTP writes /entityset(guid) on navigation@odata.bind. Same set, leading slash.

The GUID is the easy part. The bind is the set name, the parentheses, and which navigation you pointed it at. Put accounts(that-guid) in the lookup, and Add a new row creates a contact that actually belongs to the account — or fails loudly because the account is missing, which is a Condition, not a URI error.

Keep reading

Newsletter

New posts, straight to your inbox

One email when a new post goes up. Double opt-in, unsubscribe anytime. Not a daily digest.

Comments

  • No comments yet. Be the first.