Overview
Dynamic entity routing enables Power Automate flows to interact with multiple Dataverse tables through a single flow design. By extracting entity metadata from trigger payloads, converting logical names to entity set names, and using Switch or lookup table patterns, you can build modular flows that determine the correct table at runtime without duplicating logic for each table type.
Prerequisites
- Advanced Power Automate experience
- Deep understanding of Dataverse tables and entity names
- Familiarity with expressions and string manipulation
- Experience with polymorphic lookups and entity metadata
Why Dynamic Entity Routing Matters
In scalable Power Platform solutions, it's common to build flows that interact with multiple Dataverse tables — especially when records share a common process such as approvals, logging, or status updates. These flows often need to determine the correct table at runtime, without hardcoding logic for each entity.
Typical use cases include:
- Approval workflows triggered by various record types across departments
- Audit logging that tracks changes across multiple entities
- Status updates pushed from external systems into Dataverse
- Generic notifications or escalations tied to different business objects
To keep flows modular and maintainable, you can dynamically extract the entity name, pluralise it, and route the update to the correct Dataverse table — all within a single Power Automate flow.
Benefits of dynamic routing:
- Reusability – One flow handles multiple table types instead of separate flows per table
- Maintainability – Update logic once rather than editing dozens of table-specific flows
- Scalability – Easy to add new tables without creating new flows
- Governance – Centralised logic reduces inconsistencies across tables
Dataverse triggers and actions expose entity metadata in their outputs, including the entity logical name (singular: contact, account, opportunity) and entity set name (plural: contacts, accounts, opportunities). Dynamic routing extracts this metadata to determine which table to update at runtime, eliminating hardcoded table references and enabling one flow to handle multiple entity types.
Getting Table Name from Trigger Payload
Start by capturing the following fields from your trigger or input payload:
- EntityLogicalName – e.g. contact, opportunity
- EntityId – the GUID of the record
- Type – optional, useful for polymorphic lookups
Use a Filter array or Parse JSON action to isolate these values. Then extract them with expressions like:
first(body('Filter_array_EntityLogicalName'))?['contact_opportunityid']
first(outputs('Filter_array_EntityId'))?['value'] (', '', ')', '', ')')
Example trigger output structure:
{
"EntityLogicalName": "contact",
"EntityId": "a7b3c5e2-1234-5678-90ab-cdef12345678",
"Type": "account",
"RecordData": {
"firstname": "John",
"lastname": "Smith"
}
}
Extracting entity logical name:
// From trigger body
triggerBody()?['EntityLogicalName']
// From Parse JSON output
body('Parse_JSON')?['EntityLogicalName']
// From webhook payload
outputs('When_HTTP_request_received')?['body']?['EntityLogicalName']
If your payload uses type instead of value, adjust accordingly:
first(body('Filter_array_EntityType'))?['type']
Example: Extracting from Dataverse trigger
Start by operating the following fields from your trigger or input payload:
// When a row is added, modified, or deleted trigger
// Metadata available in trigger outputs
Compose: Entity Logical Name
outputs('When_a_row_is_modified')?['body']?['@odata.type']
// Returns: Microsoft.Dynamics.CRM.contact
// Extract just "contact" using replace and split
Compose: Clean Entity Name
last(split(outputs('Compose_Entity_Logical_Name'), '.'))
// Returns: contact
Alternative: Using custom webhook payloads
If triggering from external systems via HTTP request trigger:
// HTTP request payload schema
{
"type": "object",
"properties": {
"EntityLogicalName": {"type": "string"},
"EntityId": {"type": "string"},
"UpdatedFields": {"type": "object"}
}
}
// Access in flow
triggerBody()?['EntityLogicalName']
triggerBody()?['EntityId']
Not all triggers expose entity metadata in the same format. Dataverse triggers include @odata.type annotations, while HTTP request triggers depend on payload design. PowerApps triggers require passing entity name as parameter. Always examine trigger outputs in flow run history to identify where entity metadata is located before building extraction logic.
Converting Logical Name to Entity Set Name
Dataverse uses pluralised Entity Set Names in its Web API and connector actions. These names aren't always intuitive, so here's a basic expression to handle common pluralisation rules:
if(endsWith(outputs('Compose_EntityLogicalName'), 'y'),
concat(substring(outputs('Compose_EntityLogicalName'), 0, sub(length(outputs('Compose_EntityLogicalName')), 1)), 'ies'),
if(or(endsWith(outputs('Compose_EntityLogicalName'), 's'), endsWith(outputs('Compose_EntityLogicalName'), 'x')),
concat(outputs('Compose_EntityLogicalName'), 'es'),
concat(outputs('Compose_EntityLogicalName'), 's')
)
)
This handles most cases. For irregular plurals, consider using a Dataverse lookup table to store overrides.
Pluralisation rules explained:
| Rule | Logical Name | Entity Set Name |
|---|---|---|
| Ends with 'y' → replace with 'ies' | opportunity | opportunities |
| Ends with 's' or 'x' → add 'es' | address | addresses |
| Default → add 's' | contact | contacts |
| Irregular (manual mapping) | systemuser | systemusers |
Breaking down the pluralisation expression:
// Step 1: Check if ends with 'y'
if(endsWith(outputs('Compose_EntityLogicalName'), 'y'),
// Yes: Remove 'y' and add 'ies'
concat(
substring(
outputs('Compose_EntityLogicalName'),
0,
sub(length(outputs('Compose_EntityLogicalName')), 1)
),
'ies'
),
// No: Check if ends with 's' or 'x'
if(
or(
endsWith(outputs('Compose_EntityLogicalName'), 's'),
endsWith(outputs('Compose_EntityLogicalName'), 'x')
),
// Yes: Add 'es'
concat(outputs('Compose_EntityLogicalName'), 'es'),
// No: Just add 's'
concat(outputs('Compose_EntityLogicalName'), 's')
)
)
Handling irregular plurals with lookup table:
For maximum flexibility, store these mappings in a Dataverse table with columns for LogicalName and EntitySetName. This allows you to override edge cases and future-proof your flow.
// Lookup mapping table
List rows:
Table: Entity Name Mappings
Filter: LogicalName eq '@{variables('varEntityLogicalName')}'
// If match found, use mapped value
if(
greater(length(outputs('List_rows')?['body/value']), 0),
outputs('List_rows')?['body/value']?[0]?['EntitySetName'],
// Else use pluralisation expression as fallback
[pluralisation expression here]
)
Example mappings table:
| LogicalName | EntitySetName | Notes |
|---|---|---|
| systemuser | systemusers | Irregular - doesn't follow normal rules |
| incident | incidents | Case table - standard pluralisation |
| cr_customentity | cr_customentities | Custom table with publisher prefix |
Build the lookup table approach from the start, even if you only have a few tables initially. Maintaining pluralisation logic in expressions becomes unwieldy as you add more tables. A Dataverse lookup table is easier to update, allows non-developers to manage mappings, and eliminates complex nested if() expressions that are hard to maintain and troubleshoot.
Using Switch Control for Table Selection
Once you've pluralised the table name, use a Switch control or a lookup table to route the update. This keeps your flow modular and avoids hardcoding logic.
Example mapping:
- contact → contacts
- opportunity → opportunities
- clientreview → clientreviews
For maximum flexibility, store these mappings in a Dataverse table with columns for LogicalName and EntitySetName. This allows you to override edge cases and future-proof your flow.
Method 1: Switch control
Action: Switch
On: outputs('Compose_Entity_Set_Name')
Case: contacts
Update a row:
Table: Contacts
Row ID: outputs('Compose_Entity_ID')
[field updates]
Case: opportunities
Update a row:
Table: Opportunities
Row ID: outputs('Compose_Entity_ID')
[field updates]
Case: incidents
Update a row:
Table: Cases
Row ID: outputs('Compose_Entity_ID')
[field updates]
Default:
// Log error - unrecognised entity type
Method 2: Direct HTTP request (most flexible)
Action: HTTP with Azure AD
Method: PATCH
Base Resource URL: https://[org].crm.dynamics.com/
Azure AD Resource URI: https://[org].crm.dynamics.com/
URI:
concat(
'api/data/v9.2/',
outputs('Compose_Entity_Set_Name'),
'(',
outputs('Compose_Entity_ID'),
')'
)
Headers:
{
"Content-Type": "application/json"
}
Body:
{
"statuscode": 2,
"description": "Updated via dynamic routing"
}
This HTTP approach allows truly dynamic table selection without Switch cases — the table name is constructed at runtime in the URI.
Comparison of routing methods:
| Method | Pros | Cons |
|---|---|---|
| Switch control | Visual clarity, easy to debug, action-specific configuration | Must add case for each new table, doesn't scale well |
| HTTP with Azure AD | Fully dynamic, scales infinitely, no cases needed | Requires Azure AD app registration, harder to debug, raw JSON body |
| Lookup table routing | Non-developers can manage, data-driven configuration | Complexity overhead, still needs Switch or HTTP |
Example HTTP body for dynamic updates:
// Store update fields in variables or compose actions
{
"@{variables('varFieldName1')}": "@{variables('varFieldValue1')}",
"@{variables('varFieldName2')}": "@{variables('varFieldValue2')}",
"modifiedon": "@{utcNow()}"
}
// Or use Parse JSON to build from trigger payload
@{body('Parse_JSON')?['UpdateFields']}
The HTTP with Azure AD action requires an Azure AD app registration with Dataverse API permissions. Register an app in Azure Portal, grant user_impersonation permission to Dynamics CRM, and configure the connection in Power Automate. This setup enables authentication for dynamic HTTP requests to Dataverse Web API without hardcoded credentials.
Dynamic Routing with Multi-Table Lookups
In polymorphic scenarios, the type or EntityLogicalName becomes essential. Use it to:
- Determine which Dataverse table to query or update
- Log activity against the correct entity without redundant handling
- Build scalable forms that allow tri-flow services and links
Example: Setting Regarding field dynamically
// Entity type from payload
Compose: Entity Type
triggerBody()?['RegardingEntityType']
Compose: Entity ID
triggerBody()?['RegardingEntityID']
// Pluralise entity type
Compose: Entity Set Name
[pluralisation expression using varEntityType]
// Create activity with dynamic Regarding
Add a new row:
Table: Emails
Subject: "Follow up"
Regarding:
concat(
'/',
outputs('Compose_Entity_Set_Name'),
'(',
outputs('Compose_Entity_ID'),
')'
)
Example: Querying related records dynamically
// Get activities related to any entity type
List rows:
Table: Activities
Filter rows:
_regardingobjectid_value eq @{variables('varEntityID')}
and
regardingobjecttypecode eq '@{variables('varEntityLogicalName')}'
// Returns activities for specified entity regardless of type
Example: Dynamic expansion queries
// Build expand query dynamically based on entity type
Compose: Expand Query
if(
equals(variables('varEntityType'), 'account'),
'parentaccountid($select=name)',
if(
equals(variables('varEntityType'), 'contact'),
'parentcustomerid_account($select=name)',
''
)
)
// Use in Get row
Get a row:
Table: [Selected dynamically via Switch or HTTP]
Row ID: variables('varEntityID')
Expand Query: outputs('Compose_Expand_Query')
This approach supports modular architecture and aligns with governance-first design principles.
When building polymorphic lookup references dynamically, always validate that the entity type is in your allowed list before constructing the reference. Unknown or misspelled entity types cause InvalidLookupReference errors. Use a Condition to check if entity type matches expected values, and handle invalid types gracefully with error logging rather than letting the flow fail.
Design Considerations for Dynamic Routing
Best practices:
- Validate entity names early – Check that extracted entity names are in your expected list before routing
- Use lookup tables for mappings – Store logical name to entity set name mappings in Dataverse for easy maintenance
- Log unknown entity types – When Switch default case or invalid entity is encountered, log to monitoring table
- Centralise pluralisation logic – Create child flow or shared expression for entity name conversion
- Document entity metadata sources – Clearly document where entity type information comes from in different scenarios
Common pitfalls:
- Forgetting publisher prefixes – Custom tables include prefixes (cr_contact → cr_contacts) which pluralisation must handle
- Assuming standard pluralisation – Many system tables have irregular plurals (systemuser → systemusers, not systemusers)
- Hardcoding field names – Field names may differ across tables; dynamic routing works best with common fields
- Insufficient error handling – Dynamic flows have more failure points; implement comprehensive try-catch patterns
Performance considerations:
- Lookup table queries add latency — cache mappings in variables if possible
- Switch controls with many cases can impact readability — consider HTTP approach for 10+ tables
- HTTP requests to Dataverse Web API count against API limits — monitor usage
Governance and maintenance:
- Document which flows use dynamic routing and which tables they support
- Create Change Management process for adding new tables to dynamic flows
- Test thoroughly when adding tables — ensure pluralisation and field mappings work
- Use solutions to deploy dynamic flows and mapping tables together across environments
When NOT to use dynamic routing:
- Table-specific business logic that differs significantly across entities
- Performance-critical flows where Switch/HTTP overhead is unacceptable
- Simple scenarios with only 2-3 tables where separate flows are clearer
- When field names and structures differ completely across tables
Always test dynamic routing flows with data from EVERY supported table type before deploying to production. Test not just success cases but also missing data, null values, and invalid entity types. Create comprehensive test records covering edge cases for each table to ensure routing logic works reliably across all scenarios.
Building Scalable Dynamic Flows
By extracting and pluralising entity names dynamically, you can build flows that are scalable, governance-friendly, and easy to maintain. This technique empowers you to handle diverse record types without duplicating logic — ideal for consultancy-grade builds where flexibility and clarity are paramount.
Key takeaways:
- Extract entity logical name from trigger or payload metadata
- Convert logical name to entity set name using pluralisation rules or lookup table
- Route updates using Switch control or HTTP with Azure AD for maximum flexibility
- Handle polymorphic lookups by dynamically constructing entity references
- Validate entity types early and log errors for unsupported tables
Next steps for implementation:
- Create entity name mapping table in Dataverse with LogicalName and EntitySetName columns
- Build child flow for pluralisation logic that other flows can call
- Design HTTP with Azure AD template for dynamic Dataverse updates
- Document supported entity types and required field mappings
- Create test scenarios covering all supported tables
Expand your dynamic routing capabilities by exploring:
- Dynamic field mapping – Using configuration tables to map field names across different entity types
- Multi-environment deployment – Managing entity name mappings across dev/test/prod environments
- Error logging patterns – Building centralised error tracking for dynamic routing failures
- Performance optimisation – Caching entity metadata to reduce lookup table queries
- Child flow architecture – Designing reusable routing flows called by multiple parent flows
- API limit management – Monitoring and throttling dynamic HTTP requests to Dataverse
The Microsoft Dataverse Web API entity types documentation provides comprehensive information on entity logical names, entity set names, and metadata structures for building advanced dynamic routing solutions in Power Automate.
Need this built for your business?
We design and build production-grade Power Platform solutions for FM, Construction and Manufacturing businesses.
Book a Discovery Call →