Day 2 — Triggers - what starts a flow
Pick the wrong trigger and you will fight the flow forever. Pick the right one and half the work disappears.
The three kinds
| Kind | Starts when | Typical use |
|---|---|---|
| Automated | Something happens in a system | A form is submitted, an email arrives, a SharePoint item is created |
| Instant | A person presses a button | A manual button in the mobile app, or from Power Apps |
| Scheduled | The clock | Every weekday at 9am, the first of the month |
The trigger hands you data
Whatever started the flow brings its data with it. triggerBody() is how you reach it, and ?['FieldName'] picks one field out.
{
"trigger": {
"name": "When a new response is submitted",
"type": "automated",
"body": {
"ResponseId": "R-1042",
"Name": "Rahul Verma",
"Email": "rahul@example.com",
"Course": "Power BI",
"Experience": 4,
"SubmittedOn": "2026-03-18T09:12:00Z"
}
},
"actions": [
{ "name": "Everything", "type": "Compose", "input": "@triggerBody()" },
{ "name": "Name only", "type": "Compose", "input": "@triggerBody()?['Name']" },
{ "name": "Years", "type": "Compose", "input": "@triggerBody()?['Experience']" },
{ "name": "A field that is not there", "type": "Compose", "input": "@triggerBody()?['Phone']" }
]
}
Look at the last one. Asking for a field that does not exist gives you null, not an error - because of the ?. Day 4 goes into exactly why that question mark matters so much.
Scheduled flows
A recurrence trigger has no useful body - the clock does not hand you anything. Everything the flow needs, it fetches itself.
{
"trigger": {
"name": "Every weekday at 9am",
"type": "Recurrence",
"body": {}
},
"actions": [
{ "name": "Today", "type": "Compose", "input": "@utcNow()" },
{ "name": "Readable", "type": "Compose", "input": "@formatDateTime(utcNow(), 'dddd dd MMM yyyy')" },
{ "name": "India time", "type": "Compose",
"input": "@convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'HH:mm')" },
{ "name": "Report title", "type": "Compose",
"input": "Daily MIS - @{formatDateTime(utcNow(), 'dd MMM yyyy')}" }
]
}
The playground’s clock is frozen at 18 March 2026, 09:30 UTC so the examples give the same answer every time. Notice the India time above comes out at 15:00 - that is the 5 hours 30 minutes.
Trigger conditions - do not start at all
The obvious way to ignore some rows is a Condition as the first action. It works, but the flow still runs - it appears in your run history, it uses your daily quota, and the history fills up with runs that did nothing.
A trigger condition stops it before it starts. In this playground you can see the same idea with an early Terminate.
{
"trigger": { "type": "automated",
"body": { "Name": "Meera", "Course": "SQL", "Amount": 800 } },
"actions": [
{
"name": "Worth processing?",
"type": "Condition",
"expression": "@greaterOrEquals(triggerBody()?['Amount'], 1000)",
"yes": [
{ "name": "Carry on", "type": "Compose", "input": "Processing this enrolment" }
],
"no": [
{ "name": "Stop here", "type": "Terminate", "status": "Cancelled",
"message": "Amount below the threshold" }
]
},
{ "name": "Later work", "type": "Compose", "input": "This runs only if we did not stop" }
]
}
Run it. The flow takes the If no branch, stops, and "Later work" is marked Skipped. Change the Amount to 5000 and run again to see the other path.
Choosing the trigger
How soon must it react? Instant reaction needs an automated trigger. "Some time today" can be a schedule, which is far cheaper and easier to debug.
What if it fires twice? Some triggers fire again on an edit. If your flow adds a row every time, you will get duplicates. Design for it.
Try these yourself
- Write a flow whose trigger body has five fields, and compose three of them into a sentence.
- Print the current date in dd/MM/yyyy and in dddd dd MMMM yyyy.
- Work out what time a 9:00 UTC recurrence fires in India.
- Build a flow that terminates early when a field is empty.
- Give an example of a job that suits a scheduled trigger better than an automated one.
