Add a New Row: The Lookup Is accounts(guid), Not the GUID
In this series: Power Automate gotchas
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:
| Table | Logical name | Bind prefix |
|---|---|---|
| Account | account | accounts( |
| Contact | contact | contacts( |
| Case | incident | incidents( |
| User | systemuser | systemusers( |
| Team | team | teams( |
| Opportunity | opportunity | opportunities( |
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 lookup | What happens |
|---|---|
| Bare GUID | Invalid URI / bind path. The connector has no set name. |
{guid} with braces | Same, with extra characters inside the parentheses. |
account(guid) | Segment not found. Logical name is not the set. |
/accounts(guid) in the designer box | Often parsed as a path the connector did not expect. Use this spelling in HTTP JSON. |
| Account display name | Not a bind. Dataverse will not resolve it here. |
_parentcustomerid_value | Read/filter attribute. Not a write field. |
| Both Customer sides filled | Type 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)onnavigation@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
Filter Rows: The Lookup GUID Is _parentcustomerid_value
Filter rows is Dataverse OData. Lookups filter on _logicalname_value, not the name. Filter array runs after the download, and @equals does not belong in that box.
Get a Row by ID: Missing Fails the Run, Empty List Rows Does Not
Get a row by ID fails the run when the GUID is missing. List rows with zero matches succeeds. Look up the business key and branch on empty.
When an HTTP Request Is Received: The Callback URL Is a Secret
The HTTP trigger URL carries a SAS signature. Anyone with it can start the flow. Restrict who can call, always Respond, and never treat that URL as public.
Power Automate Do Until: The Loop That Succeeds After Giving Up
A Do until that hits count or timeout still succeeds. Put Get inside the loop, add a Delay, and test the condition after the loop exits.
Power Automate Trigger Conditions: The Run That Never Appears
A trigger condition that is false does not start a run and does not leave run history. How the expression differs from a Condition action, and how to see the skipped checks.
Power Platform September 2026 Update: The App-Builder Skill, Agent Plugins, and the Refreshed Model-Driven UI
What matters in Microsoft's September 2026 Power Platform feature update: the model app-builder skill goes GA, the canvas authoring agent plugin goes GA, generative pages embed in forms and reach connectors, model-driven header refresh is GA with display density in preview, and Power Automate gets server-side search.
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.