Flowstudio Power Automate Build
GitHubOfficialFirst committed by ninihen1Distributed by GitHub
- Skill
- Workflow
- Automation and integrations
- MIT
- No issues found
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
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/flowstudio-power-automate-build/downloadUnzip 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:
Load current build tools.
Check for an existing flow.
Resolve connection references.
Build the definition.
Deploy.
Verify.
Test.
Source of Truth
Always call
list_skills/tool_searchfirst 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 withtool_searchor 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-xxxxxxxxxxxx0. 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 = NoneFor 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_connectionsfirst — 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:
action-patterns-core.md — Variables, control flow, expressions
action-patterns-data.md — Array transforms, HTTP, parsing
action-patterns-connectors.md — SharePoint, Outlook, Teams, Approvals
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_flowalways returns anerrorkey.null(PythonNone) means success — do not treat the presence of the key as failure.⚠️ Flow description lives at
definition["description"]. The current server appends#flowstudio-mcpfor usage tracking. Do not pass a top-leveldescriptionargument unlesstool_searchshows one in the active schema.
Common deployment errors
| Error message (contains) | Cause | Fix |
|---|---|---|
missing from connectionReferences | An action's host.connectionName references a key that doesn't exist in the connectionReferences map | Ensure host.connectionName uses the key from connectionReferences (e.g. shared_teams), not the raw GUID |
ConnectionAuthorizationFailed / 403 | The connection GUID belongs to another user or is not authorized | Re-run Step 2a and use a connection owned by the current x-api-key user |
InvalidTemplate / InvalidDefinition | Syntax error in the definition JSON | Check runAfter chains, expression syntax, and action type spelling |
ConnectionNotConfigured | A connector action exists but the connection GUID is invalid or expired | Re-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_floworresubmit_live_flow_run.
Updated flows (have prior runs) — ANY trigger type
Use
resubmit_live_flow_runfirst. 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 nullBrand-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
| Mistake | Consequence | Prevention |
|---|---|---|
Missing connectionReferences in deploy | 400 "Supply connectionReferences" | Always call list_live_connections first |
"operationOptions" missing on Foreach | Parallel execution, race conditions on writes | Always add "Sequential" |
union(old_data, new_data) | Old values override new (first-wins) | Use union(new_data, old_data) |
split() on potentially-null string | InvalidTemplate crash | Wrap with coalesce(field, '') |
Checking result["error"] exists | Always present; true error is != null | Use result.get("error") is not None |
| Flow deployed but state is "Stopped" | Flow won't run on schedule | Call set_live_flow_state with state: "Started" — do not use update_live_flow for state changes |
| Teams "Chat with Flow bot" recipient as object | 400 GraphUserDetailNotFound | Use plain string with trailing semicolon (see below) |
| Copilot/Skills flow not in a solution | Copilot Studio may not discover it as an agent tool | After deploy, call add_live_flow_to_solution with the target solutionId |
| Button/Skills trigger used for MCP testing | Runs 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.operationMetadataId | Designer/run-only UI can behave inconsistently | Preserve existing IDs; add stable GUIDs for new connector actions |
Placeholder Excel scriptId | Dynamic validation fails at save time | Resolve the real Office Script ID before deploying |
SharePoint PatchItem omits required fields | Save can fail even if the field is not changing | Echo unchanged required fields such as item/Title |
| Copilot Studio connector calls a draft agent | Connector invocation can fail or hit stale behavior | Publish the agent before testing/resubmitting the flow |
Teams PostMessageToConversation — Recipient Formats
The body/recipient parameter format depends on the location value:
| Location | body/recipient format | Example |
|---|---|---|
| Chat with Flow bot | Plain email string with trailing semicolon | "[email protected];" |
| Channel | Object with groupId and channelId | {"groupId": "...", "channelId": "..."} |
Common mistake: passing
{"to": "[email protected]"}for "Chat with Flow bot" returns a 400GraphUserDetailNotFounderror. The API expects a plain string.
Reference Files
flow-schema.md — Full flow definition JSON schema
trigger-types.md — Trigger type templates
action-patterns-core.md — Variables, control flow, expressions
action-patterns-data.md — Array transforms, HTTP, parsing
action-patterns-connectors.md — SharePoint, Outlook, Teams, Approvals
build-patterns.md — Complete flow definition templates (Recurrence+SP+Teams, HTTP trigger)
Related Skills
flowstudio-power-automate-mcp— Core connection setup and tool referenceflowstudio-power-automate-debug— Debug failing flows after deployment
Files
- ATTRIBUTION.mdAdded by AutoGineer895 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md19.5 KiB
- references/action-patterns-connectors.md18.0 KiB
- references/action-patterns-core.md16.2 KiB
- references/action-patterns-data.md18.6 KiB
- references/build-patterns.md2.7 KiB
- references/flow-schema.md5.1 KiB
- references/trigger-types.md5.3 KiB
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 inconnectionReferences(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 theconnectionReferencesmap.
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
$filterstring 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
minimumItemCountto 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(notGetItemswith 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"
}
}
}
PatchItemcan 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 exampleitem/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_Filesucceeds,Get_File_Metadata_By_PathisSkippedandUpdate_Filestill fires (acceptingSkipped), harmlessly overwriting the file just created. IfCreate_Filefails (file exists), the metadata call retrieves the existing file's ID andUpdate_Fileoverwrites it. Either way you end with the latest content.Document library system properties — when iterating a file library result (e.g. from
ListFolderorGetFilesV2), 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),
GetItemChangesmay 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
HttpRequestoperation 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, andparameters/body. The body is a JSON string, andparameters/uriis relative to the SharePointdataset.
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:
GetFileContentreturns base64-encoded content inbody(...)?['$content']. ApplydecodeBase64()thenjson()to get a usable array.Filter Arraythen 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
idparameter, 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: falseso 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 intoSendEmailV2.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::infolderPath.
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']"
}
}
}
approvalTypeoptions:
"Approve/Reject - First to respond"— binary, first responder wins
"Approve/Reject - Everyone must approve"— requires all assignees
"CustomResponse/Result"— define your own response buttonsAfter
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
ninihen1
- 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