8 min readRishi

Filter Rows: The Lookup GUID Is _parentcustomerid_value

In this series: Power Automate gotchas

Filter Rows: The Lookup GUID Is _parentcustomerid_value

The contacts-for-this-account flow listed every contact in the environment. Filter rows had been parentcustomerid eq 'Contoso Ltd' until List rows failed, so the maker cleared the box and moved the check into Filter array. Apply to each ran for minutes. The account name was never a column Dataverse would compare in Filter rows. Filter rows is OData against Dataverse attributes. A lookup's GUID lives in _parentcustomerid_value, not in parentcustomerid, and not in the name you see on the form.

That is the whole property. Filter array is a different action, on a payload you already paid to download. Putting Filter array syntax in Filter rows does not "filter harder." It sends a string Dataverse cannot evaluate as $filter.

Two boxes, two languages

Filter rows on List rows is $filter. Dataverse evaluates it. You get back the rows that match, or an empty value array if none do — the same successful empty as a business-key miss. A broken OData string is a failed List rows. Those are not the same outcome.

Filter array is a Data Operations action. It walks body/value (or whatever array you hand it) in the flow. The language is Workflow Definition Language: @equals, item(), contains on the current row. Dataverse never sees it. You already pulled the page.

List rows  Filter rows
  parentcustomerid eq 'Contoso Ltd'
→ Dataverse does not treat parentcustomerid as a name column.
  You wanted the account's GUID on _parentcustomerid_value.

List rows  Filter rows
  @equals(item()?['parentcustomerid'], variables('AccountId'))
→ That is Filter array syntax. There is no item() in $filter.
  The string is not valid OData.

Filter array  From: outputs('List_rows')?['body/value']
  @equals(item()?['_parentcustomerid_value'], variables('AccountId'))
→ Valid. Late. You already listed contacts you did not need.

The Apply to each post already says Filter array is in-memory reduction after the fetch. This post is the lookup column, the OData name, and the query that has to run in Dataverse if you care which rows come back.

The attribute you actually filter

On the contact form, Parent Customer is parentcustomerid. That is the lookup. In the Web API payload, and in Filter rows, the foreign key is _parentcustomerid_value: a GUID. The display name you clicked in the lookup control is not stored in parentcustomerid. It is resolved from the related account or contact.

List rows
  Table name   Contacts
  Filter rows  _parentcustomerid_value eq '@{variables('AccountId')}'
  Select columns  contactid,fullname,_parentcustomerid_value

AccountId is the account's primary key from this run: the trigger body's accountid, an Add a new row output, a Get a row you already did. Interpolate it into the OData string. The quotes around the expression are the usual maker pattern and Dataverse accepts that GUID.

Do not put the account name in that predicate. Names are not unique, and this column is not a name. If you only have a name, List rows on Accounts with name eq 'Contoso Ltd' (and a row count of 2, so you can see duplicates), take the accountid, then filter contacts on _parentcustomerid_value. Two queries, one GUID. That is find. Comparing a name to a lookup field is not.

The same suffix applies to every lookup:

Form labelLogical nameFilter rows
Parent Customerparentcustomerid_parentcustomerid_value eq {guid}
Primary Contactprimarycontactid_primarycontactid_value eq {guid}
Ownerownerid_ownerid_value eq {guid}
Owning Business Unitowningbusinessunit_owningbusinessunit_value eq {guid}

The pattern is _ + lookup logical name + _value. Copy the logical name from the column in the maker portal. Do not guess from the display label. Parent Customer is not an OData identifier.

Select columns on List rows is a payload trim — logical names, comma-separated — the same idea as Select columns on the trigger, except here it does not decide whether the flow starts. Include _parentcustomerid_value if a later action needs the GUID. You do not need the lookup's display name in Select columns to filter on the GUID.

Integers, strings, and @equals

OData is typed. statuscode eq 1 is an integer. statuscode eq '1' is a string. On account and contact, Active is statecode eq 0 and statuscode eq 1. The form label "Active" is formatted text. It is not the filter. Quote strings (accountnumber eq 'US-001'). Do not quote the integer choice.

@equals(item()?['statuscode'], 1) belongs in Filter array, or in a Condition. In Filter rows it is characters Dataverse will not parse as $filter. The Get a row post called this out for accountnumber. Lookups are the case that still ships with the navigation name in the box, because that is the name on the form.

contains(fullname, 'smith') is Dataverse OData. contains(item()?['fullname'], 'smith') is Filter array. Same English word, two grammars. item() only exists when there is a current row in the flow.

If the interpolated value can contain a single quote (O'Brien), escape it before you concatenate. A quote inside an OData string terminates the literal. The action then fails. That is a failed List rows, not an empty list.

Expand Query on List rows is $expand. You name the navigation property, not _…_value. For a simple lookup that is often the logical name (primarycontactid). For a Customer lookup (account or contact) Dataverse exposes two navigations: parentcustomerid_account and parentcustomerid_contact. Expand the side you need. The GUID filter still uses _parentcustomerid_value either way.

List rows
  Table name    Contacts
  Filter rows   _parentcustomerid_value eq '@{variables('AccountId')}'
  Expand Query  parentcustomerid_account
  Select columns  contactid,fullname,_parentcustomerid_value

Expand is for fields on the parent in the same call. It is not a substitute for Filter rows. Expanding parentcustomerid_account and then Filter array on the account name still downloaded every contact on the page. Filter on the GUID first. Expand second, if you need the parent's columns.

You can filter on a related attribute in OData with the navigation: parentcustomerid_account/name eq 'Contoso Ltd'. Use that when you truly have only a name and cannot look the account up first. It is still a name, so it is still not unique. Prefer the GUID.

Filter array is the leftover test

Use Filter array when the predicate cannot be OData: a value you computed in the flow, a match against another array you already hold, a formatted annotation that is not a Dataverse column. The connector's payload includes a formatted display name next to _parentcustomerid_value. Filter array can read that. Filter rows cannot. If the rule is "this account's GUID," you do not need the name, and you should not download the table to find it.

List rows
  Table name   Contacts
  Filter rows  _parentcustomerid_value eq '@{variables('AccountId')}'
  Row count    500

Filter array   only if a second, non-OData rule remains
  From         outputs('List_rows')?['body/value']

Row count caps the page. It does not replace Filter rows. Five hundred unfiltered contacts and a Filter array on the GUID is still five hundred rows across the wire, then a reduction. Put the GUID in Filter rows. Keep Row count as a safety cap, and turn on pagination only when the filtered set is honestly larger than one page.

empty(body('Filter_array')) after a Filter array is a valid branch. It is also a smell when List rows had no Filter rows. You listed, then discarded. The discard should have been a where clause in Dataverse.

SharePoint is the same shape with different names. Get items Filter Query is OData. A lookup column is Account/Id eq 12 or Account/Title eq 'Contoso Ltd', not Account eq 'Contoso Ltd'. Filter array on item()?['Account']?['Id'] is again after the download. Get item by id is still not a lookup-by-title.

What to type in Filter rows

  • _logicalname_value eq plus a GUID this run already owns.
  • statecode eq 0, statuscode eq 1, accountnumber eq 'US-001' — attributes, typed literals.
  • Combined with and / or in OData, not nested Condition actions that run per row.
  • Not the lookup's form label, not parentcustomerid eq 'Contoso Ltd', not @equals(...).
  • Not a display name you hope Dataverse will resolve. Resolve the name to a GUID with List rows on the parent table, then filter children on _…_value.

Filter rows is the query. Filter array is a second pass over rows you already have. For a lookup, the column that query understands is _parentcustomerid_value. Put the GUID there, and List rows returns the contacts that belong to that account — or a successful empty array, which is a Condition, not a loop.

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.