Generated banner for Flowstudio Power Automate Build
Generated logo for Flowstudio Power Automate Build

Flowstudio Power Automate Build

GitHubOfficialFirst committed by ninihen1Distributed by GitHub

  • Skill
  • Workflow
  • Automation and integrations
  • MIT
  • No issues found

From the author

Build, scaffold, and deploy Power Automate cloud flows using the FlowStudio MCP server. Your agent constructs flow definitions, wires connections, deploys, and tests — all via MCP without opening the portal. Load this skill when asked to: create a flow, build a new flow, deploy a flow definition, scaffold a Power Automate workflow, construct a flow JSON, update an existing flow's actions, patch a flow definition, add actions to a flow, wire up connections, or generate a workflow definition from scratch. Requires a FlowStudio MCP subscription — see https://mcp.flowstudio.app
— the description by ninihen1

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured workflow for constructing and managing Power Automate cloud flows entirely through the FlowStudio MCP server. It enables developers to scaffold flow definitions, manage connection references, and perform deployments and testing without needing to access the Power Automate web portal. The skill emphasizes programmatic interaction, ensuring that flow definitions are built, validated, and deployed using consistent patterns.

What it does

  • Scaffolds new Power Automate flow definitions from scratch.
  • Manages and wires connection references for various connectors.
  • Updates and patches existing flow definitions.
  • Automates the deployment of flows to specific environments.
  • Verifies flow state and performs testing via run resubmission or custom triggers.

When to use it

  • When you need to automate the creation or modification of Power Automate workflows.
  • When you want to manage flow deployments programmatically via an MCP server.
  • When you need to update existing flows without using the web-based designer.
  • When you require a repeatable process for testing and verifying flow logic.

Advantages

  • Enables infrastructure-as-code practices for Power Automate flows.
  • Reduces manual effort by eliminating the need to use the web portal.
  • Provides a consistent, programmatic approach to flow construction and deployment.
  • Supports automated testing and validation of flow logic.

Usage examples

“Create a new Power Automate flow that triggers on a SharePoint item creation and posts a message to Teams.”
The agent will list available connections, construct the flow definition with the appropriate SharePoint and Teams actions, and deploy it to the specified environment.
“Update the existing flow 'Overdue Invoice Notifications' to add a new logging step.”
The agent will retrieve the current flow definition, patch it with the new action, and redeploy the updated version.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/github/flowstudio-power-automate-build/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip flowstudio-power-automate-build.zip -d .agents/skills

Documentation by ninihen1

Build & Deploy Power Automate Flows with FlowStudio MCP

Step-by-step guide for constructing and deploying Power Automate cloud flows programmatically through the FlowStudio MCP server.

Prerequisite: A FlowStudio MCP server must be reachable with a valid JWT. See the flowstudio-power-automate-mcp skill for connection setup. Subscribe at https://mcp.flowstudio.app

Workflow:

  1. Load current build tools.

  2. Check for an existing flow.

  3. Resolve connection references.

  4. Build the definition.

  5. Deploy.

  6. Verify.

  7. Test.


Source of Truth

Always call list_skills / tool_search first to confirm available tool names and parameter schemas. Tool names and parameters may change between server versions. This skill covers response shapes, behavioral notes, and build patterns — things tool schemas cannot tell you. If this document disagrees with tool_search or a real API response, the API wins.


Python Helper

import json, urllib.request

MCP_URL   = "https://mcp.flowstudio.app/mcp"
MCP_TOKEN = "<YOUR_JWT_TOKEN>"

def mcp(tool, **kwargs):
    payload = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
                          "params": {"name": tool, "arguments": kwargs}}).encode()
    req = urllib.request.Request(MCP_URL, data=payload,
        headers={"x-api-key": MCP_TOKEN, "Content-Type": "application/json",
                 "User-Agent": "FlowStudio-MCP/1.0"})
    try:
        resp = urllib.request.urlopen(req, timeout=120)
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"MCP HTTP {e.code}: {body[:200]}") from e
    raw = json.loads(resp.read())
    if "error" in raw:
        raise RuntimeError(f"MCP error: {json.dumps(raw['error'])}")
    return json.loads(raw["result"]["content"][0]["text"])

ENV = "<environment-id>"  # e.g. Default-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

0. Load the Current Build Tools

For a brand-new flow, load the server's create-flow bundle. For editing an existing flow, load build-flow. This keeps the agent aligned with the MCP server's current schema before constructing JSON.

schemas = mcp("tool_search", query="skill:create-flow")
# Includes list_live_environments, list_live_connections,
# describe_live_connector, get_live_dynamic_options, update_live_flow.

If you need a tool outside the bundle, load it explicitly:

mcp("tool_search", query="select:get_live_dynamic_properties")

1. Safety Check: Does the Flow Already Exist?

Always look before you build to avoid duplicates:

results = mcp("list_live_flows",
    environmentName=ENV,
    mode="owner",
    search="My New Flow",
    top=20)

# list_live_flows returns { "flows": [...], "mode": "...", ... }
matches = [f for f in results["flows"]
           if "My New Flow".lower() in f["displayName"].lower()]

if len(matches) > 0:
    # Flow exists — modify rather than create
    FLOW_ID = matches[0]["id"]   # plain UUID from list_live_flows
    print(f"Existing flow: {FLOW_ID}")
    defn = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)
else:
    print("Flow not found — building from scratch")
    FLOW_ID = None

For very large environments, list_live_flows may return a continuation URL. Pass it back as continuationUrl with the same mode to retrieve the next batch. Use mode="admin" only when the user needs all environment flows and the MCP identity has admin rights.


2. Obtain Connection References

Every connector action needs a connectionName that points to a key in the flow's connectionReferences map. That key links to an authenticated connection in the environment.

MANDATORY: You MUST call list_live_connections first — do NOT ask the user for connection names or GUIDs. The API returns the exact values you need. Only prompt the user if the API confirms that required connections are missing.

2a — Find active connections
conns = mcp("list_live_connections", environmentName=ENV)
active = [c for c in conns["connections"]
          if c["statuses"][0]["status"] == "Connected"]
conn_map = {c["connectorName"]: c["id"] for c in active}

For a known connector, pass search to reduce output and get paste-ready connectionReferenceTemplate and hostTemplate values:

sp_conns = mcp("list_live_connections",
    environmentName=ENV,
    search="shared_sharepointonline")
2b — Determine which connectors the flow needs

Common connector API names: SharePoint shared_sharepointonline, Outlook shared_office365, Teams shared_teams, Approvals shared_approvals, OneDrive shared_onedriveforbusiness, Excel shared_excelonlinebusiness, Dataverse shared_commondataserviceforapps, Forms shared_microsoftforms.

Flows that need no connectors, such as Recurrence + Compose + HTTP only, can omit connectionReferences.

2c — If connections are missing, guide the user
connectors_needed = ["shared_sharepointonline", "shared_office365"]  # adjust per flow
missing = [c for c in connectors_needed if c not in conn_map]
if missing:
    # STOP: connections require browser OAuth consent.
    # Ask the user to create the missing connector connections in the
    # selected environment, then re-run list_live_connections.
    raise Exception(f"Missing active connections: {missing}")
2d — Build the connectionReferences block
connection_references = {}
host_templates = {}
for connector in connectors_needed:
    c = next(c for c in active if c["connectorName"] == connector)
    connection_references[connector] = c.get("connectionReferenceTemplate") or {
        "connectionName": c["id"],   # the connection id from list_live_connections
        "source": "Invoker",
        "id": f"/providers/Microsoft.PowerApps/apis/{connector}"
    }
    host_templates[connector] = c.get("hostTemplate") or {
        "connectionName": connector
    }

In Step 3 action JSON, inputs.host.connectionName must be the map key such as shared_teams, not the GUID. The GUID belongs only inside the connectionReferences[connector].connectionName value. If an existing flow uses the same connectors, you may also copy its properties.connectionReferences from get_live_flow.


3. Build the Flow Definition

Construct the definition object. See flow-schema.md for the full schema and these action pattern references for copy-paste templates:

definition = {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "contentVersion": "1.0.0.0",
    "triggers": { ... },   # see trigger-types.md / build-patterns.md
    "actions": { ... }     # see ACTION-PATTERNS-*.md / build-patterns.md
}

See build-patterns.md for complete, ready-to-use flow definitions covering Recurrence+SharePoint+Teams, HTTP triggers, and more.

Discover connector operations before guessing JSON

For connector-backed triggers/actions, prefer the live connector describer over hand-written shapes. It can return authored hints, canonical examples, variant keys, inputs/outputs, and dynamic metadata pointers.

# Search across connectors when you know the user's intent but not the API.
matches = mcp("describe_live_connector",
    environmentName=ENV,
    search="send email",
    top=5)

# Describe a specific operation before copying an exampleDefinition.
op = mcp("describe_live_connector",
    environmentName=ENV,
    connectorName="shared_office365",
    operationId="SendEmailV2")
print(op.get("hint"))

When an operation has multiple authored variants, request the variant the flow needs:

teams_chat = mcp("describe_live_connector",
    environmentName=ENV,
    connectorName="shared_teams",
    operationId="PostMessageToConversation",
    variant="flowbot_chat")

When the operation description says a parameter has dynamic options or dynamic properties, call the indicated next tool:

sp_op = mcp("describe_live_connector",
    environmentName=ENV,
    connectorName="shared_sharepointonline",
    operationId="GetItems")

sites = mcp("get_live_dynamic_options",
    environmentName=ENV,
    connectorName="shared_sharepointonline",
    connectionName=conn_map["shared_sharepointonline"],
    operationId="GetItems",
    parameterName="dataset",
    dynamicMetadata=sp_op["dynamicParameters"]["dataset"])

fields = mcp("get_live_dynamic_properties",
    environmentName=ENV,
    connectorName="shared_sharepointonline",
    connectionName=conn_map["shared_sharepointonline"],
    operationId="GetItems",
    parameterName="item",
    parameters={"dataset": "<site-url>", "table": "<list-id>"},
    dynamicMetadata=sp_op["dynamicProperties"]["item"])

Use dynamic options for dropdown IDs such as SharePoint sites/lists and Teams teams/channels. Use dynamic properties for schema/field shapes such as SharePoint list item columns.


4. Deploy (Create or Update)

update_live_flow handles both creation and updates in a single tool.

Create a new flow (no existing flow)

Omit flowName — the server generates a new GUID and creates via PUT:

definition["description"] = "Weekly SharePoint → Teams notification flow, built by agent"

result = mcp("update_live_flow",
    environmentName=ENV,
    # flowName omitted → creates a new flow
    definition=definition,
    connectionReferences=connection_references,
    displayName="Overdue Invoice Notifications"
)

if result.get("error") is not None:
    print("Create failed:", result["error"])
else:
    # Capture the new flow ID for subsequent steps
    FLOW_ID = result["created"]
    print(f"✅ Flow created: {FLOW_ID}")
Update an existing flow

Provide flowName to PATCH:

definition["description"] = (
    "Updated by agent on " + __import__('datetime').datetime.utcnow().isoformat()
)

result = mcp("update_live_flow",
    environmentName=ENV,
    flowName=FLOW_ID,
    definition=definition,
    connectionReferences=connection_references,
    displayName="My Updated Flow"
)

if result.get("error") is not None:
    print("Update failed:", result["error"])
else:
    print("Update succeeded:", result)

⚠️ update_live_flow always returns an error key. null (Python None) means success — do not treat the presence of the key as failure.

⚠️ Flow description lives at definition["description"]. The current server appends #flowstudio-mcp for usage tracking. Do not pass a top-level description argument unless tool_search shows one in the active schema.

Common deployment errors
Error message (contains)CauseFix
missing from connectionReferencesAn action's host.connectionName references a key that doesn't exist in the connectionReferences mapEnsure host.connectionName uses the key from connectionReferences (e.g. shared_teams), not the raw GUID
ConnectionAuthorizationFailed / 403The connection GUID belongs to another user or is not authorizedRe-run Step 2a and use a connection owned by the current x-api-key user
InvalidTemplate / InvalidDefinitionSyntax error in the definition JSONCheck runAfter chains, expression syntax, and action type spelling
ConnectionNotConfiguredA connector action exists but the connection GUID is invalid or expiredRe-check list_live_connections for a fresh GUID

5. Verify the Deployment

check = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)

# Confirm state
print("State:", check["properties"]["state"])  # Should be "Started"
# If state is "Stopped", use set_live_flow_state — NOT update_live_flow
# mcp("set_live_flow_state", environmentName=ENV, flowName=FLOW_ID, state="Started")

# Confirm the action we added is there
acts = check["properties"]["definition"]["actions"]
print("Actions:", list(acts.keys()))

6. Test the Flow

MANDATORY: Before triggering any test run, ask the user for confirmation. Running a flow has real side effects — it may send emails, post Teams messages, write to SharePoint, start approvals, or call external APIs. Explain what the flow will do and wait for explicit approval before calling trigger_live_flow or resubmit_live_flow_run.

Updated flows (have prior runs) — ANY trigger type

Use resubmit_live_flow_run first. It works for EVERY trigger type — Recurrence, SharePoint, connector webhooks, Button, and HTTP. It replays the original trigger payload. Do NOT ask the user to manually trigger the flow or wait for the next scheduled run.

runs = mcp("get_live_flow_runs", environmentName=ENV, flowName=FLOW_ID, top=1)
if runs:
    # Works for Recurrence, SharePoint, connector triggers — not just HTTP
    result = mcp("resubmit_live_flow_run",
        environmentName=ENV, flowName=FLOW_ID, runName=runs[0]["name"])
    print(result)   # {"resubmitted": true, "triggerName": "..."}
HTTP, Button, and PowerApps flows — custom test payload

Only use trigger_live_flow when you need to send a different payload than the original run. For verifying a fix, resubmit_live_flow_run is better because it uses the exact data that caused the failure.

defn = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)
triggers = defn["properties"]["definition"]["triggers"]
manual = next(iter(triggers.values()))
print("Expected body:", manual.get("inputs", {}).get("schema"))

result = mcp("trigger_live_flow",
    environmentName=ENV, flowName=FLOW_ID,
    body={"name": "Test", "value": 1})
print(f"Status: {result['responseStatus']}, via: {result['invocation']}")
print(result.get("warning"))   # set when a required input was missing: the run still ran, with null
Brand-new non-HTTP flows

A brand-new Recurrence flow needs no workaround: deploy it, then run it immediately with trigger_live_flow and no body — same as the portal's "Run flow" button. A body is refused; scheduled triggers take no inputs.

A brand-new connector-triggered flow (SharePoint, webhooks) has no prior runs and cannot fire without a real source event. Deploy with a temporary HTTP trigger, test the actions, then swap to the production trigger:

production_trigger = definition["triggers"]
definition["triggers"] = {
    "manual": {"type": "Request", "kind": "Http", "inputs": {"schema": {}}}
}
result = mcp("update_live_flow", environmentName=ENV,
    flowName=FLOW_ID,       # omit if creating new
    definition=definition, connectionReferences=connection_references,
    displayName="Overdue Invoice Notifications")
FLOW_ID = FLOW_ID or result["flowName"]

mcp("trigger_live_flow", environmentName=ENV, flowName=FLOW_ID,
    body={"sample": "payload"})
runs = mcp("get_live_flow_runs", environmentName=ENV, flowName=FLOW_ID, top=1)
if runs[0]["status"] == "Failed":
    err = mcp("get_live_flow_run_error",
        environmentName=ENV, flowName=FLOW_ID, runName=runs[0]["name"])
    raise Exception(err["failedActions"][-1])

definition["triggers"] = production_trigger
mcp("update_live_flow", environmentName=ENV, flowName=FLOW_ID,
    definition=definition, connectionReferences=connection_references)

The trigger is only the entry point; testing through HTTP still exercises the same actions. If actions use triggerBody() or triggerOutputs(), pass a representative body shaped like the production trigger payload.


Gotchas

MistakeConsequencePrevention
Missing connectionReferences in deploy400 "Supply connectionReferences"Always call list_live_connections first
"operationOptions" missing on ForeachParallel execution, race conditions on writesAlways add "Sequential"
union(old_data, new_data)Old values override new (first-wins)Use union(new_data, old_data)
split() on potentially-null stringInvalidTemplate crashWrap with coalesce(field, '')
Checking result["error"] existsAlways present; true error is != nullUse result.get("error") is not None
Flow deployed but state is "Stopped"Flow won't run on scheduleCall set_live_flow_state with state: "Started" — do not use update_live_flow for state changes
Teams "Chat with Flow bot" recipient as object400 GraphUserDetailNotFoundUse plain string with trailing semicolon (see below)
Copilot/Skills flow not in a solutionCopilot Studio may not discover it as an agent toolAfter deploy, call add_live_flow_to_solution with the target solutionId
Button/Skills trigger used for MCP testingRuns even when a required input is missing (null)Pass inputs in trigger_live_flow body; on warning, cancel and retry with the full body
Connector action missing metadata.operationMetadataIdDesigner/run-only UI can behave inconsistentlyPreserve existing IDs; add stable GUIDs for new connector actions
Placeholder Excel scriptIdDynamic validation fails at save timeResolve the real Office Script ID before deploying
SharePoint PatchItem omits required fieldsSave can fail even if the field is not changingEcho unchanged required fields such as item/Title
Copilot Studio connector calls a draft agentConnector invocation can fail or hit stale behaviorPublish the agent before testing/resubmitting the flow
Teams PostMessageToConversation — Recipient Formats

The body/recipient parameter format depends on the location value:

Locationbody/recipient formatExample
Chat with Flow botPlain email string with trailing semicolon"[email protected];"
ChannelObject with groupId and channelId{"groupId": "...", "channelId": "..."}

Common mistake: passing {"to": "[email protected]"} for "Chat with Flow bot" returns a 400 GraphUserDetailNotFound error. The API expects a plain string.


Reference Files

  • flowstudio-power-automate-mcp — Core connection setup and tool reference

  • flowstudio-power-automate-debug — Debug failing flows after deployment

Files

references/action-patterns-connectors.md

FlowStudio MCP — Action Patterns: Connectors

SharePoint, Outlook, Teams, and Approvals connector action patterns.

All examples assume "runAfter" is set appropriately. Replace <connectionName> with the key you used in connectionReferences (e.g. shared_sharepointonline, shared_teams). This is NOT the connection GUID — it is the logical reference name that links the action to its entry in the connectionReferences map.


SharePoint

SharePoint — Get Items
"Get_SP_Items": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItems"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "$filter": "Status eq 'Active'",
      "$top": 500
    }
  }
}

Result reference: @outputs('Get_SP_Items')?['body/value']

Dynamic OData filter with string interpolation: inject a runtime value directly into the $filter string using @{...} syntax:

"$filter": "Title eq '@{outputs('ConfirmationCode')}'"  

Note the single-quotes inside double-quotes — correct OData string literal syntax. Avoids a separate variable action.

Pagination for large lists: by default, GetItems stops at $top. To auto-paginate beyond that, enable the pagination policy on the action. In the flow definition this appears as:

"paginationPolicy": { "minimumItemCount": 10000 }

Set minimumItemCount to the maximum number of items you expect. The connector will keep fetching pages until that count is reached or the list is exhausted. Without this, flows silently return a capped result on lists with >5,000 items.


SharePoint — Get Item (Single Row by ID)
"Get_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "id": "@triggerBody()?['ID']"
    }
  }
}

Result reference: @body('Get_SP_Item')?['FieldName']

Use GetItem (not GetItems with a filter) when you already have the ID. Re-fetching after a trigger gives you the current row state, not the snapshot captured at trigger time — important if another process may have modified the item since the flow started.


SharePoint — Create Item
"Create_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "PostItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "item/Title": "@variables('myTitle')",
      "item/Status": "Active"
    }
  }
}

SharePoint — Update Item
"Update_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "PatchItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "id": "@item()?['ID']",
      "item/Status": "Processed"
    }
  }
}

PatchItem can validate required SharePoint columns even when you are not changing those fields. Echo unchanged required fields from the trigger or a prior Get Item action, for example item/Title, and use internal field names.


SharePoint — File Upsert (Create or Overwrite in Document Library)

SharePoint's CreateFile fails if the file already exists. To upsert (create or overwrite) without a prior existence check, use GetFileMetadataByPath on both Succeeded and Failed from CreateFile — if create failed because the file exists, the metadata call still returns its ID, which UpdateFile can then overwrite:

"Create_File": {
  "type": "OpenApiConnection",
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "CreateFile" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "folderPath": "/My Library/Subfolder",
      "name": "@{variables('filename')}",
      "body": "@outputs('Compose_File_Content')"
    }
  }
},
"Get_File_Metadata_By_Path": {
  "type": "OpenApiConnection",
  "runAfter": { "Create_File": ["Succeeded", "Failed"] },
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "GetFileMetadataByPath" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "path": "/My Library/Subfolder/@{variables('filename')}"
    }
  }
},
"Update_File": {
  "type": "OpenApiConnection",
  "runAfter": { "Get_File_Metadata_By_Path": ["Succeeded", "Skipped"] },
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "UpdateFile" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "id": "@outputs('Get_File_Metadata_By_Path')?['body/{Identifier}']",
      "body": "@outputs('Compose_File_Content')"
    }
  }
}

If Create_File succeeds, Get_File_Metadata_By_Path is Skipped and Update_File still fires (accepting Skipped), harmlessly overwriting the file just created. If Create_File fails (file exists), the metadata call retrieves the existing file's ID and Update_File overwrites it. Either way you end with the latest content.

Document library system properties — when iterating a file library result (e.g. from ListFolder or GetFilesV2), use curly-brace property names to access SharePoint's built-in file metadata. These are different from list field names:

@item()?['{Name}']                  — filename without path (e.g. "report.csv")
@item()?['{FilenameWithExtension}'] — same as {Name} in most connectors
@item()?['{Identifier}']            — internal file ID for use in UpdateFile/DeleteFile
@item()?['{FullPath}']              — full server-relative path
@item()?['{IsFolder}']             — boolean, true for folder entries

SharePoint — GetItemChanges Column Gate

When a SharePoint "item modified" trigger fires, it doesn't tell you WHICH column changed. Use GetItemChanges to get per-column change flags, then gate downstream logic on specific columns:

"Get_Changes": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItemChanges"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "<list-guid>",
      "id": "@triggerBody()?['ID']",
      "since": "@triggerBody()?['Modified']",
      "includeDrafts": false
    }
  }
}

Gate on a specific column:

"expression": {
  "and": [{
    "equals": [
      "@body('Get_Changes')?['Column']?['hasChanged']",
      true
    ]
  }]
}

New-item detection: On the very first modification (version 1.0), GetItemChanges may report no prior version. Check @equals(triggerBody()?['OData__UIVersionString'], '1.0') to detect newly created items and skip change-gate logic for those.


SharePoint — REST MERGE via HttpRequest

For cross-list updates or advanced operations not supported by the standard Update Item connector (e.g., updating a list in a different site), use the SharePoint REST API via the HttpRequest operation:

"Update_Cross_List_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "HttpRequest"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/target-site",
      "parameters/method": "POST",
      "parameters/uri": "/_api/web/lists(guid'<list-guid>')/items(@{variables('ItemId')})",
      "parameters/headers": {
        "Accept": "application/json;odata=nometadata",
        "Content-Type": "application/json;odata=nometadata",
        "X-HTTP-Method": "MERGE",
        "IF-MATCH": "*"
      },
      "parameters/body": "{ \"Title\": \"@{variables('NewTitle')}\", \"Status\": \"@{variables('NewStatus')}\" }"
    }
  }
}

Key headers:

  • X-HTTP-Method: MERGE — tells SharePoint to do a partial update (PATCH semantics)

  • IF-MATCH: * — overwrites regardless of current ETag (no conflict check)

The HttpRequest operation reuses the existing SharePoint connection — no extra authentication needed. Use this when the standard Update Item connector can't reach the target list (different site collection, or you need raw REST control). Keep the connector-specific parameter names exactly as shown: parameters/method, parameters/uri, parameters/headers, and parameters/body. The body is a JSON string, and parameters/uri is relative to the SharePoint dataset.


SharePoint — File as JSON Database (Read + Parse)

Use a SharePoint document library JSON file as a queryable "database" of last-known-state records. A separate process (e.g., Power BI dataflow) maintains the file; the flow downloads and filters it for before/after comparisons.

"Get_File": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetFileContent"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "id": "%252fShared%2bDocuments%252fdata.json",
      "inferContentType": false
    }
  }
},
"Parse_JSON_File": {
  "type": "Compose",
  "runAfter": { "Get_File": ["Succeeded"] },
  "inputs": "@json(decodeBase64(body('Get_File')?['$content']))"
},
"Find_Record": {
  "type": "Query",
  "runAfter": { "Parse_JSON_File": ["Succeeded"] },
  "inputs": {
    "from": "@outputs('Parse_JSON_File')",
    "where": "@equals(item()?['id'], variables('RecordId'))"
  }
}

Decode chain: GetFileContent returns base64-encoded content in body(...)?['$content']. Apply decodeBase64() then json() to get a usable array. Filter Array then acts as a WHERE clause.

When to use: When you need a lightweight "before" snapshot to detect field changes from a webhook payload (the "after" state). Simpler than maintaining a full SharePoint list mirror — works well for up to ~10K records.

File path encoding: In the id parameter, SharePoint URL-encodes paths twice. Spaces become %2b (plus sign), slashes become %252f.


Excel Online

Excel — Run Office Script

Office Script actions require real workbook and script identifiers at save time. Do not deploy placeholder scriptId values; update_live_flow can fail during dynamic operation validation even before a test run exists.

Use describe_live_connector or get_live_dynamic_options when available, or ask the user for the workbook and script if they are not discoverable. If a real scriptId still cannot be resolved, ask the user to add the Run script action once in the designer, then read the flow definition and preserve the resolved parameters.


Outlook

Outlook — Send Email
"Send_Email": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
      "connectionName": "<connectionName>",
      "operationId": "SendEmailV2"
    },
    "parameters": {
      "emailMessage/To": "[email protected]",
      "emailMessage/Subject": "Automated notification",
      "emailMessage/Body": "<p>@{outputs('Compose_Message')}</p>",
      "emailMessage/IsHtml": true
    }
  }
}

Outlook — Get Emails (Read Template from Folder)
"Get_Email_Template": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
      "connectionName": "<connectionName>",
      "operationId": "GetEmailsV3"
    },
    "parameters": {
      "folderPath": "Id::<outlook-folder-id>",
      "fetchOnlyUnread": false,
      "includeAttachments": false,
      "top": 1,
      "importance": "Any",
      "fetchOnlyWithAttachment": false,
      "subjectFilter": "My Email Template Subject"
    }
  }
}

Access subject and body:

@first(outputs('Get_Email_Template')?['body/value'])?['subject']
@first(outputs('Get_Email_Template')?['body/value'])?['body']

Outlook-as-CMS pattern: store a template email in a dedicated Outlook folder. Set fetchOnlyUnread: false so the template persists after first use. Non-technical users can update subject and body by editing that email — no flow changes required. Pass subject and body directly into SendEmailV2.

To get a folder ID: in Outlook on the web, right-click the folder → open in new tab — the folder GUID is in the URL. Prefix it with Id:: in folderPath.


Teams

Teams — Post Message
"Post_Teams_Message": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
      "connectionName": "<connectionName>",
      "operationId": "PostMessageToConversation"
    },
    "parameters": {
      "poster": "Flow bot",
      "location": "Channel",
      "body/recipient": {
        "groupId": "<team-id>",
        "channelId": "<channel-id>"
      },
      "body/messageBody": "@outputs('Compose_Message')"
    }
  }
}
Variant: Group Chat (1:1 or Multi-Person)

To post to a group chat instead of a channel, use "location": "Group chat" with a thread ID as the recipient:

"Post_To_Group_Chat": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
      "connectionName": "<connectionName>",
      "operationId": "PostMessageToConversation"
    },
    "parameters": {
      "poster": "Flow bot",
      "location": "Group chat",
      "body/recipient": "19:<thread-hash>@thread.v2",
      "body/messageBody": "@outputs('Compose_Message')"
    }
  }
}

For 1:1 ("Chat with Flow bot"), use "location": "Chat with Flow bot" and set body/recipient to the user's email address.

Active-user gate: When sending notifications in a loop, check the recipient's Azure AD account is enabled before posting — avoids failed deliveries to departed staff:

"Check_User_Active": {
  "type": "OpenApiConnection",
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365users",
              "operationId": "UserProfile_V2" },
    "parameters": { "id": "@{item()?['Email']}" }
  }
}

Then gate: @equals(body('Check_User_Active')?['accountEnabled'], true)


Copilot Studio

Copilot Studio — Invoke Agent

When using the Copilot Studio connector, publish the agent before running the flow. Draft/test agents can exist in the studio canvas but still be unavailable or stale through the flow connector endpoint.

If a connector action fails with an unavailable-agent or endpoint-style error, publish the agent, wait briefly for propagation, then resubmit the same flow run before changing the flow definition.


Approvals

Split Approval (Create → Wait)

The standard "Start and wait for an approval" is a single blocking action. For more control (e.g., posting the approval link in Teams, or adding a timeout scope), split it into two actions: CreateAnApproval (fire-and-forget) then WaitForAnApproval (webhook pause).

"Create_Approval": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_approvals",
      "connectionName": "<connectionName>",
      "operationId": "CreateAnApproval"
    },
    "parameters": {
      "approvalType": "CustomResponse/Result",
      "ApprovalCreationInput/title": "Review: @{variables('ItemTitle')}",
      "ApprovalCreationInput/assignedTo": "[email protected]",
      "ApprovalCreationInput/details": "Please review and select an option.",
      "ApprovalCreationInput/responseOptions": ["Approve", "Reject", "Defer"],
      "ApprovalCreationInput/enableNotifications": true,
      "ApprovalCreationInput/enableReassignment": true
    }
  }
},
"Wait_For_Approval": {
  "type": "OpenApiConnectionWebhook",
  "runAfter": { "Create_Approval": ["Succeeded"] },
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_approvals",
      "connectionName": "<connectionName>",
      "operationId": "WaitForAnApproval"
    },
    "parameters": {
      "approvalName": "@body('Create_Approval')?['name']"
    }
  }
}

approvalType options:

  • "Approve/Reject - First to respond" — binary, first responder wins

  • "Approve/Reject - Everyone must approve" — requires all assignees

  • "CustomResponse/Result" — define your own response buttons

After Wait_For_Approval, read the outcome:

@body('Wait_For_Approval')?['outcome']          → "Approve", "Reject", or custom
@body('Wait_For_Approval')?['responses'][0]?['responder']?['displayName']
@body('Wait_For_Approval')?['responses'][0]?['comments']

The split pattern lets you insert actions between create and wait — e.g., posting the approval link to Teams, starting a timeout scope, or logging the pending approval to a tracking list.

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

First committed by
GitHub avatar of ninihen1ninihen1
Publisher
GitHub (Official)
Distributed by
GitHub
Source
github/awesome-copilot · skills/flowstudio-power-automate-build · commit 797337f
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Contributors
ninihen1

AutoGineer is not affiliated with or endorsed by ninihen1 or GitHub.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download flowstudio-power-automate-build.zip