Learn how to build a complete Power Automate system that creates Microsoft Planner tasks from SharePoint data, assigns them using identity-aware logic, and keeps status synchronized across your entire Microsoft 365 environment. Goes beyond the basics to cover bucket ID resolution, ETag handling, approval integration, and idempotency patterns you need in production.

Picture this: your project manager drops a new client intake form response every Monday morning, and someone on your team manually creates a dozen Planner tasks, assigns them to the right people, sets due dates, and updates the board as work progresses. It takes about two hours each week, nobody loves doing it, and things occasionally slip through the cracks when the person responsible is out. This is exactly the kind of structured, repeatable, rule-based work that Power Automate was built to eliminate.
Microsoft Planner is a surprisingly capable task management tool for teams already living inside Microsoft 365 — but it doesn't act on anything by itself. Every task is created by a human, assigned by a human, and updated by a human. Power Automate changes that equation entirely. You can wire Planner into the rest of your Microsoft 365 ecosystem so that tasks appear automatically when a SharePoint record is created, assignments shift when someone's workload changes, and status updates ripple outward to Teams channels and email summaries without anyone touching the Planner board.
By the end of this lesson, you'll have built a complete, production-ready project intake automation that covers the full lifecycle of a Planner task from creation to closure. Here's exactly what you'll be able to do:
What you'll learn:
You should already be comfortable building flows in Power Automate — if you're new to the interface, take a step back and get oriented with navigating the designer and running your first test before continuing. You'll also benefit from knowing how triggers determine when a flow starts, since we'll use multiple trigger types in this lesson. A working Microsoft 365 account with access to Planner, SharePoint, and Teams is required.
Before you start building, it's worth understanding how Power Automate talks to Planner — because the connector has some quirks that will bite you if you don't know about them.
The Planner connector in Power Automate authenticates using delegated permissions, meaning it acts as the signed-in user who created the connection. This is different from some connectors that can use service account credentials or managed identities. The practical implication: tasks created by a flow will show the flow owner as the creator in the audit log, which matters for governance and compliance reasons.
The connector exposes the following core actions you'll use throughout this lesson:
That last point is the one that surprises people most. When you create a task and need to put it in the "In Progress" bucket, you can't just type "In Progress." You need the bucket's GUID, which looks something like _PLsOMqQwkiLOFN3vDzVkJZAGKk4. We'll handle this properly with a lookup pattern rather than hardcoding IDs.
Warning: Never hardcode Planner plan IDs or bucket IDs directly into your flow expressions. These IDs change if a plan is deleted and recreated, and they differ between environments. Store them in environment variables or a SharePoint configuration list so your flow degrades gracefully when plans are reorganized.
For this lesson, we're building a client project onboarding workflow for a consulting firm. When a new project is added to a SharePoint list called "Projects," the automation should:
Create a SharePoint list with these columns:
| Column Name | Type | Notes |
|---|---|---|
| Title | Single line of text | Project name |
| ClientName | Single line of text | |
| ProjectType | Choice | Options: Analytics, Implementation, Advisory |
| ProjectManager | Person | Who owns the project |
| TechLead | Person | Who leads technical delivery |
| StartDate | Date | |
| PlannerPlanId | Single line of text | Populated by the flow after creation |
| OnboardingStatus | Choice | Not Started, In Progress, Complete |
Tip: The
PlannerPlanIdcolumn is populated by the flow on first run. This is a common pattern — let the automation capture and store the IDs it generates, rather than pre-populating them manually. This way the SharePoint record becomes a single source of truth for the project.
For this automation, assume you have a single shared "Client Projects" Planner plan with the following buckets:
Before building the flow, do a one-time lookup of your bucket IDs. The easiest way is to use the Planner connector's "List buckets in a plan" action in a temporary flow or use the Planner API directly via a browser. Store these in a SharePoint configuration list or Power Automate environment variables.
This is the core flow — triggered by a new SharePoint list item, it creates the standard onboarding task set in Planner and writes back the task IDs.
Use the "When an item is created" trigger from the SharePoint connector, pointed at your Projects list. Make sure you've set the Site Address and List Name correctly. Since you're reading how triggers work, you already know that SharePoint triggers poll on an interval — there's a slight delay between item creation and flow execution, which is perfectly acceptable for this use case.
Add an Initialize variable action for each task ID you'll need to reference later. Using variables means you can pass these IDs downstream into your Teams notification and status sync steps without re-querying Planner.
Variable: var_TaskId_Kickoff | Type: String | Value: (empty)
Variable: var_TaskId_Requirements | Type: String | Value: (empty)
Variable: var_TaskId_Stakeholders | Type: String | Value: (empty)
Variable: var_TaskId_EnvSetup | Type: String | Value: (empty)
Variable: var_OnboardingBucketId | Type: String | Value: (empty)
If you want to understand variable scoping in depth, the lesson on conditions, loops, and variables in Power Automate covers the mechanics thoroughly.
Rather than hardcoding bucket GUIDs, use the "List buckets in a plan" action with your plan ID (pulled from an environment variable), then filter to find the "Onboarding" bucket:
Action: List buckets in a plan
Plan Id: @{variables('var_PlanId')}
Action: Filter array
From: @{outputs('List_buckets_in_a_plan')?['body/value']}
Filter: @{item()?['name']} is equal to "Onboarding"
Action: Set variable — var_OnboardingBucketId
Value: @{first(body('Filter_array'))?['id']}
This pattern — list everything, filter to what you need, grab the ID — is the correct way to work with Planner's GUID-heavy API without brittle hardcoding.
Now create each standard task. Here's the "Kickoff Call" task as a detailed example:
Action: Create a task
Plan Id: @{variables('var_PlanId')}
Title: Kickoff Call — @{triggerOutputs()?['body/Title']}
Bucket Id: @{variables('var_OnboardingBucketId')}
Due Date: @{addDays(triggerOutputs()?['body/StartDate'], 3)}
Assigned User Ids: @{triggerOutputs()?['body/ProjectManager/Claims']}
Priority: 1
Percent Complete: 0
A few things worth explaining here:
The title pattern includes the project name so tasks remain identifiable when the Planner board shows dozens of items across multiple projects. This seems obvious but is frequently skipped.
Due date expression uses addDays() to calculate relative to the project start date. For a kickoff call, three business days is reasonable. You can make this more sophisticated — the mastering dynamic expressions lesson covers business day calculations and date manipulation in detail.
Assigned User Ids takes the SharePoint Person column's Claims property, which is the Azure AD user UPN in the format i:0#.f|membership|user@domain.com. Planner actually wants the Azure AD Object ID, not the UPN — which means you need one more step.
Warning: This is the single most common failure point in Planner automation. The Planner connector's "Assigned User Ids" field expects Azure AD Object IDs (GUIDs like
8a7b3c42-d1e2-4f5a-b6c7-d8e9f0a1b2c3), not UPNs or email addresses. You must resolve user identities before passing them to Planner actions.
Add an Office 365 Users - Get user profile (V2) action before each Create task action that needs an assignment:
Action: Get user profile (V2) — for Project Manager
User (UPN): @{triggerOutputs()?['body/ProjectManager/Email']}
Action: Get user profile (V2) — for Tech Lead
User (UPN): @{triggerOutputs()?['body/TechLead/Email']}
Then reference the id property from those outputs in your Create task actions:
Assigned User Ids: @{outputs('Get_user_profile_(V2)_PM')?['body/id']}
Now your task assignments will work correctly every time.
The requirements doc task should be assigned to different people depending on the project type. Use a Switch action (or nested conditions) on the ProjectType field:
Switch on: @{triggerOutputs()?['body/ProjectType/Value']}
Case: "Analytics"
→ Create task: Requirements Document
Assigned User Ids: @{outputs('Get_user_profile_TechLead')?['body/id']}
Case: "Implementation"
→ Create task: Requirements Document
Assigned User Ids: @{outputs('Get_user_profile_TechLead')?['body/id']}
[Also adds an additional checklist item for integration specs]
Case: "Advisory"
→ Create task: Requirements Document
Assigned User Ids: @{outputs('Get_user_profile_PM')?['body/id']}
[Advisory projects have PM-owned requirements, not tech lead]
This is where Power Automate starts paying real dividends — you're encoding business logic that previously lived only in your team's collective memory.
After each Create a task action, immediately set the corresponding variable:
Action: Set variable — var_TaskId_Kickoff
Value: @{outputs('Create_task_Kickoff')?['body/id']}
Once all tasks are created, update the SharePoint item with the plan ID and mark onboarding as started:
Action: Update item (SharePoint)
Id: @{triggerOutputs()?['body/ID']}
OnboardingStatus: In Progress
Tip: If you're creating many tasks in sequence and worried about API throttling, consider adding a brief Delay action (5-10 seconds) between task creation calls, or implement retry logic using run-after configuration. The lesson on error handling and retry patterns covers this pattern in depth.
Creating a Planner task with the Create a task action only sets the surface-level fields. To add a description, checklist items, or reference links, you need a separate Update task details action — and it must run after the task is created because it requires the task ID.
This is a design quirk of the Planner connector that confuses many builders: there are two different actions for updating a task, and they cover different fields.
Action: Update task details
Task Id: @{variables('var_TaskId_Kickoff')}
Description: |
Standard kickoff call for @{triggerOutputs()?['body/ClientName']}.
Agenda:
- Project scope review
- Team introductions
- Communication cadence agreement
- Tool access confirmation
Checklist Item 1 - Title: Send calendar invite to client
Checklist Item 1 - Is Checked: false
Checklist Item 2 - Title: Prepare project brief document
Checklist Item 2 - Is Checked: false
Checklist Item 3 - Title: Confirm attendees with PM
Checklist Item 3 - Is Checked: false
The description field supports plain text only — Planner doesn't render markdown in task descriptions, so don't waste time formatting with asterisks and headers.
For the checklist, Power Automate's Planner connector exposes a fixed number of checklist item fields in the designer. If you need to add checklist items programmatically based on variable data (for example, adding one checklist item per integration point from a list), you'll need to use the Planner HTTP API directly via a custom HTTP action or custom connector.
The second major flow handles status transitions. When a team member marks a task as complete in Planner, or when an external event (like a document being approved in SharePoint) should advance a task's status, this flow handles the synchronization.
Use the "When a task is completed" trigger from the Planner connector:
Trigger: When a task is completed
Plan Id: @{variables('var_PlanId')}
This trigger fires every time any task in the plan is marked 100% complete. You'll need to filter in the flow body to handle only the tasks relevant to your project onboarding workflow — and this is where storing task IDs in SharePoint pays off.
Add a Get items action against your Projects SharePoint list, filtering for the project whose onboarding task matches the completed task:
Action: Get items (SharePoint)
Site Address: [your site]
List Name: Projects
Filter Query: OnboardingStatus eq 'In Progress'
Then loop through the results and check whether any stored task ID matches the completed task:
Action: Apply to each (results from Get items)
Condition: Does this project own the completed task?
@{triggerOutputs()?['body/id']} is equal to
@{items('Apply_to_each')?['KickoffTaskId']}
OR
@{triggerOutputs()?['body/id']} is equal to
@{items('Apply_to_each')?['RequirementsTaskId']}
[... and so on for each task type]
If Yes:
→ [Handle the completion logic for this project]
Key insight: This lookup pattern — storing task IDs in SharePoint and querying back against them — is what makes your Planner automation genuinely stateful. Without it, you'd have no way to know which project a completed task belongs to, and you couldn't update the right SharePoint record.
When the requirements document task completes, the environment setup task should automatically move from "Onboarding" to "In Progress" bucket. This is how you build a workflow-aware Kanban board rather than a static one.
First, retrieve the "In Progress" bucket ID using the same filter-array pattern from Flow 1. Then:
Action: Update a task
Task Id: @{items('Apply_to_each')?['EnvSetupTaskId']}
Bucket Id: @{variables('var_InProgressBucketId')}
Due Date: @{addDays(utcNow(), 5)}
To determine whether to mark the project's onboarding as fully complete, you need to check the status of all onboarding tasks. Use Get a task for each task ID and inspect the percentComplete field:
Action: Get a task
Task Id: @{items('Apply_to_each')?['KickoffTaskId']}
Action: Get a task
Task Id: @{items('Apply_to_each')?['RequirementsTaskId']}
[... repeat for each task ...]
Condition: Are all tasks done?
@{outputs('Get_task_Kickoff')?['body/percentComplete']} is equal to 100
AND
@{outputs('Get_task_Requirements')?['body/percentComplete']} is equal to 100
AND
@{outputs('Get_task_Stakeholders')?['body/percentComplete']} is equal to 100
AND
@{outputs('Get_task_EnvSetup')?['body/percentComplete']} is equal to 100
If Yes:
→ Update SharePoint item: OnboardingStatus = "Complete"
→ Post Teams message: "🎉 Onboarding complete for [ClientName]!"
→ Send summary email to Project Manager
For the Teams notification, you can use the Power Automate Teams integration to post adaptive cards with rich formatting — far better than a plain text message when you want to summarize task completion status.
The third flow runs on a schedule — every weekday morning — and scans all in-progress projects for overdue tasks. This is the kind of proactive monitoring that makes the system genuinely useful rather than just reactive.
Use a Recurrence trigger set to run at 8:00 AM on weekdays. The lesson on scheduling and time-based flows covers timezone handling and business hours logic, which you'll want for multi-region teams.
Action: Get items (SharePoint)
List Name: Projects
Filter Query: OnboardingStatus eq 'In Progress'
Top Count: 100
Warning: If you have more than 100 in-progress projects, the default Get items action won't return them all. You'll need pagination handling. The detailed approach is covered in the lesson on handling pagination and throttling when querying large datasets.
Loop through each in-progress project and check task due dates:
Action: Apply to each (Projects)
Action: Get a task (Kickoff)
Task Id: @{items('Apply_to_each')?['KickoffTaskId']}
Condition: Is Kickoff task overdue?
@{outputs('Get_task_Kickoff')?['body/percentComplete']} is less than 100
AND
@{outputs('Get_task_Kickoff')?['body/dueDateTime']} is less than @{utcNow()}
If Yes:
→ Set variable var_OverdueTasks: append task title and due date
→ Send email to Project Manager about overdue task
The expression for comparing dates against now deserves a closer look:
@{less(outputs('Get_task_Kickoff')?['body/dueDateTime'], utcNow())}
This returns true if the due date is in the past. Combine this with a check that percentComplete is less than 100 to avoid flagging tasks that are overdue but already finished.
For the escalation email, you have a well-established pattern available — the email notification automation lesson covers dynamic email composition with rich HTML formatting that makes overdue task summaries actually readable.
One scenario that comes up frequently in project onboarding is a task that shouldn't be marked complete until a formal approval happens. The "Requirements Document" task is a good example — it should only close when the client signs off, not when the consultant decides they're done writing.
You can wire Power Automate's approval system into Planner status updates using a pattern like this:
The building approval workflows lesson covers the approval action mechanics in depth. The key integration point is using the task ID from the trigger to perform the update after the approval response comes in.
Trigger: When a task is updated (Planner)
Plan Id: @{variables('var_PlanId')}
Condition: Was the task moved to Review bucket?
@{triggerOutputs()?['body/bucketId']} is equal to @{variables('var_ReviewBucketId')}
AND
@{triggerOutputs()?['body/percentComplete']} is less than 100
If Yes:
Action: Start and wait for an approval
Title: Client sign-off needed: @{triggerOutputs()?['body/title']}
Assigned to: @{[approver email from your config]}
Details: Task requires client approval before closing.
If Approved:
Action: Update a task
Task Id: @{triggerOutputs()?['body/id']}
Percent Complete: 100
Bucket Id: @{variables('var_DoneBucketId')}
If Rejected:
Action: Update a task
Bucket Id: @{variables('var_InProgressBucketId')}
Action: Update task details
Description: ⚠️ Returned from review: @{body('Start_and_wait_for_an_approval')?['response']['comments']}
Build the complete three-flow system described in this lesson against a test SharePoint site and Planner plan you control. Here's your structured checklist:
Phase 1 — Setup (30 minutes)
SettingName (text) and SettingValue (text), and add a row for your Planner plan ID and each bucket IDPhase 2 — Flow 1: Task Creation (60 minutes)
Phase 3 — Flow 2: Status Updates (45 minutes)
Phase 4 — Flow 3: Escalation (30 minutes)
Stretch goal: Add an adaptive card to the Teams completion notification that shows all task completion dates and who completed each one, using data pulled from the task history.
Cause: You passed a UPN or email string into the Assigned User Ids field instead of an Azure AD Object ID.
Fix: Add a Get user profile (V2) action before the task creation and use outputs('Get_user_profile')?['body/id'] as your assigned user value. Verify the ID looks like a GUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx), not an email.
Cause: The Planner API uses ETags for concurrency control. If you try to update task details immediately after creating the task in the same flow run, the ETag may not yet be stable.
Fix: Add a Delay action of 5-10 seconds between the Create task and Update task details actions. Alternatively, add a Get a task action and use the @odata.etag value in a custom HTTP call if you need precise control.
Cause: The Planner "When a task is updated" trigger fires on any change to the task, including system updates that happen in the background.
Fix: Add a condition at the very top of your flow to check that the relevant field actually changed. For bucket changes, compare triggerOutputs()?['body/bucketId'] against a known value. For percent complete changes, check triggerOutputs()?['body/percentComplete'] equals 100. Use run history to inspect trigger payloads and understand exactly what changed each time the flow ran.
Cause: The Apply to each loop is likely using a Set variable action inside it. Variables in Power Automate are scoped to the entire flow, not to the current loop iteration. If you set var_TaskId_Kickoff inside a loop, you'll always see the last iteration's value when you reference it.
Fix: Use the current item's properties directly via items('Apply_to_each')?['FieldName'] rather than setting intermediate variables inside loops. If you genuinely need to accumulate values, use an array variable and the Append to array variable action. This is a fundamental Power Automate concept — the conditions, loops, and variables lesson explains the scoping behavior in detail.
Cause: Planner stores dates in UTC. When you pass a date from a SharePoint date column, it may be stored as midnight local time, which when converted to UTC could be the previous day.
Fix: Use startOfDay(convertTimeZone(triggerOutputs()?['body/StartDate'], 'UTC', 'Eastern Standard Time')) to normalize to the intended local day before passing to Planner. Adjust the timezone to match your organization's primary timezone.
Cause: SharePoint's "When an item is created" trigger occasionally fires twice if the item is saved rapidly or if there's a network interruption during the first run.
Fix: Add idempotency protection at the top of your task creation flow. Check whether a task ID already exists in the SharePoint record before creating new tasks:
Condition: Has the onboarding already been set up?
@{triggerOutputs()?['body/PlannerPlanId']} is not equal to (empty string)
If Yes:
→ Terminate (Succeeded) — tasks already created, skip
If No:
→ Continue with task creation
You've now built a complete, production-grade Planner automation covering task creation with conditional assignment, status synchronization back to SharePoint, approval-gated task closure, and proactive overdue escalation. The key architectural decisions that make this system maintainable:
The natural next step is to make this system scale across multiple plans and team structures. As the number of project types and teams grows, you'll want to extract the task creation logic into a child flow architecture where each project type calls a standardized task-creation child flow with parameters rather than having branching logic grow unwieldy in a single flow. You might also consider parallel branching for creating all four onboarding tasks simultaneously rather than sequentially — cutting task creation time from ~20 seconds to under 10 seconds when Planner API rate limits allow.
If you're deploying this in a regulated environment or need to audit who created and modified tasks, the ALM and environment management lesson will guide you through packaging these flows as a solution with environment variables, making it promotable from dev to production without manual reconfiguration.