If you’re building flows in Power Automate without trigger conditions, you’re wasting API calls and creating noise in your run history. I learned this after deploying a SharePoint approval flow with no condition on it at all: every single edit to the list fired the flow, whether or not anything relevant had changed, and the run history filled up with executions that never had any work to do.

Trigger conditions are expressions that determine whether your flow executes. Add one and the flow stops running at all for the edits it doesn’t care about, instead of running and then discovering there’s nothing to do. Here’s how to use them effectively with SharePoint.

That flow is also why trigger conditions stopped being optional in our review process. After that incident, “does this flow have a trigger condition, and can you justify why it’s shaped the way it is” became a required question in flow review, not a nice-to-have. A rule that only lives in documentation gets skipped under deadline pressure; a rule that’s a required line in the review doesn’t.

Trigger conditions use Workflow Definition Language expressions starting with @. Common functions: equals(), not(), and(), or(), greater(). The single most common way these silently fail: the field name in your condition doesn’t match what SharePoint actually sends in triggerBody(), because internal column names diverge from display names in ways you won’t see until you go looking (more on that below). Here are SharePoint-specific patterns that reduce unnecessary runs.

When an Item Is Created or Modified

Run only when specific person modifies:

@equals(triggerBody()?['Editor']?['Email'], '[email protected]')

Run only once, using a sentinel column: SharePoint triggers don’t expose an old-value comparison the way Dataverse triggers do, so “only run when this field actually changed” isn’t available directly. The pattern that works instead: a column the flow itself flips after processing, checked as part of the condition.

@and(equals(triggerBody()?['Status'], 'Approved'), equals(triggerBody()?['ProcessedFlag'], false))

The flow’s last action sets ProcessedFlag back to true. Every SharePoint edit re-triggers the flow, but the condition only passes once, on the transition into Approved with the flag still false.

Run only for specific status:

@equals(triggerBody()?['Status'], 'Approved')

Exclude your own flow’s writes: any action in the flow that writes back to the same list (setting that sentinel flag, updating a status) re-triggers the flow unless the condition excludes it. Check the editor against the account the flow runs as:

@not(equals(triggerOutputs()?['body/Editor/Email'], '[email protected]'))

Without this, a flow that both reads and writes the same list can retrigger itself in a loop, and the loop is invisible until someone notices the run count.

When a File Is Created or Modified

Run only for specific file types:

@endsWith(triggerBody()?['FileLeafRef'], '.docx')

Run only for files larger than 1 MB:

@greater(int(triggerBody()?['Length']), 1048576)

Note: The property Length typically contains the file size in bytes, and it arrives as a string. Wrap it in int() before comparing, or the greater-than check runs a string comparison instead of a numeric one.

3. When an Item Is Deleted

Run Only When Items Are Deleted from a Specific Folder

For example, run when items are deleted from the SpecificFolder:

@startsWith(triggerBody()?['FileRef'], '/sites/YourSite/SpecificFolder/')

Note: Ensure you provide the correct server-relative URL to your folder.

Advanced Techniques

1. Combining Multiple Conditions

Use logical operators to combine multiple conditions:

  • and()
  • or()
  • not()

Example: Run when Status is Approved and Priority is High:

@and(
  equals(triggerBody()?['Status'], 'Approved'),
  equals(triggerBody()?['Priority'], 'High')
)

2. Working with Date/Time Fields

When dealing with date/time fields in SharePoint, you can compare date values directly. However, it’s important to ensure that both dates are in the same format and time zone. SharePoint date columns come back in UTC already, so the comparison usually doesn’t need a conversion step at all:

Example: Check if the DueDate is before now:

@less(triggerBody()?['DueDate'], utcNow())

If the column is genuinely stored in a different time zone and needs converting first, that’s what convertTimeZone() is for, but converting from UTC to UTC (as an earlier version of this example did) is a no-op that does nothing except add a function call.

3. Using Expressions

You can use expressions in your trigger conditions. For example, check if Quantity exceeds a minimum threshold (e.g., 10):

@greater(
  int(triggerBody()?['Quantity']),
  10
)

4. Checking for Column Changes

Dataverse triggers expose an old-value comparison directly; SharePoint triggers don’t. Across every production flow I checked while writing this, none used an old-value comparison against SharePoint, because the platform doesn’t send one. The working substitute is the sentinel-column pattern from the top of this post: a column the flow sets after it finishes, checked alongside the condition you actually care about, so a repeat trigger on the same value doesn’t reprocess it.

@and(
  equals(triggerBody()?['Status'], 'Approved'),
  equals(triggerBody()?['ProcessedFlag'], false)
)

5. Working with Person Fields

Person fields in SharePoint require special handling.

Check if the AssignedTo Field Is Not Empty

@not(empty(triggerBody()?['AssignedTo']))

Check if AssignedTo Is Assigned to a Specific User

@equals(triggerBody()?['AssignedTo']?['Email'], '[email protected]')

6. Handling Null Values and Missing Fields

It’s important to handle potential null values or missing fields to avoid errors.

Check if Status Is Approved, Accounting for Null or Missing Values

@and(
  not(empty(triggerBody()?['Status'])),
  equals(triggerBody()?['Status'], 'Approved')
)

Check if CustomField Exists and Equals ExpectedValue

@and(
  contains(triggerBody(), 'CustomField'),
  equals(triggerBody()?['CustomField'], 'ExpectedValue')
)

7. Working with Lookup Fields

Lookup fields in SharePoint contain more complex data. Here’s how to handle them:

Check if the Department Lookup Field Is Set to HR

@and(
  not(empty(triggerBody()?['Department']?['Value'])),
  equals(triggerBody()?['Department']?['Value'], 'HR')
)

Real-World Scenarios

Document Approval Workflow

For a document management system I built, flows were triggering on every single file upload regardless of document type. By implementing this trigger condition, I focused only on contract documents that needed approval:

@and(
  startsWith(triggerBody()?['FileLeafRef'], 'Contract-'),
  endsWith(triggerBody()?['FileLeafRef'], '.docx'),
  equals(triggerBody()?['ApprovalStatus'], 'Pending')
)

This reduced flow runs from hundreds per day to just the dozen or so contract uploads that actually needed processing.

HR Onboarding Process

For an employee onboarding system I built, I needed to trigger a flow only when an employee’s status changed to “Hired” for the first time, not on every subsequent edit to that record:

@and(
  equals(triggerBody()?['EmployeeStatus'], 'Hired'),
  equals(triggerBody()?['OnboardingFlowTriggered'], false)
)

The onboarding flow’s last step flips OnboardingFlowTriggered to true. Later edits to the same record still fire the trigger, but the condition only passes once, on the transition into “Hired.”

Metadata-Only Changes That Look Like Real Edits

SharePoint’s “Modified” trigger fires on more than what a user typed into a form. Permission changes, indexing passes, and background system sync can all touch an item’s Modified metadata without changing any field a person would recognize as data. A flow triggering on “item modified” alone, with no condition on which fields actually changed, will fire on all of that noise too. Checking the specific field you care about, rather than the fact that something on the item changed, is the difference between a flow that runs when content changes and one that runs whenever SharePoint feels like touching the item.

Troubleshooting Guide

When your trigger conditions aren’t working as expected, follow these steps:

  1. Verify Syntax: Double-check for missing parentheses, quotes, or operator typos.

  2. Test with Simpler Conditions: Replace your complex condition with something basic like @equals(1,1) to confirm the trigger condition functionality works.

  3. Examine Your Data: Use Flow outputs to verify what triggerBody() contains. The property names and structure might not be what you expect.

  4. Watch for Capitalization: SharePoint column internal names might differ from display names. Use triggerBody() without conditions to see the exact property names.

  5. Check Data Types: If comparing numbers, ensure you’re comparing the same types by using int() or float() functions.

I once spent hours debugging why a condition wasn’t working. I finally discovered that a SharePoint column with the display name “Approval Status” was actually coming through as Approval_x0020_Status in the trigger body, the internal name SharePoint generates from a display name with a space in it.

Best Practices

  1. Keep It Simple: Start with simple conditions and build up complexity as needed.
  2. Test Thoroughly: Always test your trigger conditions with various scenarios to ensure they work as expected.
  3. Document Your Expressions: Keep a record of all trigger conditions used, along with explanations. This aids in maintenance and troubleshooting.
  4. Monitor Performance: Complex trigger conditions can impact performance. Use the Power Automate analytics feature to monitor your flows’ run history and duration.
  5. Use Trigger Conditions Judiciously: While powerful, overuse of trigger conditions can make flows harder to maintain.
  6. Handle Null Values and Missing Fields: Always account for potential null values or missing fields in your conditions to avoid runtime errors.
  7. Use a Sentinel Column for SharePoint “Run Once” Logic: SharePoint triggers don’t expose an old-value comparison. A flag column the flow sets after processing is the working substitute.
  8. Test Each Expression Individually: Before deploying the trigger condition in a production environment, test it in a controlled setting to ensure it behaves as expected.

Performance Monitoring

To monitor the performance of your flows:

  1. Go to the Power Automate portal (https://make.powerautomate.com).
  2. Select your flow.
  3. Click on the Analytics tab.
  4. Here you can view run history, success rate, and average duration.

For more detailed monitoring, consider using Azure Application Insights. You can integrate Power Automate with Application Insights for advanced telemetry and diagnostics.

Common Pitfalls and Solutions

  1. Case Sensitivity: SharePoint column names are case-sensitive in trigger conditions. Double-check your column names.
  2. Data Types: Ensure you’re comparing the correct data types. Use functions like int(), string(), or float() to convert when necessary.
  3. Null Values: Always handle potential null values to avoid errors. Use functions like empty() and logical operators like and() and not() as demonstrated in the “Handling Null Values and Missing Fields” section.
  4. Complex Data Types: For lookups or person fields, access properties correctly. Refer to the examples in the “Working with Lookup Fields” and “Working with Person Fields” sections.
  5. Time Zones: Be aware of time zone differences when working with date/time fields. Use utcNow() and explicitly format dates to avoid inconsistencies.
  6. Throttling: While trigger conditions themselves don’t directly impact throttling, optimizing them can reduce unnecessary flow runs, indirectly mitigating throttling issues.

Key Takeaways

  • Trigger conditions save resources by preventing unnecessary flow runs. A flow with no condition runs on every edit; a flow with the right condition only runs on the edits it actually cares about.
  • Always handle null values and data type mismatches, or a missing field will silently break the condition.
  • SharePoint triggers don’t expose an old-value comparison. A sentinel column the flow sets after processing is the working substitute, and it also solves the loop-guard problem a naive old-value check wouldn’t.
  • Test with real data, including the malformed edge cases, before deployment.
  • Document your conditions. Sentinel-column and loop-guard logic especially aren’t self-explanatory six months later.

Check your run history for the flow with the highest run count. If it doesn’t have a trigger condition, that’s very likely where your unnecessary runs are coming from.

Additional Resources