Msg.Planner.Plans (msg v0.3.8)

Manage Microsoft Planner Plans.

Planner Plans are containers for tasks. Each Microsoft 365 Group can have multiple Plans, which provide project management functionality within Teams and other Microsoft 365 apps.

Required Permissions

Group Plans

  • Application: ❌ Not supported
  • Delegated: Tasks.ReadWrite or Group.ReadWrite.All - required for group plans

User-Accessible Plans

  • Application: Tasks.ReadWrite.All - read/write all plans
  • Delegated: Tasks.ReadWrite - read/write user's accessible plans

Authentication

  • Group plans (/groups/{group_id}/planner/plans): Requires delegated permissions
  • User-accessible plans (/users/{user_id}/planner/plans): Works with application-only

Etag-Based Concurrency

Planner API requires etags for all update and delete operations to prevent conflicts. Etags are returned in the @odata.etag field and must be included in the If-Match header for PATCH and DELETE requests.

Examples

# List plans for a group (requires delegated permissions)
delegated_client = Msg.Client.new(refresh_token, credentials)
{:ok, plans} = Msg.Planner.Plans.list(delegated_client, group_id: "group-id")

# Create a plan
{:ok, plan} = Msg.Planner.Plans.create(delegated_client, %{
  owner: "group-id",
  title: "Project: Q1 Marketing Campaign"
})

# Update a plan (requires etag)
{:ok, updated} = Msg.Planner.Plans.update(delegated_client, plan_id, %{
  title: "Updated Title"
}, etag: plan["@odata.etag"])

# Delete a plan (requires etag)
:ok = Msg.Planner.Plans.delete(delegated_client, plan_id, plan["@odata.etag"])

References

Summary

Functions

Creates a new Planner Plan.

Deletes a Planner Plan.

Gets a single Planner Plan by ID.

Lists Planner Plans.

Updates a Planner Plan.

Functions

create(client, plan)

@spec create(Req.Request.t(), map()) :: {:ok, map()} | {:error, term()}

Creates a new Planner Plan.

Parameters

  • client - Authenticated Req.Request client
  • plan - Map with plan properties:
    • :owner (required) - Group ID that owns the plan
    • :title (required) - Plan name

Returns

  • {:ok, plan} - Created plan with generated id and @odata.etag
  • {:error, :unauthorized} - Invalid or expired token
  • {:error, {:invalid_request, message}} - Validation error
  • {:error, term} - Other errors

Examples

{:ok, plan} = Msg.Planner.Plans.create(client, %{
  owner: "group-id-here",
  title: "Project: Q1 Marketing Campaign"
})

delete(client, plan_id, etag)

@spec delete(Req.Request.t(), String.t(), String.t()) :: :ok | {:error, term()}

Deletes a Planner Plan.

Important: Requires the current etag for concurrency control.

Parameters

  • client - Authenticated Req.Request client
  • plan_id - ID of plan to delete
  • etag - Current etag for concurrency control

Returns

  • :ok - Plan deleted successfully (204 status)
  • {:error, {:etag_mismatch, current_etag}} - Etag conflict (412)
  • {:error, :not_found} - Plan doesn't exist
  • {:error, term} - Other errors

Examples

{:ok, plan} = Msg.Planner.Plans.get(client, plan_id)
:ok = Msg.Planner.Plans.delete(client, plan_id, plan["@odata.etag"])

get(client, plan_id)

@spec get(Req.Request.t(), String.t()) :: {:ok, map()} | {:error, term()}

Gets a single Planner Plan by ID.

Parameters

  • client - Authenticated Req.Request client
  • plan_id - ID of the plan to retrieve

Returns

  • {:ok, plan} - Plan map with details including @odata.etag
  • {:error, :not_found} - Plan doesn't exist
  • {:error, term} - Other errors

Examples

{:ok, plan} = Msg.Planner.Plans.get(client, "plan-id")
etag = plan["@odata.etag"]

list(client, opts)

@spec list(
  Req.Request.t(),
  keyword()
) :: {:ok, [map()]} | {:ok, map()} | {:error, term()}

Lists Planner Plans.

Parameters

  • client - Authenticated Req.Request client
  • opts - Keyword list of options:
    • :group_id - Group ID (for group's plans - primary use case)
    • :user_id - User ID or UPN (for user's accessible plans)
    • :auto_paginate - Boolean, default true (fetch all pages)

Note: Either :group_id or :user_id is required.

Returns

  • {:ok, [plan]} - List of plans (when auto_paginate: true)
  • {:ok, %{items: [plan], next_link: url}} - First page with next link (when auto_paginate: false)
  • {:error, term} - Error

Examples

# List plans for a group
{:ok, plans} = Msg.Planner.Plans.list(client, group_id: "group-id")

# List plans accessible by user
{:ok, plans} = Msg.Planner.Plans.list(client, user_id: "user@contoso.com")

update(client, plan_id, updates, opts)

@spec update(Req.Request.t(), String.t(), map(), keyword()) ::
  {:ok, map()} | {:error, term()}

Updates a Planner Plan.

Important: Requires the current etag for concurrency control. If the etag doesn't match the current version, the update will fail with a 412 Precondition Failed error.

Parameters

  • client - Authenticated Req.Request client
  • plan_id - ID of plan to update
  • updates - Map of fields to update (typically just :title)
  • opts - Keyword list of options:
    • :etag (required) - Current etag from the plan

Returns

  • {:ok, plan} - Updated plan with new @odata.etag
  • {:error, {:etag_mismatch, current_etag}} - Etag conflict (412)
  • {:error, :not_found} - Plan doesn't exist
  • {:error, term} - Other errors

Examples

# Get current plan first to obtain etag
{:ok, plan} = Msg.Planner.Plans.get(client, plan_id)

{:ok, updated} = Msg.Planner.Plans.update(client, plan_id,
  %{title: "Updated Project Name"},
  etag: plan["@odata.etag"]
)