10.3 Power Automate Expression Language (WDL / Functions)
Key Takeaways
- Power Automate expressions use the Workflow Definition Language (WDL), evaluating functions, dynamic tokens, and literal values within action parameters.
- The safe navigation operator '?' (e.g., body('Get_row')?['cr123_revenue']) prevents flow-crashing null reference exceptions when accessing optional or empty properties.
- String functions (concat, substring, toLower, toUpper, trim, split, replace, guid) and Collection functions (first, last, length, empty, contains, union, intersection) provide robust manipulation without custom code.
- Logical and comparison functions (if, equals, greater, less, and, or, not, coalesce) allow inline conditional evaluations and fallback value assignment.
- Date/Time functions (utcNow, addDays, addHours, formatDateTime, convertTimeZone) handle timezone adjustments and ISO 8601 formatting critical for scheduled and enterprise workflows.
Power Automate Expression Language (WDL / Functions)
While Microsoft Power Automate provides an extensive visual designer with prebuilt dynamic content tokens, enterprise scenarios frequently require operations beyond basic token mapping: calculating future business dates, parsing complex text formats, evaluating inline ternary logic, coercing data types, and preventing null reference crashes. The underlying engine executes these calculations using the Workflow Definition Language (WDL).
For the PL-200: Microsoft Power Platform Functional Consultant certification exam, you must master the syntax, function taxonomy, safe navigation operators, and execution rules of the Power Automate expression engine.
1. Workflow Definition Language (WDL) Architecture
WDL is the declarative expression language powering Azure Logic Apps and Power Automate cloud flows. Expressions are entered via the Expression Builder tab in the dynamic content pop-up or written directly into flow definitions.
+-----------------------------------------------------------------------------+
| WDL EXPRESSION SYNTAX ANATOMY |
| |
| @concat('Account: ', toUpper(triggerOutputs()?['body/name']), ' (', guid(), ')')
| |---| |----------| |------| |----------------------------| |----|
| Prefix Outer Func Inner Safe Navigation Token Scalar|
| (JSON) Func to Trigger Body Column Func |
+-----------------------------------------------------------------------------+
Core Syntax Principles
- Prefix Notation: Inside raw JSON definitions, expressions are prefixed with
@(e.g.,@equals(...)). When using the visual Expression Builder GUI, the leading@is omitted. - Function Nesting: Functions can be nested infinitely (e.g.,
toUpper(trim(first(split(variables('FullName'), ' '))))). - Case Sensitivity: Function names are case-insensitive in WDL (e.g.,
utcNow()andutcnow()are identical), but property schema names and dictionary keys are strictly case-sensitive. - Action Name Formatting: When referencing action names containing spaces, replace the spaces with underscores (e.g., action
Get customer recordbecomesoutputs('Get_customer_record')orbody('Get_customer_record')).
The Safe Navigation Operator: ?
In enterprise flows, data payloads often contain optional columns or null values. If an expression attempts to traverse a missing property using standard bracket syntax (e.g., body('Get_row')['address1_city']), and body('Get_row') is null or missing the property, the flow runtime crashes with an unhandled NullReferenceException.
The safe navigation operator (?) instructs the engine to return null rather than throwing an exception if the preceding object or property is undefined:
// Unsafe (Crashes if contact is null or missing telephone1):
outputs('Get_contact')['body']['telephone1']
// Safe Navigation (Evaluates cleanly to null if property is missing):
outputs('Get_contact')?['body']?['telephone1']
triggerOutputs()?['body/telephone1']
2. Referencing Trigger & Action Outputs
| Function / Syntax | Purpose | Example Syntax |
|---|---|---|
triggerOutputs() | Returns complete trigger output headers and body | triggerOutputs()?['body/accountnumber'] |
triggerBody() | Returns the trigger payload body directly | triggerBody()?['emailaddress1'] |
outputs('Action_Name') | Returns headers and body of a completed action | outputs('HTTP_Call')?['statusCode'] |
body('Action_Name') | Returns the parsed body payload of an action | body('Get_Account_Row')?['revenue'] |
item() | References the current element in an Apply to each loop | item()?['primarycontactid'] |
items('Loop_Name') | References current item of a specifically named loop | items('Apply_to_each_Order')?['total'] |
variables('VarName') | Retrieves current value of an initialized variable | variables('varCounter') |
3. Core WDL Function Taxonomy
+-----------------------------------------------------------------------------+
| WDL FUNCTION TAXONOMY |
| |
| +-----------------------+ +-----------------------+ +-----------+ |
| | STRING FUNCTIONS | | COLLECTION FUNCTIONS | | LOGICAL | |
| | - concat, substring | | - first, last, length | | - if | |
| | - toLower, toUpper | | - empty, contains | | - equals | |
| | - trim, split, replace| | - union, intersection | | - greater | |
| | - guid, indexOf | | - take, skip | | - coalesce| |
| +-----------------------+ +-----------------------+ +-----------+ |
| | | | |
| v v v |
| +-----------------------+ +----------------------------------------+ |
| | DATE & TIME FUNCS | | CONVERSION FUNCTIONS | |
| | - utcNow, addDays | | - string, int, float, bool | |
| | - addHours, addMinutes| | - json, xml, base64, base64ToString | |
| | - formatDateTime | | - uriComponent, uriComponentToString | |
| | - convertTimeZone | +----------------------------------------+ |
| +-----------------------+ |
+-----------------------------------------------------------------------------+
1. String Functions
| Function | Signature & Behavior | Example |
|---|---|---|
concat | Combines two or more strings into one | concat('EMP-', string(101)) --> 'EMP-101' |
substring | Extracts characters: substring(text, startIndex, length) | substring('PL-200 Exam', 0, 6) --> 'PL-200' |
toLower / toUpper | Converts text casing | toUpper('contoso') --> 'CONTOSO' |
trim | Strips leading and trailing whitespace | trim(' hello world ') --> 'hello world' |
split | Splits string by delimiter into an array | split('red;green;blue', ';') --> ['red','green','blue'] |
replace | Replaces all occurrences of substring | replace('2026/08/17', '/', '-') --> '2026-08-17' |
guid | Generates a new unique GUID string | guid() --> 'c7a8b3e1-9d24-4e89-b12a-8f9210a5b4c3' |
indexOf | Returns 0-based index of substring (-1 if not found) | indexOf('admin@contoso.com', '@') --> 5 |
2. Collection & Array Functions
| Function | Signature & Behavior | Example |
|---|---|---|
first | Returns the first element of an array or string | first(body('List_rows')?['value']) |
last | Returns the last element of an array or string | last(split('doc_v2_final.pdf', '.')) --> 'pdf' |
length | Returns item count in array or character count in string | length(body('List_rows')?['value']) --> 45 |
empty | Returns true if array, string, or object is empty/null | empty(triggerOutputs()?['body/websiteurl']) |
contains | Checks if collection contains item or string contains substring | contains(variables('AdminList'), 'jsmith') |
union | Combines two arrays/objects removing duplicate entries | union(variables('TeamA'), variables('TeamB')) |
intersection | Returns common elements present in both collections | intersection(varRolesA, varRolesB) |
take | Returns first N elements: take(collection, count) | take(body('List_rows')?['value'], 10) |
skip | Skips first N elements and returns remainder | skip(body('List_rows')?['value'], 10) |
3. Logical & Comparison Functions
| Function | Signature & Behavior | Example |
|---|---|---|
if | Inline ternary conditional: if(expression, valueIfTrue, valueIfFalse) | if(greater(variables('Score'), 700), 'Pass', 'Fail') |
equals | Strict equality comparison returning boolean | equals(triggerOutputs()?['body/statecode'], 0) |
greater / less | Relational numerical/chronological comparison | greater(item()?['revenue'], 500000) |
and / or / not | Compound boolean logic evaluation | and(equals(varTier, 'Gold'), not(empty(varEmail))) |
coalesce | Evaluates arguments left-to-right; returns first non-null/non-empty value | coalesce(triggerOutputs()?['body/telephone1'], triggerOutputs()?['body/mobilephone'], 'No Phone') |
[!IMPORTANT] The Power of
coalesceon PL-200:coalesceis the definitive WDL function for establishing fallback values when querying Dataverse lookups or optional fields. If a contact record hasmobilephonepopulated buttelephone1is null,coalesce(body('Get_contact')?['telephone1'], body('Get_contact')?['mobilephone'], 'N/A')immediately evaluates to the mobile number without requiring multiple nestedConditionorif()actions.
4. Date and Time Functions
| Function | Signature & Behavior | Example Output |
|---|---|---|
utcNow() | Current timestamp in UTC ISO 8601 | '2026-08-17T13:30:00.0000000Z' |
addDays | Adds/subtracts days: addDays(timestamp, days, format?) | addDays(utcNow(), 30, 'yyyy-MM-dd') |
addHours / addMinutes | Adds/subtracts hours or minutes from timestamp | addHours(utcNow(), -8) |
formatDateTime | Formats ISO timestamp using standard .NET specifiers | formatDateTime(utcNow(), 'MMMM dd, yyyy') |
convertTimeZone | Converts timestamp between standard IANA/Windows time zones | convertTimeZone(utcNow(), 'UTC', 'Eastern Standard Time', 'yyyy-MM-dd HH:mm') |
ticks | Returns 100-nanosecond tick count since 0001-01-01 (for date math) | ticks('2026-08-17T00:00:00Z') |
5. Type Conversion Functions
| Function | Purpose & Input Type | Example Conversion |
|---|---|---|
string | Converts any scalar/object into string representation | string(1050.50) --> '1050.5' |
int | Converts string representation of number to 64-bit integer | int('450') --> 450 |
float | Converts numeric string to floating-point number | float('19.99') --> 19.99 |
bool | Converts string 'true' or 'false' to boolean | bool('true') --> true |
json | Parses raw JSON string into JSON object/array | json('{"id": 1}') --> {"id": 1} |
base64 | Encodes string/binary into Base64 format (for email attachments) | base64('Sample Text') --> 'U2FtcGxlIFRleHQ=' |
base64ToString | Decodes Base64 string back to plaintext | base64ToString('U2FtcGxl...') --> 'Sample Text' |
4. Complex Nested Expression Patterns for PL-200
Pattern A: Extracting Email Domain for Dynamic Routing
// Input: 'sarah.connor@cyberdyne-systems.com'
// Expression: Extract substring after '@' and convert to lowercase
toLower(last(split(triggerOutputs()?['body/emailaddress1'], '@')))
// Evaluates to: 'cyberdyne-systems.com'
Pattern B: Calculating Dynamic Expiration Date in Local Time
// Calculate due date 14 days from now formatted for US Central Standard Time
formatDateTime(convertTimeZone(addDays(utcNow(), 14), 'UTC', 'Central Standard Time'), 'yyyy-MM-dd')
Pattern C: Safe Conditional Full Name Construction
// Build 'LastName, FirstName' if both exist, otherwise use whichever is populated
if(
and(not(empty(triggerOutputs()?['body/lastname'])), not(empty(triggerOutputs()?['body/firstname']))),
concat(triggerOutputs()?['body/lastname'], ', ', triggerOutputs()?['body/firstname']),
coalesce(triggerOutputs()?['body/lastname'], triggerOutputs()?['body/firstname'], 'Valued Customer')
)
A cloud flow receives an incoming webhook containing an optional customer shipping address payload. The consultant writes the expression 'body('Parse_Webhook')['shippingAddress']['postalCode']' inside a Compose action. When an order arrives without a shipping address object, the flow execution fails with an 'ActionFailed - Cannot evaluate property on null object' error. How should the consultant update the expression to prevent this failure and return null safely?
An automated cloud flow must generate a dynamic reminder notification for invoices created in Dataverse. The flow must calculate a due date exactly 45 days after the current UTC execution time, convert the resulting timestamp to 'Pacific Standard Time', and format the output as 'yyyy-MM-dd'. Which WDL expression correctly achieves this?
A functional consultant needs to extract the top-level domain extension from an email address stored in the variable 'varEmail' (e.g., extracting 'org' from 'contact@charity.org' or 'com' from 'user@contoso.com'). Which expression returns the file extension or domain extension following the final period in the string?
A flow reads an employee record where the consultant needs to populate a greeting string. The flow must check 'preferredName'; if 'preferredName' is null or empty, it must fall back to 'firstName'; if 'firstName' is also null or empty, it must default to 'Colleague'. Which WDL function is the most concise and efficient way to implement this fallback logic?