
Integrate Backend
- 110 installs
- 572 repo stars
- Updated July 28, 2026
- microsoft/power-platform-skills
>-.
About
>-. > **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. The integrate-backend skill documents workflows, constraints, and examples from SKILL.md for agent-assisted execution.
- > **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user
- **Understand the problem first**: Never jump to a technology choice. Analyze the user's intent, data flow, security need
- **Combinations are normal**: Many real scenarios need more than one approach. Recommend combinations when justified, but
- **Route, don't implement**: This skill recommends and invokes the right skill(s). It does not create backend files itsel
- **Initial request:** $ARGUMENTS
Integrate Backend by the numbers
- 110 all-time installs (skills.sh)
- Ranked #979 of 2,209 Security skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
integrate-backend capabilities & compatibility
- Capabilities
- > **plugin check**: run `node "${plugin_root}/sc · **understand the problem first**: never jump to · **combinations are normal**: many real scenarios · **route, don't implement**: this skill recommend
- Use cases
- documentation
What integrate-backend says it does
>-
npx skills add https://github.com/microsoft/power-platform-skills --skill integrate-backendAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 110 |
|---|---|
| repo stars | ★ 572 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | microsoft/power-platform-skills ↗ |
How do I apply integrate-backend using the workflow in its SKILL.md?
>-
Who is it for?
Developers following the integrate-backend skill for the tasks it documents.
Skip if: Tasks outside the integrate-backend scope described in SKILL.md.
When should I use this skill?
User mentions integrate-backend or related triggers from the skill description.
What you get
Working integrate-backend setup aligned with the documented patterns and constraints.
Files
Plugin check: Run node "${PLUGIN_ROOT}/scripts/check-version.js" — if it outputs a message, show it to the user before proceeding.Backend Integration
Analyze the user's business problem and recommend the right backend integration approach — Web API, AI Web API, Server Logic, Cloud Flows, or a combination — then route to the appropriate skill(s) to implement the solution.
Core Principles
- Understand the problem first: Never jump to a technology choice. Analyze the user's intent, data flow, security needs, and performance requirements before recommending.
- Recommend the simplest approach that works: Web API for straightforward Dataverse CRUD, AI Web API for generative summaries or grounded search over existing data, Server Logic when server-side processing is needed, Cloud Flows for async background work. Don't over-engineer.
- Secure actions belong on the server: When a write depends on a business rule that must be tamper-proof (state transitions, approval workflows, computed values), the server logic must validate AND execute the write — not just validate and leave the write to a client-side Web API call. See the Secure Action Principle in the decision framework.
- AI Web API sits on top of Web API: The Data Summarization and Case-preset endpoints read through the same
_apilayer as regular Web API, so they inherit the same table permissions, column permissions, andWebapi/<table>/*site settings. When a plan has both a Web API item and an AI Web API item for the same table, the AI item depends on (and goes in a later phase than) the Web API item. Search Summary has no per-table prereqs and can stand alone. - Combinations are normal: Many real scenarios need more than one approach. Recommend combinations when justified, but explain why each piece is needed.
- Route, don't implement: This skill recommends and invokes the right skill(s). It does not create backend files itself.
Initial request: $ARGUMENTS
---
Workflow
1. Verify Site Exists — Locate the Power Pages project and check prerequisites 2. Understand the Business Problem — Analyze what the user needs and why 3. Recommend Integration Approach — Present the recommendation with reasoning 4. Route to Skill(s) — Invoke the appropriate backend skill(s) to implement
---
Phase 1: Verify Site Exists
Goal: Locate the Power Pages project root and confirm prerequisites
Actions:
1. Create todo list with all 4 phases (see Progress Tracking table)
1.1 Locate Project
Look for powerpages.config.json in the current directory or immediate subdirectories.
If not found: Tell the user to create a site first with /create-site.
1.2 Explore Current State
Use the Explore agent to quickly scan the site for existing backend integrations:
"Analyze this Power Pages code site for existing backend integrations:
1. Check .powerpages-site/server-logic/ — list any existing server logic endpoints2. Check .powerpages-site/cloud-flow-consumer/ — list any registered cloud flows3. Search frontend code (src/**/*.{ts,tsx,js,jsx,vue,astro}) for calls to/_api/(Web API) and/_api/serverlogics/(Server Logic) and/_api/cloudflow/(Cloud Flows)
4. Check for existing service layers or API utilities insrc/services/,src/shared/, or similar
5. List available web roles from .powerpages-site/web-roles/*.webrole.ymlReport what backend integrations already exist so we can build on them."
1.3 Discover Dataverse Custom Actions
Check whether the user's Dataverse environment has existing custom actions that could be leveraged in the integration:
node "${PLUGIN_ROOT}/scripts/list-custom-actions.js" "<ENV_URL>"The script returns Custom APIs (modern) and Custom Process Actions (legacy) with their names, descriptions, binding types, and parameters. If custom actions are found, note them — they will be factored into the recommendation in Phase 3.
Output: Project root confirmed, existing backend integrations identified, Dataverse custom actions discovered
---
Phase 2: Understand the Business Problem
Goal: Analyze the user's request to understand the underlying business problem, not just the technical ask
Actions:
2.1 Analyze the Request
From the user's request and the existing site state, determine:
- What is the user trying to accomplish? (business outcome, not technology)
- What data is involved? (Dataverse tables, external systems, user input)
- Who triggers the operation? (user action, form submit, page load, scheduled)
- Does the user need an immediate response? (real-time UI update vs. background processing)
- Are external services involved? (payment gateways, email, Graph, SharePoint, third-party APIs)
- Are credentials or secrets involved? (API keys, client secrets, tokens)
- Must logic be hidden from the browser? (pricing rules, validation algorithms, business rules)
- Is this a simple data operation or complex business logic? (CRUD vs. multi-step processing)
- Does any write depend on a business rule that must be tamper-proof? (state transitions, approval conditions, computed values) — if yes, the server logic must validate AND execute the write, not just validate
- Does the UI want an AI-generated summary, grounded AI search, or related-record discovery? (e.g., "summarize this case", "summarize open orders", "suggest KB articles on the case page", "AI-powered search") — if yes, AI Web API is the right fit. Watch for the phrasing signals: summarize, summary of, Copilot, related / similar / suggested <entity>, AI search, semantic search.
- Can existing Dataverse custom actions handle part of the requirement? If custom actions were discovered in Phase 1.3, check whether any align with the user's needs — server logic can wrap existing custom actions via
InvokeCustomApiinstead of building equivalent logic from scratch
2.2 Clarify if Ambiguous
<!-- not-a-gate: ambiguous-intent clarification — sub-prompts under the upcoming Phase 3.4 plan gate; multi-question data-gathering that shapes the recommendation -->
If the request could map to multiple approaches and the right choice isn't clear, use AskUserQuestion to clarify:
| Question | When to ask |
|---|---|
| Does the user need to see the result immediately, or can it happen in the background? | When the request involves processing that could be sync or async |
| Are external APIs or services involved (e.g., Stripe, SendGrid, SharePoint)? | When the request mentions "integration" without specifics |
| Does this involve sensitive credentials that shouldn't be in the browser? | When external service integration is mentioned |
| Is this a one-time action or a multi-step workflow? | When the request could be a simple call or an orchestration |
Output: Clear understanding of the business problem and technical requirements
---
Phase 3: Recommend Integration Approach
Goal: Present a recommendation with clear reasoning
Actions:
3.1 Apply the Decision Framework
Reference: ${PLUGIN_ROOT}/skills/integrate-backend/references/decision-framework.mdUse the decision matrix, intent mapping, and Secure Action Principle from the reference to determine the right approach. Consider:
1. Can Web API alone handle this? If it's straightforward Dataverse CRUD with no external calls, no secrets, no server-side logic, and no business rules governing the write — recommend Web API. It's the simplest option.
2. Does it need AI Web API? If any of these apply, AI Web API is the right fit:
- The UI wants an AI-generated summary of a record on its detail page (e.g., "summarize this case", "Copilot summary")
- The UI wants an AI summary of a list or collection (e.g., "summarize open orders", "highlight trends in this week's cases")
- A detail page wants related-record discovery (e.g., "suggest KB articles for this case", "similar products", "similar cases") — Search Summary's grounded retrieval is a better fit than a hand-rolled OData keyword match
- The site wants AI-grounded search with citations, replacing or augmenting keyword-only search
AI Web API is read-only — the Secure Action Principle does not apply. If an AI item covers a Dataverse table that is also covered by a Web API item, put the AI item in a later phase (it depends on the Web API Layer 1/2 prereqs being in place). Search Summary items have no per-table prereqs and can stand alone.
3. Does it need Server Logic? If any of these apply, Server Logic is needed:
- External API calls (HttpClient)
- Credentials/secrets must stay on the server
- Business logic must be hidden from the browser
- Multiple Dataverse queries should be batched into one endpoint
- Server-side validation that can't be bypassed
- Wrapping a Dataverse Custom API/Action for portal consumption — if custom actions were found in Phase 1.3, check whether any match the requirement before recommending building from scratch
- The write depends on a business rule that must be tamper-proof (state transitions, approval conditions, computed values) — server logic must validate AND execute the write
4. Does it need Cloud Flows? If any of these apply, Cloud Flows are the right fit:
- The operation is async — the user doesn't need an immediate result
- Background processing: sending emails, notifications, processing orders
- Multi-step workflows across systems with Power Automate connectors
- Long-running processes that exceed the 120-second server logic timeout
- Non-developers should be able to modify the workflow
5. Does it need a combination? Common combinations:
- Web API + AI Web API: UI displays raw Dataverse records and an AI summary of the same data (most common AI pattern — dashboards, case/order detail pages)
- Web API + Cloud Flow: UI reads/writes non-sensitive Dataverse fields, some actions trigger background flows
- Server Logic + Cloud Flow: Real-time endpoint validates and executes the action, async flow does follow-up (e.g., server logic transitions status, Cloud Flow sends notification)
- Web API + Server Logic: Web API for safe direct reads/writes, server logic for operations that need business rule enforcement (server logic validates AND writes for those operations)
- Web API + AI Web API + Cloud Flow: support portal with browsable cases, Copilot summary + KB discovery, and async notifications
3.1.1 Security Review — Apply the Secure Action Principle
Before finalizing the plan, review every item assigned to Web API and ask: "If a user skipped any preceding server logic validation and called this Web API endpoint directly, could they violate a business rule?"
If the answer is yes, that write does not belong in a Web API item. Move the write into the server logic item that validates it. The server logic should validate AND execute the write using Server.Connector.Dataverse.
Common patterns that must use validate-and-execute server logic (not Web API):
| Pattern | Why it must be server-side |
|---|---|
| Status/state transitions (Draft → Submitted → Approved) | Client could jump to any status by sending a direct PATCH |
| Conditional writes (only allowed before a deadline, only for certain roles) | Client could write after deadline or from wrong role context |
| Computed field writes (server calculates a score, price, or derived value) | Client could submit any value if it writes the field directly |
| Multi-table atomic operations (award bid + reject others + update event) | Partial execution from client could leave data inconsistent |
| Writes that depend on the current state of other records | Client's stale view of data could lead to invalid writes |
Correct plan structure for state transitions:
Phase 1: Server Logic — "transition-order" endpoint
- POST: accepts { entityId, targetStatus }
- Reads current record, validates transition is allowed, writes new status
- Returns { success, previousStatus, newStatus }
Phase 2: Web API — Order table CRUD
- Read: list/filter orders (safe for Web API)
- Create: new orders in Draft status (safe — initial state, no rule to enforce)
- Update: description, notes, dates (safe — no business rules on these fields)
- NOTE: Status changes are NOT here — they go through the server logic endpointIncorrect plan structure (anti-pattern):
❌ Phase 1: Server Logic — "validate-transition" endpoint
- POST: accepts { entityId, targetStatus }
- Reads current record, validates transition
- Returns { valid: true/false } ← only validates, doesn't write
❌ Phase 2: Web API — Order table CRUD
- Update: includes status field ← client writes status after "validation"
- PROBLEM: client can skip Phase 1 and write any status directly3.2 Render the HTML Plan
Build the plan data and render an HTML plan before asking for approval. The plan visualizes:
- Key Concepts — Educational overview of Web API, Server Logic, and Cloud Flows (hardcoded in template)
- Overview — Stats per approach, approach chips, design rationale
- Data Flow — Visual flow diagrams showing how data moves for each user action, with steps color-coded by approach
- Implementation Order — Phase-grouped items with dependencies, complexity badges, and implementation commands
- Integration Items — Each item with its approach, reasoning, and implementation details
Prepare a JSON object with these keys:
| Key | Description |
|---|---|
SITE_NAME | Site name from powerpages.config.json |
PLAN_TITLE | Short title (e.g., "Backend Integration Plan") |
SUMMARY | 1-3 sentence summary of the integration strategy |
ITEMS_DATA | Array of integration items (see format below) |
DATA_FLOWS_DATA | Array of data flow diagrams (see format below) |
RATIONALE_DATA | Array of design rationale entries (icon, title, desc) |
ITEMS_DATA format:
{
"name": "Create PayPal Order",
"approach": "webapi|aiwebapi|serverlogic|cloudflow",
"description": "What this item does",
"reasoning": "Why this approach was chosen",
"phase": 1,
"status": "new|existing|extends",
"complexity": "low|medium|high",
"depends": "Name of item this depends on (if any)",
"details": [
{ "label": "Endpoint", "value": "/_api/serverlogics/create-paypal-order" },
{ "label": "Secrets", "value": "PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET" }
],
"docs": [
{ "label": "Server Logic Overview", "url": "https://learn.microsoft.com/..." }
]
}Phase assignment rules — assign a phase number to each item based on dependencies:
1. Items with no dependencies go in the earliest phase appropriate for their approach 2. Items that depend on other items go in a later phase than their dependency 3. Items in the same phase have no dependencies on each other and can be built in parallel 4. Recommended default ordering: Server Logic foundations first (validate-and-execute endpoints for state transitions, batch queries), then Web API CRUD for non-sensitive fields (reads, creates with safe defaults, updates to fields with no business rules), then advanced Server Logic (multi-table transactions), then AI Web API for summaries/grounded search over the tables set up by Web API, then Cloud Flows (async follow-ups) 5. Security constraint: A Web API item must never write a field whose value is governed by a business rule enforced in a server logic item. If a field needs validation, the server logic item should write it directly — the Web API item should exclude that field from its scope 6. AI layering constraint: An AI Web API item for a Dataverse table that is also covered by a Web API item must be in a later phase than the Web API item — AI Web API's Data Summarization endpoint reuses the Web API's Layer 1/2 prereqs (table permissions, Webapi/<table>/* site settings). Search Summary items have no per-table prereqs and can go in any phase.
DATA_FLOWS_DATA format (each step's approach is one of webapi, aiwebapi, serverlogic, cloudflow):
{
"trigger": "User registers and pays",
"description": "User fills the form, pays via PayPal, receives confirmation",
"steps": [
{ "approach": "serverlogic", "name": "Validate Seats", "detail": "Check availability" },
{ "approach": "serverlogic", "name": "Create Order", "detail": "Server calls PayPal" },
{ "approach": "cloudflow", "name": "Send Email", "detail": "Async confirmation" }
]
}Important: In data flow diagrams, when a server logic step validates a business rule, the next step should NOT be a Web API write for the same field. The server logic step should validate AND execute. For example:
// ✅ Correct — server logic validates and writes status
{ "approach": "serverlogic", "name": "Submit Order", "detail": "Validates Draft→Submitted, writes new status" }
// ❌ Incorrect — split across server logic validation and Web API write
{ "approach": "serverlogic", "name": "Validate Transition", "detail": "Checks Draft→Submitted" },
{ "approach": "webapi", "name": "Update Status", "detail": "PATCH status to Submitted" }Write the plan to <PROJECT_ROOT>/docs/backend-plan.html (create docs/ if needed). Use the render script:
node "${PLUGIN_ROOT}/scripts/render-backend-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"The render script refuses to overwrite existing files. If the default path exists, choose a new descriptive filename (e.g., backend-plan-payments.html).
After rendering, open the HTML plan in the user's default browser:
open "<OUTPUT_PATH>" # macOS
# start "<OUTPUT_PATH>" # Windows
# xdg-open "<OUTPUT_PATH>" # Linux3.3 Present Plan Summary
Do not restate the full plan in the CLI. The HTML file is the single detailed plan artifact.
In the CLI, give only a brief summary:
- Total items and how many per approach
- Whether the plan uses one approach or a combination
- The actual output path
- A note that the browser-opened HTML contains the full details including data flow diagrams
3.4 Confirm with User
<!-- gate: integrate-backend:3.4.plan-approval | category=plan | cancel-leaves=nothing -->
🚦 Gate (plan · integrate-backend:3.4.plan-approval): Approve the integration plan before invoking the appropriate child skill (integrate-webapi/add-server-logic/add-cloud-flow). The plan HTML stays on disk regardless of choice — Cancel just stops the dispatch.
>
Trigger: Phase 3.3 has rendered the HTML plan and surfaced a brief CLI summary.
Why we ask: Wrong child skill gets dispatched —add-server-logicfor a Web API task wastes minutes;add-cloud-flowfor a Web API task creates orphaned flow YAML.
Cancel leaves: Nothing — no child skill invoked, HTML plan stays at its saved path.
Use AskUserQuestion:
| Question | Options |
|---|---|
| Here's the integration plan. The HTML plan is open in your browser with data flow diagrams and per-item reasoning. Does this approach look right? | Yes, proceed (Recommended), Change approach, Cancel |
If "Change approach": Ask what they'd prefer and why, update the plan, and present again.
If "Cancel": Stop the workflow.
Output: User-approved integration approach
---
Phase 4: Route to Skill(s)
Goal: Invoke the appropriate skill(s) to implement the approved approach, respecting phase ordering and building in parallel within each phase
Actions:
4.1 Build the Phase Execution Plan
Group the approved items by their phase number from the plan. Each phase is a batch of independent items — items within a phase have no dependencies on each other and can be built in parallel.
| Approach | Skill to invoke | What to pass |
|---|---|---|
| Web API | /integrate-webapi | The user's request + tables for this phase + existing patterns |
| AI Web API | /add-ai-webapi | The user's request + target pages/tables for this phase + which of the two APIs to use (Search Summary or Data Summarization) + any trigger preferences (auto-on-mount vs manual button for list summaries). If Web API prereqs for the target table were set up in an earlier phase, mention that so the AI skill skips its Layer 1/2 delegation. |
| Server Logic | /add-server-logic | The user's request + endpoints for this phase + SDK features needed + secrets identified + any matching Dataverse custom actions from Phase 1.3 |
| Cloud Flow | /add-cloud-flow | The user's request + async operations for this phase |
4.2 Execute Phase by Phase
Process phases in order (Phase 1, then Phase 2, etc.). Complete all items in a phase before moving to the next — later phases depend on earlier phases.
Within each phase, maximize parallelism:
- Single approach in the phase: Invoke the skill once with all items for that phase. Tell the skill: "These N items are independent — implement them in parallel where possible."
- Multiple approaches in the same phase: Invoke each skill for its items. Since items in the same phase have no cross-dependencies, the order of skill invocation within a phase does not matter. When invoking the second skill, pass context from the first so it can follow the same frontend patterns (e.g., naming conventions, file organization).
Example — a plan with 4 phases:
| Phase | Items | Skill(s) | Parallelism |
|---|---|---|---|
| 1 | Validate Transition (serverlogic), Dashboard Metrics (serverlogic) | /add-server-logic | Both items passed together — skill builds them in parallel |
| 2 | Supplier Updates (webapi), Bid CRUD (webapi), PR Creation (webapi) | /integrate-webapi | All 3 items passed together — skill builds them in parallel |
| 3 | Award Bid (serverlogic) | /add-server-logic | Single item — sequential |
| 4 | Approval Notification (cloudflow), Expiry Alerts (cloudflow) | /add-cloud-flow | Both items passed together — skill builds them in parallel |
When invoking each skill, include: 1. Which items to implement — list the specific item names from the plan for this phase 2. Parallelism guidance — "These items are in the same phase and have no dependencies on each other. Implement them in parallel where possible." 3. Context from previous phases — what was created so far (files, patterns, services) so the skill can build on it
4.3 Summary
After all phases complete, present a brief summary of everything that was created:
| Phase | Approach | What was created |
|---|---|---|
| 1 | Server Logic | [endpoints created, SDK features used] |
| 2 | Web API | [files created, tables integrated] |
| 3 | Server Logic | [endpoints created] |
| 4 | Cloud Flow | [flows registered, triggers wired] |
Remind the user to deploy with /deploy-site if they haven't already.
4.4 Record Skill Usage
Reference: ${PLUGIN_ROOT}/references/skill-tracking-reference.mdFollow the skill tracking instructions in the reference to record this skill's usage. Use --skillName "IntegrateBackend".
Output: All recommended backend integrations implemented
---
Important Notes
When NOT to Use This Skill
If the user's request clearly and unambiguously maps to a single approach, skip this skill and go directly to the implementation skill:
- "Create a server logic endpoint for..." →
/add-server-logic - "Integrate Web API for the contacts table" →
/integrate-webapi - "Add a cloud flow for sending emails" →
/add-cloud-flow - "Add an AI summary to the case detail page" / "summarize open cases" / "suggest KB articles on this page" →
/add-ai-webapi
This skill is for ambiguous requests where the user describes a business problem and needs help choosing the right approach.
Examples: What Routing Looks Like
Example 1: Simple Dataverse CRUD → Web API
User: I need to show a list of products on the homepage and let users
filter by category.
Recommendation: Web API
Reason: This is straightforward Dataverse read operations with filtering.
No external APIs, no secrets, no server-side logic needed.
Skill: /integrate-webapiExample 2: External API with credentials → Server Logic
User: Add payment processing through Stripe. The API key must stay
on the server.
Recommendation: Server Logic
Reason: Calls an external API (Stripe) with credentials that must be
protected. Server logic hides the code and credentials from
the browser.
Skill: /add-server-logicExample 3: Background email → Cloud Flow
User: When a user submits the contact form, send them a confirmation
email and notify the support team on Teams.
Recommendation: Cloud Flow
Reason: Email and Teams notifications are async — the user doesn't
need to wait for them. Cloud Flows have built-in connectors
for Outlook and Teams.
Skill: /add-cloud-flowExample 4: Server-side validation → Server Logic (validate-and-execute)
User: Add validation that rejects orders when quantity exceeds
inventory. Check the actual Dataverse data, not just the form.
Recommendation: Server Logic (validate-and-execute)
Reason: Server-side validation that can't be bypassed from the browser.
The server logic checks inventory AND creates/updates the order
in a single call — the client doesn't write the order via Web API
because quantity validation would be bypassable.
Skill: /add-server-logicExample 5: Dashboard performance → Server Logic
User: The dashboard makes 3 separate API calls to load contacts,
orders, and products. It's slow.
Recommendation: Server Logic
Reason: Batching multiple Dataverse queries into a single server
endpoint reduces round-trips and improves load time.
Skill: /add-server-logicExample 6: CRUD + background processing → Web API + Cloud Flow
User: Let users submit support tickets from the portal. After
submission, assign it to the right team and send an email.
Recommendation: Web API + Cloud Flow
Reason: The ticket creation is a Dataverse write (Web API). The
assignment and email happen in the background after the
user submits (Cloud Flow).
Phases: Phase 1 → /integrate-webapi (ticket CRUD)
Phase 2 → /add-cloud-flow (assignment + email, depends on ticket creation)Example 7: Validate + process + notify → Server Logic + Cloud Flow
User: When a user places an order, validate inventory, process the
payment through Stripe, and send a confirmation email.
Recommendation: Server Logic + Cloud Flow
Reason: Inventory validation and Stripe payment need real-time
server-side processing with credentials (Server Logic).
The confirmation email is async (Cloud Flow).
Phases: Phase 1 → /add-server-logic (validate inventory + process payment — parallel)
Phase 2 → /add-cloud-flow (confirmation email, depends on payment)Example 8: State transitions + CRUD → Server Logic (validate-and-execute) + Web API
User: Build a procurement workflow with status transitions
(Draft → Submitted → Approved) and let users edit request details.
Recommendation: Server Logic + Web API
Reason: Status transitions must be tamper-proof — server logic validates
the transition AND writes the new status to Dataverse in one call
(the Secure Action Principle). Editing non-sensitive fields like
description or notes is safe via Web API since no business rule
governs those writes.
Phases: Phase 1 → /add-server-logic (transition-request endpoint that
validates AND writes status changes)
Phase 2 → /integrate-webapi (read/list requests, edit description
and notes — but NOT status, which goes through server logic)
NOTE: The Web API item must NOT include the status field in its Update
operations. Status writes go exclusively through the server logic
endpoint.Example 9: AI summary on detail page → AI Web API (Data Summarization, support-case recipe)
User: Add an AI-generated summary card to the case detail page that
includes the comments customers have posted.
Recommendation: AI Web API — Data Summarization
Reason: Microsoft Learn documents a ready-made Data Summarization
configuration for this scenario on the standard incident table —
POST to /_api/summarization/data/v1.0/incidents(<id>) with
InstructionIdentifier "Summarization/prompt/case_summary" and
$expand=incident_adx_portalcomments($select=description). The
user can adopt this verbatim or tweak the columns / prompt for
their own case-handling UX.
Skill: /add-ai-webapiExample 10: Related KB articles on detail page → AI Web API (search summary)
User: On the case detail page, show relevant KB articles to help
agents find answers faster.
Recommendation: AI Web API (Search Summary for related-record discovery)
Reason: Search Summary's grounded retrieval returns AI-ranked citations
from the site's knowledge index — a much better fit than a
hand-rolled OData keyword match on the knowledge article table.
The citations rewrite into SPA routes for clean deep-linking.
Skill: /add-ai-webapiExample 11: List summary + browsable records → Web API + AI Web API
User: On the dashboard, show the list of open cases from Dataverse
and an AI summary highlighting the most common issue themes.
Recommendation: Web API + AI Web API
Reason: The list is direct Dataverse reads — Web API handles pagination,
filtering, and status badges. The summary is a list-form Data
Summarization call with $filter=statecode eq 0 using the tabular-
insight prompt pattern. Web API item goes in Phase 1 because AI
Web API reuses its Layer 1/2 prereqs (table permissions, Webapi
site settings for the incident table).
Phases: Phase 1 → /integrate-webapi (incident list CRUD for the dashboard)
Phase 2 → /add-ai-webapi (list summary for open cases, reuses
Phase 1's Layer 1/2 — the AI skill detects this and skips its
Web API delegation)Progress Tracking
Before starting Phase 1, create a task list with all phases using TaskCreate:
| Task subject | activeForm | Description |
|---|---|---|
| Verify site exists | Verifying site prerequisites | Locate project root, scan for existing backend integrations |
| Understand business problem | Analyzing requirements | Determine what the user needs, clarify ambiguities |
| Recommend integration approach | Evaluating approaches | Apply decision framework, present recommendation |
| Route to implementation skill(s) | Implementing backend integration | Invoke the approved skill(s) and summarize results |
Mark each task in_progress when starting and completed when done via TaskUpdate.
---
Begin with Phase 1: Verify Site Exists
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>__PLAN_TITLE__ - __SITE_NAME__</title>
<style>
:root{
--bg:#faf9f8; --surface:#ffffff; --surface2:#f3f2f1; --border:#e1dfdd; --border-light:#c8c6c4;
--text:#323130; --text-dim:#605e5c; --text-bright:#201f1e; --accent:#0078d4; --accent-bg:#0078d40a; --accent-border:#0078d425;
--pass:#107c10; --pass-bg:#107c100a; --pass-border:#107c1020; --high:#ca5010; --high-bg:#ca50100a; --high-border:#ca501020;
--purple:#8764b8; --purple-bg:#8764b80a; --purple-border:#8764b820;
--webapi:#0078d4; --aiwebapi:#00b7c3; --serverlogic:#5c2d91; --cloudflow:#ca5010;
--mono:'Cascadia Code','Consolas',monospace; --sans:'Segoe UI','Segoe UI Web (West European)',-apple-system,system-ui,sans-serif;
--radius:8px; --radius-sm:4px; --shadow-4:0 1.6px 3.6px 0 rgba(0,0,0,0.132),0 0.3px 0.9px 0 rgba(0,0,0,0.108);
}
*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{font-family:var(--sans);background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;}
.topbar{z-index:100;background:var(--surface);box-shadow:var(--shadow-4);padding:14px 28px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;}
.topbar-left{display:flex;align-items:center;gap:14px;}
.logo{width:36px;height:36px;object-fit:contain;display:block;flex-shrink:0;}
.topbar-title{font-size:16px;font-weight:700;color:var(--text-bright);}
.topbar-sub{font-size:11px;color:var(--text-dim);margin-top:1px;}
.layout{display:flex;min-height:calc(100vh - 65px);}
.sidebar{width:240px;background:var(--surface);border-right:1px solid var(--border);padding:20px 0;flex-shrink:0;}
.nav-group-label{font-size:10px;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:1.2px;padding:16px 22px 6px;}
.nav-btn{display:flex;align-items:center;gap:10px;width:100%;padding:11px 22px;background:none;border:none;border-left:2px solid transparent;color:var(--text-dim);font-size:13px;font-weight:500;cursor:pointer;font-family:var(--sans);text-align:left;transition:all 0.15s;}
.nav-btn:hover{color:var(--text);background:var(--surface2);}
.nav-btn.active{color:var(--accent);font-weight:600;border-left-color:var(--accent);background:var(--accent-bg);}
.nav-btn .nav-icon{font-size:15px;opacity:0.5;width:18px;text-align:center;}
.nav-btn.active .nav-icon{opacity:0.9;}
.content{flex:1;padding:32px 40px 72px;max-width:1060px;}
.section{display:none;}
.section.active{display:block;animation:fadeIn 0.3s ease;}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:translateY(0);}}
h2{font-size:21px;font-weight:800;color:var(--text-bright);letter-spacing:-0.3px;margin-bottom:5px;}
.section-desc{font-size:13px;color:var(--text-dim);margin-bottom:20px;}
.section-desc code{color:var(--accent);background:var(--accent-bg);padding:1px 6px;border-radius:3px;font-family:var(--mono);font-size:12px;}
h3{font-size:15px;font-weight:700;color:var(--text-bright);margin-top:24px;margin-bottom:12px;}
.summary-box{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:22px;margin-bottom:22px;font-size:14px;color:var(--text);line-height:1.75;}
.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:24px;}
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 16px;text-align:center;position:relative;overflow:hidden;box-shadow:var(--shadow-4);}
.stat-card::after{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:40px;height:2px;border-radius:0 0 2px 2px;}
.stat-card:nth-child(1)::after{background:var(--webapi);}
.stat-card:nth-child(2)::after{background:var(--aiwebapi);}
.stat-card:nth-child(3)::after{background:var(--serverlogic);}
.stat-card:nth-child(4)::after{background:var(--cloudflow);}
.stat-num{font-size:30px;font-weight:800;font-family:var(--mono);line-height:1;}
.stat-label{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:6px;}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 20px;margin-bottom:10px;transition:box-shadow 0.2s,border-color 0.2s;}
.card:hover{box-shadow:var(--shadow-4);}
.principle{display:flex;gap:16px;padding:16px 0;border-bottom:1px solid var(--border);}
.principle:last-child{border-bottom:none;}
.principle-icon{font-size:22px;flex-shrink:0;margin-top:2px;}
.principle-title{font-size:13px;font-weight:700;color:var(--text-bright);margin-bottom:3px;}
.principle-desc{font-size:13px;color:var(--text-dim);line-height:1.65;}
.field-label{font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;}
.chip-row{display:flex;flex-wrap:wrap;gap:6px;}
.approach-chip{font-size:11px;padding:3px 10px;border-radius:4px;font-weight:700;display:inline-flex;align-items:center;gap:5px;}
.approach-chip .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
.approach-webapi{background:#0078d40a;border:1px solid #0078d425;color:var(--webapi);}
.approach-webapi .dot{background:var(--webapi);}
.approach-aiwebapi{background:#00b7c30a;border:1px solid #00b7c325;color:var(--aiwebapi);}
.approach-aiwebapi .dot{background:var(--aiwebapi);}
.approach-serverlogic{background:#5c2d910a;border:1px solid #5c2d9125;color:var(--serverlogic);}
.approach-serverlogic .dot{background:var(--serverlogic);}
.approach-cloudflow{background:#ca50100a;border:1px solid #ca501025;color:var(--cloudflow);}
.approach-cloudflow .dot{background:var(--cloudflow);}
.mono{font-family:var(--mono);}
.reason-box{font-size:12px;color:var(--text);background:var(--surface2);padding:10px 14px;border-radius:var(--radius-sm);line-height:1.8;}
.reason-webapi{border-left:3px solid var(--webapi);}
.reason-aiwebapi{border-left:3px solid var(--aiwebapi);}
.reason-serverlogic{border-left:3px solid var(--serverlogic);}
.reason-cloudflow{border-left:3px solid var(--cloudflow);}
.item-header{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:12px;}
.item-name{font-size:15px;font-weight:700;color:var(--text-bright);}
.item-desc{font-size:13px;color:var(--text-dim);margin-top:2px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px;}
.detail-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 14px;}
.detail-item .field-label{margin-bottom:6px;}
.flow-arrow{display:flex;align-items:center;justify-content:center;gap:12px;padding:16px 0;flex-wrap:wrap;}
.flow-step{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 18px;text-align:center;min-width:140px;box-shadow:var(--shadow-4);}
.flow-step-label{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;}
.flow-step-name{font-size:13px;font-weight:700;color:var(--text-bright);}
.flow-step-detail{font-size:11px;color:var(--text-dim);margin-top:2px;}
.flow-connector{font-size:18px;color:var(--border-light);flex-shrink:0;}
/* --- Concepts tab --- */
.concept-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:0;margin-bottom:18px;overflow:hidden;}
.concept-header{display:flex;align-items:center;gap:14px;padding:20px 22px 0;}
.concept-icon-box{width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}
.concept-icon-webapi{background:#0078d412;color:var(--webapi);}
.concept-icon-aiwebapi{background:#00b7c312;color:var(--aiwebapi);}
.concept-icon-serverlogic{background:#5c2d9112;color:var(--serverlogic);}
.concept-icon-cloudflow{background:#ca501012;color:var(--cloudflow);}
.concept-title{font-size:16px;font-weight:800;color:var(--text-bright);}
.concept-subtitle{font-size:12px;color:var(--text-dim);margin-top:1px;}
.concept-body{padding:16px 22px 20px;font-size:13px;color:var(--text);line-height:1.75;}
.concept-body p{margin-bottom:10px;}
.note-callout{margin:0 0 14px;padding:12px 14px;border-left:3px solid var(--high);background:var(--high-bg);border-radius:6px;font-size:12.5px;line-height:1.65;color:var(--text);}
.note-callout strong{color:var(--text-bright);font-weight:700;}
.note-callout p{margin:0;}
.note-callout p + p{margin-top:6px;}
.concept-when{background:var(--surface2);border-top:1px solid var(--border);padding:14px 22px;font-size:12px;line-height:1.7;}
.concept-when strong{color:var(--text-bright);}
.concept-when-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px 18px;margin-top:6px;}
.concept-when-item{display:flex;align-items:baseline;gap:6px;}
.concept-when-item::before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-top:4px;}
.concept-when-item.use::before{background:var(--pass);}
.concept-when-item.avoid::before{background:#d13438;}
.concept-link{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--accent);text-decoration:none;padding:3px 10px;border:1px solid var(--accent-border);border-radius:var(--radius-sm);margin-top:4px;margin-right:6px;transition:background 0.15s;}
.concept-link:hover{background:var(--accent-bg);}
.concept-diagram{display:flex;align-items:center;justify-content:center;gap:10px;padding:18px 22px;flex-wrap:wrap;}
.concept-diagram-box{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius);padding:10px 16px;text-align:center;min-width:100px;}
.concept-diagram-box .cd-label{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.5px;}
.concept-diagram-box .cd-value{font-size:13px;font-weight:700;color:var(--text-bright);margin-top:2px;}
.concept-diagram-arrow{font-size:16px;color:var(--border-light);}
/* --- Implementation order --- */
.phase-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;margin-bottom:16px;overflow:hidden;}
.phase-header{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none;}
.phase-header:hover{background:var(--surface2);}
.phase-num{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:800;color:#fff;flex-shrink:0;}
.phase-title{font-size:15px;font-weight:700;color:var(--text-bright);}
.phase-subtitle{font-size:12px;color:var(--text-dim);margin-top:1px;}
.phase-chevron{margin-left:auto;font-size:16px;color:var(--text-dim);transition:transform 0.2s;}
.phase-card.open .phase-chevron{transform:rotate(180deg);}
.phase-items{display:none;padding:0;}
.phase-card.open .phase-items{display:block;}
.phase-item{display:flex;align-items:flex-start;gap:14px;padding:14px 22px;border-bottom:1px solid var(--border);}
.phase-item:last-child{border-bottom:none;}
.phase-item-left{flex:1;}
.phase-item-name{font-size:13px;font-weight:700;color:var(--text-bright);}
.phase-item-desc{font-size:12px;color:var(--text-dim);margin-top:2px;}
.phase-item-right{display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex-shrink:0;}
/* --- Complexity badges --- */
.complexity-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:0.5px;}
.complexity-low{background:#107c100a;border:1px solid #107c1025;color:#107c10;}
.complexity-medium{background:#ca50100a;border:1px solid #ca501025;color:#ca5010;}
.complexity-high{background:#d134380a;border:1px solid #d1343825;color:#d13438;}
/* --- Status badges --- */
.status-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:0.5px;}
.status-new{background:#0078d40a;border:1px solid #0078d425;color:#0078d4;}
.status-existing{background:#107c100a;border:1px solid #107c1025;color:#107c10;}
.status-extends{background:#8764b80a;border:1px solid #8764b825;color:#8764b8;}
/* --- Implement action bar --- */
.implement-bar{display:flex;align-items:center;gap:10px;margin-top:14px;padding:12px 16px;background:linear-gradient(135deg,#0078d406,#5c2d9106);border:1px solid var(--accent-border);border-radius:var(--radius);cursor:default;}
.implement-bar.manual{background:linear-gradient(135deg,#ca50100a,#ca501006);border-color:#ca501025;}
.implement-label{font-size:11px;color:var(--text-dim);flex-shrink:0;}
.implement-cmd{font-family:var(--mono);font-size:13px;font-weight:700;color:var(--accent);background:var(--surface);padding:5px 14px;border-radius:var(--radius-sm);border:1px solid var(--accent-border);}
.implement-bar.manual .implement-cmd{color:var(--cloudflow);border-color:#ca501025;font-family:var(--sans);font-weight:600;}
.implement-hint{font-size:11px;color:var(--text-dim);margin-left:auto;}
/* --- Doc links row --- */
.doc-links{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;}
/* --- Dependency tag --- */
.dep-tag{font-size:11px;color:var(--text-dim);background:var(--surface2);padding:2px 8px;border-radius:3px;display:inline-flex;align-items:center;gap:4px;margin-top:6px;}
.dep-tag::before{content:'Depends on:';font-weight:600;color:var(--text-dim);}
@media(max-width:768px){
.sidebar{display:none;}
.content{padding:20px 16px 72px;}
.stats-grid,.detail-grid{grid-template-columns:1fr;}
.flow-arrow,.concept-diagram{flex-direction:column;}
.flow-connector,.concept-diagram-arrow{transform:rotate(90deg);}
.concept-when-grid{grid-template-columns:1fr;}
}
</style>
</head>
<body>
<div id="mainApp">
<div class="topbar">
<div class="topbar-left">
<img class="logo" src="./power-pages-icon.png" alt="Power Pages" />
<div>
<div class="topbar-title">__PLAN_TITLE__</div>
<div class="topbar-sub">__SITE_NAME__</div>
</div>
</div>
<div class="topbar-sub" id="topbarCount"></div>
</div>
<div class="layout">
<div class="sidebar">
<div class="nav-group-label">Learn</div>
<button class="nav-btn active" data-tab="concepts"><span class="nav-icon">◈</span> Key Concepts</button>
<div class="nav-group-label">Plan</div>
<button class="nav-btn" data-tab="overview"><span class="nav-icon">◉</span> Overview</button>
<button class="nav-btn" data-tab="dataflow"><span class="nav-icon">⇄</span> Data Flow</button>
<div class="nav-group-label">Build</div>
<button class="nav-btn" data-tab="order"><span class="nav-icon">▶</span> Implementation Order</button>
<button class="nav-btn" data-tab="items"><span class="nav-icon">▦</span> Integration Items</button>
</div>
<div class="content">
<!-- Concepts Tab -->
<div class="section active" id="tab-concepts">
<h2>Key Concepts</h2>
<p class="section-desc">Power Pages offers three ways to add backend logic. Understanding when to use each is the key to a well-architected site.</p>
<div id="conceptsContainer"></div>
</div>
<!-- Overview Tab -->
<div class="section" id="tab-overview">
<h2>Integration Plan</h2>
<p class="section-desc">Backend integration strategy for <code>__SITE_NAME__</code></p>
<div class="summary-box">__SUMMARY__</div>
<div class="stats-grid">
<div class="stat-card"><div class="stat-num" style="color:var(--webapi)" id="statWebApi">0</div><div class="stat-label">Web API</div></div>
<div class="stat-card"><div class="stat-num" style="color:var(--aiwebapi)" id="statAiWebApi">0</div><div class="stat-label">AI Web API</div></div>
<div class="stat-card"><div class="stat-num" style="color:var(--serverlogic)" id="statServerLogic">0</div><div class="stat-label">Server Logic</div></div>
<div class="stat-card"><div class="stat-num" style="color:var(--cloudflow)" id="statCloudFlow">0</div><div class="stat-label">Cloud Flows</div></div>
</div>
<div class="card">
<div class="field-label">Approaches Used</div>
<div class="chip-row" id="approachesSummary"></div>
</div>
<h3>Design Rationale</h3>
<div id="rationaleContainer"></div>
</div>
<!-- Data Flow Tab -->
<div class="section" id="tab-dataflow">
<h2>Data Flow</h2>
<p class="section-desc">How data moves through the integration — from user action to backend and back</p>
<div id="dataflowContainer"></div>
</div>
<!-- Implementation Order Tab -->
<div class="section" id="tab-order">
<h2>Implementation Order</h2>
<p class="section-desc">Phases are ordered by dependency — complete each phase before moving to the next. Items within a phase can be implemented in parallel.</p>
<div id="orderContainer"></div>
</div>
<!-- Items Tab -->
<div class="section" id="tab-items">
<h2>Integration Items</h2>
<p class="section-desc" id="itemsDesc"></p>
<div id="itemsContainer"></div>
</div>
</div>
</div>
</div>
<script>
const ITEMS = __ITEMS_DATA__;
const RATIONALE = __RATIONALE_DATA__;
const DATA_FLOWS = __DATA_FLOWS_DATA__;
function esc(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; }
const CONCEPTS = [
{
id: 'webapi',
icon: '⇄',
iconCls: 'concept-icon-webapi',
title: 'Web API',
subtitle: 'Browser-to-Dataverse CRUD',
body: '<p>The <strong>Power Pages Web API</strong> lets your frontend JavaScript talk directly to Dataverse \u2014 Microsoft\u2019s cloud database that stores your site\u2019s data. It supports standard operations: <strong>Create</strong>, <strong>Read</strong>, <strong>Update</strong>, and <strong>Delete</strong> (CRUD).</p><p>When a user fills out a form and clicks \u201cSave\u201d, your code sends an HTTP request (like <code>PATCH /api/data/v9.2/cr_table(...)</code>) that writes the data to Dataverse. <strong>Table Permissions</strong> control who can access which records.</p><p>Web API runs in the browser. This means the user\u2019s browser sends requests directly to Dataverse, and anyone with browser dev tools can see (and potentially tamper with) those requests. This is why business rules and validations should not rely on Web API alone.</p>',
diagram: [
{ label: 'Browser', value: 'User clicks Save' },
{ label: 'Web API', value: 'PATCH /api/data/...' },
{ label: 'Table Permissions', value: 'Checks access rules' },
{ label: 'Dataverse', value: 'Record updated' }
],
whenUse: ['Reading and displaying data', 'Simple form submissions', 'CRUD with no complex rules', 'User-facing create/update/delete'],
whenAvoid: ['Sensitive business rule validation', 'Multi-table atomic operations', 'Background or scheduled tasks', 'Operations the user shouldn\'t wait for'],
docs: [
{ label: 'Web API Overview', url: 'https://learn.microsoft.com/power-pages/configure/web-api-overview' },
{ label: 'Table Permissions', url: 'https://learn.microsoft.com/power-pages/security/table-permissions' }
]
},
{
id: 'aiwebapi',
icon: '✨',
iconCls: 'concept-icon-aiwebapi',
title: 'AI Web API (preview)',
subtitle: 'Generative summaries & grounded search \u2014 preview',
body: '<div class="note-callout"><p><strong>Note.</strong> AI summarization APIs are a preview feature. Preview features aren\u2019t meant for production use and may have restricted functionality. These features are available before an official release so that customers can get early access and provide feedback.</p></div><p>The <strong>AI Web API</strong> is a specialization of the Web API for AI-augmented reads. It exposes two endpoints: <strong>Search Summary</strong> (grounded AI answer over the site\u2019s search index) and <strong>Data Summarization</strong> (AI summary of a single record or a collection of records). Microsoft Learn documents one ready-made Data Summarization recipe for a Copilot-style summary on the standard support-case (<code>incident</code>) detail page \u2014 use it verbatim if it fits your UX, or configure Data Summarization with your own columns and prompt for any other table.</p><p>Like regular Web API, it runs in the browser and uses the same cookie-based auth and CSRF token. It respects the same table permissions and column permissions \u2014 so once you\u2019ve wired Web API for a table, the AI endpoints can read the same data and add a generative pass on top. The output is a human-readable summary, not raw records.</p><p><strong>Read-only</strong>: neither endpoint mutates Dataverse, so the Secure Action Principle does not apply. <strong>Three-level admin gate</strong>: tenant PowerShell setting, Copilot Hub environment/site governance, and (for Search Summary) the site-level maker toggle must all allow it.</p>',
diagram: [
{ label: 'Browser', value: 'Opens case detail page' },
{ label: 'AI Web API', value: 'POST /_api/summarization/data/...' },
{ label: 'Table Permissions', value: 'Same as Web API' },
{ label: 'AI Summary', value: 'Rendered in UI card' }
],
whenUse: ['Summary card on a detail page', 'Summary of a list of records', 'Suggested / related records on a page', 'AI-grounded site search with citations'],
whenAvoid: ['Operations that write data', 'Deterministic / exact data output', 'Data that isn\'t in Dataverse', 'Tenants without generative AI enabled'],
docs: [
{ label: 'Search Summary API', url: 'https://learn.microsoft.com/power-pages/configure/search/generative-ai' },
{ label: 'Data Summarization API', url: 'https://learn.microsoft.com/power-pages/configure/data-summarization-api' }
]
},
{
id: 'serverlogic',
icon: '⚙',
iconCls: 'concept-icon-serverlogic',
title: 'Server Logic',
subtitle: 'Server-side business rules',
body: '<p><strong>Server Logic</strong> (also called server-side code) runs code on the Power Pages server \u2014 not in the user\u2019s browser. This is critical for business rules that must be tamper-proof.</p><p>For example, if a record can only move from \u201cDraft\u201d to \u201cSubmitted\u201d (never from \u201cDraft\u201d to \u201cApproved\u201d), that rule must be checked on the server. If you only checked it in the browser, a technical user could bypass it with dev tools.</p><p>Server Logic is also the right choice when you need to read or write multiple Dataverse tables in one operation (an atomic transaction), or when you want to batch multiple queries into a single call for performance.</p>',
diagram: [
{ label: 'Browser', value: 'Calls /_api/serverlogics/...' },
{ label: 'Power Pages Server', value: 'Runs your server code' },
{ label: 'Dataverse', value: 'Reads/writes with full access' },
{ label: 'Browser', value: 'Gets result back' }
],
whenUse: ['Business rule enforcement', 'Multi-table transactions', 'Aggregation / batch queries', 'Logic that must be tamper-proof'],
whenAvoid: ['Simple CRUD (Web API is simpler)', 'Async / background work', 'Sending emails or notifications', 'Scheduled recurring tasks'],
docs: [
{ label: 'Server Logic Overview', url: 'https://learn.microsoft.com/power-pages/configure/server-logic-overview' },
{ label: 'Dataverse SDK', url: 'https://learn.microsoft.com/power-pages/configure/server-objects#dataverse' }
]
},
{
id: 'cloudflow',
icon: '☁',
iconCls: 'concept-icon-cloudflow',
title: 'Cloud Flows',
subtitle: 'Async background automation',
body: '<p><strong>Cloud Flows</strong> (built in Power Automate) run asynchronously in the background \u2014 meaning the user doesn\u2019t wait for them to finish. They are triggered by events (like a record status changing) or on a schedule (like \u201cevery day at 8 AM\u201d).</p><p>Cloud Flows are ideal for work that happens <em>after</em> the main operation: sending approval emails, notifying users, generating reports, or checking for expiring contracts. They have hundreds of built-in connectors to services like Outlook, Teams, SharePoint, and more.</p><p><strong>Important:</strong> Cloud Flows are built in the Power Automate visual designer, not in code. This plan provides the design specification for each flow, but they are created manually in the <a href="https://make.powerautomate.com" style="color:var(--accent);">Power Automate maker portal</a>.</p>',
diagram: [
{ label: 'Trigger', value: 'Record status changes' },
{ label: 'Cloud Flow', value: 'Runs in Power Automate' },
{ label: 'Connectors', value: 'Outlook, Teams, etc.' },
{ label: 'Result', value: 'Email sent, record updated' }
],
whenUse: ['Email notifications', 'Approval workflows', 'Scheduled / recurring jobs', 'Integration with external services'],
whenAvoid: ['Real-time validation', 'Operations the user waits for', 'Complex data transformations', 'Anything needing instant response'],
docs: [
{ label: 'Cloud Flows Overview', url: 'https://learn.microsoft.com/power-automate/overview-cloud' },
{ label: 'Triggers & Actions', url: 'https://learn.microsoft.com/power-automate/triggers-introduction' }
]
}
];
const APPROACH_META = {
webapi: { label: 'Web API', color: 'var(--webapi)', cls: 'webapi' },
aiwebapi: { label: 'AI Web API', color: 'var(--aiwebapi)', cls: 'aiwebapi' },
serverlogic: { label: 'Server Logic', color: 'var(--serverlogic)', cls: 'serverlogic' },
cloudflow: { label: 'Cloud Flow', color: 'var(--cloudflow)', cls: 'cloudflow' },
};
// --- Navigation ---
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
function approachChip(approach) {
const m = APPROACH_META[approach] || { label: approach, cls: 'webapi' };
return `<span class="approach-chip approach-${m.cls}"><span class="dot"></span>${m.label}</span>`;
}
function complexityBadge(level) {
if (!level) return '';
return `<span class="complexity-badge complexity-${level}">${level}</span>`;
}
function statusBadge(status) {
if (!status) return '';
const labels = { 'new': 'New', 'existing': 'Existing', 'extends': 'Extends Existing' };
return `<span class="status-badge status-${status}">${labels[status] || status}</span>`;
}
function implementBar(approach) {
if (approach === 'webapi') {
return `<div class="implement-bar">
<span class="implement-label">Implement with:</span>
<span class="implement-cmd">/integrate-webapi</span>
<span class="implement-hint">Generates Web API service code + table permissions</span>
</div>`;
} else if (approach === 'aiwebapi') {
return `<div class="implement-bar">
<span class="implement-label">Implement with:</span>
<span class="implement-cmd">/add-ai-webapi</span>
<span class="implement-hint">Generates summarization service + Summarization/* site settings</span>
</div>`;
} else if (approach === 'serverlogic') {
return `<div class="implement-bar">
<span class="implement-label">Implement with:</span>
<span class="implement-cmd">/add-server-logic</span>
<span class="implement-hint">Generates server-side endpoint + SDK code</span>
</div>`;
} else if (approach === 'cloudflow') {
return `<div class="implement-bar manual">
<span class="implement-label">Build manually in:</span>
<span class="implement-cmd">Power Automate</span>
<span class="implement-hint">Cloud Flows are designed visually, not generated as code</span>
</div>`;
}
return '';
}
// --- Concepts Tab ---
const conceptsContainer = document.getElementById('conceptsContainer');
conceptsContainer.innerHTML = CONCEPTS.map(c => {
const diagramHtml = c.diagram.map((d, idx) => {
const arrow = idx < c.diagram.length - 1 ? '<span class="concept-diagram-arrow">➜</span>' : '';
return `<div class="concept-diagram-box"><div class="cd-label">${d.label}</div><div class="cd-value">${d.value}</div></div>${arrow}`;
}).join('');
const whenGrid = `<div class="concept-when-grid">
${c.whenUse.map(w => `<div class="concept-when-item use">${w}</div>`).join('')}
${c.whenAvoid.map(w => `<div class="concept-when-item avoid">${w}</div>`).join('')}
</div>`;
const docsHtml = c.docs.map(d => `<a class="concept-link" href="${d.url}" target="_blank">📖 ${d.label}</a>`).join('');
return `<div class="concept-card">
<div class="concept-header">
<div class="concept-icon-box ${c.iconCls}">${c.icon}</div>
<div><div class="concept-title">${c.title}</div><div class="concept-subtitle">${c.subtitle}</div></div>
</div>
<div class="concept-body">${c.body}</div>
<div class="concept-diagram">${diagramHtml}</div>
<div class="concept-when">
<strong>When to use</strong> <span style="color:#107c10;">●</span> vs <strong>when to avoid</strong> <span style="color:#d13438;">●</span>
${whenGrid}
</div>
<div style="padding:12px 22px;">${docsHtml}</div>
</div>`;
}).join('');
// --- Overview Tab ---
const webApiCount = ITEMS.filter(i => i.approach === 'webapi').length;
const aiWebApiCount = ITEMS.filter(i => i.approach === 'aiwebapi').length;
const serverLogicCount = ITEMS.filter(i => i.approach === 'serverlogic').length;
const cloudFlowCount = ITEMS.filter(i => i.approach === 'cloudflow').length;
document.getElementById('statWebApi').textContent = webApiCount;
document.getElementById('statAiWebApi').textContent = aiWebApiCount;
document.getElementById('statServerLogic').textContent = serverLogicCount;
document.getElementById('statCloudFlow').textContent = cloudFlowCount;
document.getElementById('topbarCount').textContent =
ITEMS.length + ' integration item' + (ITEMS.length !== 1 ? 's' : '');
const summaryEl = document.getElementById('approachesSummary');
const usedApproaches = [...new Set(ITEMS.map(i => i.approach))];
summaryEl.innerHTML = usedApproaches.map(approach => {
const meta = APPROACH_META[approach] || APPROACH_META.webapi;
const count = ITEMS.filter(i => i.approach === approach).length;
return `<span class="approach-chip approach-${meta.cls}">
<span class="dot"></span>${meta.label} (${count})
</span>`;
}).join('');
document.getElementById('rationaleContainer').innerHTML = RATIONALE.map(r => `
<div class="principle">
<div class="principle-icon">${r.icon}</div>
<div>
<div class="principle-title">${r.title}</div>
<div class="principle-desc">${r.desc}</div>
</div>
</div>
`).join('');
// --- Data Flow Tab ---
const dfContainer = document.getElementById('dataflowContainer');
dfContainer.innerHTML = DATA_FLOWS.map(flow => {
const stepsHtml = flow.steps.map((step, idx) => {
const m = APPROACH_META[step.approach] || APPROACH_META.webapi;
const connector = idx < flow.steps.length - 1 ? '<span class="flow-connector">➜</span>' : '';
return `<div class="flow-step" style="border-top:3px solid ${m.color}">
<div class="flow-step-label">${m.label}</div>
<div class="flow-step-name">${step.name}</div>
<div class="flow-step-detail">${step.detail || ''}</div>
</div>${connector}`;
}).join('');
return `<div class="card" style="margin-bottom:16px;">
<div style="font-size:14px;font-weight:700;color:var(--text-bright);margin-bottom:4px;">${flow.trigger}</div>
<div style="font-size:12px;color:var(--text-dim);margin-bottom:14px;">${flow.description}</div>
<div class="flow-arrow">${stepsHtml}</div>
</div>`;
}).join('');
// --- Implementation Order Tab ---
// Derive phases from items: group by approach in recommended build order
// If items have explicit `phase` numbers, use those instead
const orderContainer = document.getElementById('orderContainer');
const PHASE_META = [
{ approach: 'serverlogic', title: 'Server Logic', subtitle: 'Server-side validation and business rules', color: 'var(--serverlogic)' },
{ approach: 'webapi', title: 'Web API', subtitle: 'Browser-to-Dataverse CRUD operations', color: 'var(--webapi)' },
{ approach: 'aiwebapi', title: 'AI Web API', subtitle: 'Generative summaries and grounded search', color: 'var(--aiwebapi)' },
{ approach: 'cloudflow', title: 'Cloud Flows', subtitle: 'Async notifications and background automation', color: 'var(--cloudflow)' },
];
const hasExplicitPhases = ITEMS.some(i => typeof i.phase === 'number');
let phases;
if (hasExplicitPhases) {
// Group items by their explicit phase number
const phaseMap = {};
ITEMS.forEach(item => {
const p = item.phase || 1;
if (!phaseMap[p]) phaseMap[p] = [];
phaseMap[p].push(item);
});
phases = Object.keys(phaseMap).sort((a, b) => a - b).map((num, idx) => {
const items = phaseMap[num];
const dominant = items.reduce((acc, i) => { acc[i.approach] = (acc[i.approach] || 0) + 1; return acc; }, {});
const topApproach = Object.keys(dominant).sort((a, b) => dominant[b] - dominant[a])[0];
const meta = PHASE_META.find(m => m.approach === topApproach) || PHASE_META[0];
return { num: parseInt(num), title: `Phase ${num} — ${meta.title}`, subtitle: meta.subtitle, color: meta.color, items };
});
} else {
// Auto-derive: group by approach in recommended order
phases = PHASE_META.map((meta, idx) => {
const items = ITEMS.filter(i => i.approach === meta.approach);
return items.length > 0 ? { num: idx + 1, title: `Phase ${idx + 1} — ${meta.title}`, subtitle: meta.subtitle, color: meta.color, items } : null;
}).filter(Boolean);
}
orderContainer.innerHTML = phases.map((phase, phaseIdx) => {
const itemsHtml = phase.items.map(item => {
const m = APPROACH_META[item.approach];
const depHtml = item.depends ? `<div class="dep-tag">${item.depends}</div>` : '';
return `<div class="phase-item">
<div class="phase-item-left">
<div class="phase-item-name">${item.name} ${approachChip(item.approach)}</div>
<div class="phase-item-desc">${item.description}</div>
${depHtml}
${implementBar(item.approach)}
</div>
<div class="phase-item-right">
${complexityBadge(item.complexity)}
</div>
</div>`;
}).join('');
return `<div class="phase-card${phaseIdx === 0 ? ' open' : ''}" onclick="this.classList.toggle('open')">
<div class="phase-header">
<div class="phase-num" style="background:${phase.color};">${phase.num}</div>
<div>
<div class="phase-title">${phase.title}</div>
<div class="phase-subtitle">${phase.subtitle}</div>
</div>
<span class="phase-chevron">▼</span>
</div>
<div class="phase-items" onclick="event.stopPropagation()">
${itemsHtml}
</div>
</div>`;
}).join('');
// --- Items Tab ---
const itemParts = [];
if (webApiCount) itemParts.push(webApiCount + ' Web API');
if (aiWebApiCount) itemParts.push(aiWebApiCount + ' AI Web API');
if (serverLogicCount) itemParts.push(serverLogicCount + ' Server Logic');
if (cloudFlowCount) itemParts.push(cloudFlowCount + ' Cloud Flow');
document.getElementById('itemsDesc').textContent =
ITEMS.length + ' item' + (ITEMS.length !== 1 ? 's' : '') + ' \u2014 ' + itemParts.join(', ');
const itemsContainer = document.getElementById('itemsContainer');
itemsContainer.innerHTML = ITEMS.map(item => {
const m = APPROACH_META[item.approach] || APPROACH_META.webapi;
const detailsHtml = (item.details || []).map(d =>
`<div class="detail-item">
<div class="field-label">${d.label}</div>
<div style="font-size:13px;color:var(--text);">${d.value}</div>
</div>`
).join('');
const depHtml = item.depends ? `<div class="dep-tag">${item.depends}</div>` : '';
const docsHtml = (item.docs || []).map(d =>
`<a class="concept-link" href="${d.url}" target="_blank">📖 ${d.label}</a>`
).join('');
return `<div class="card" style="border-left:3px solid ${m.color};">
<div class="item-header">
<div>
<div class="item-name">${item.name} ${approachChip(item.approach)} ${complexityBadge(item.complexity)} ${statusBadge(item.status)}</div>
<div class="item-desc">${item.description}</div>
</div>
</div>
<div class="reason-box reason-${m.cls}"><strong>Why ${m.label}:</strong> ${item.reasoning}</div>
${depHtml}
${detailsHtml ? `<div class="detail-grid">${detailsHtml}</div>` : ''}
${docsHtml ? `<div class="doc-links">${docsHtml}</div>` : ''}
${implementBar(item.approach)}
</div>`;
}).join('');
</script>
</body>
</html>
Backend Integration Decision Framework
Use this framework to recommend the right backend integration approach for a Power Pages code site. A single user request may map to one approach or a combination.
The Four Approaches
Web API (/integrate-webapi)
What it is: A client-side, browser-based OData API that lets frontend code perform CRUD operations directly against Dataverse tables via /_api/<entity-set> endpoints.
How it works: JavaScript/TypeScript in the browser makes HTTP calls to Dataverse. Authentication is cookie-based (user session). Table permissions and web roles control access. No code runs on the server — the browser does all the work.
Best for:
- Displaying Dataverse records in the UI (lists, tables, dashboards, detail views)
- Form submissions that create or update Dataverse records
- Filtering, sorting, searching records with OData queries
- Inline editing of records
- File/image upload to Dataverse File columns
- Real-time data binding where the user sees results immediately
- Aggregation queries (
$apply) for charts and summaries
Not suitable when:
- The logic requires calling external APIs (Stripe, SendGrid, Graph, etc.)
- API keys, client secrets, or other credentials are involved
- Business logic must be hidden from the browser (pricing rules, validation algorithms)
- The operation needs to batch multiple table queries into one call for performance
- The write depends on a business rule that must be tamper-proof (e.g., status transitions, approval workflows) — use Server Logic to validate AND execute the write in a single call instead (see Secure Action Principle)
- The operation should happen in the background after the user moves on
Key characteristics:
- Code runs in the browser (visible in DevTools)
- No external API access
- No credential/secret handling
- Real-time, synchronous responses
- Requires table permissions for every Dataverse table accessed
- CSRF token required for mutations (POST, PATCH, DELETE)
---
AI Web API (/add-ai-webapi)
What it is: A specialization of Web API for AI-augmented reads. Two endpoints: Search Summary (/_api/search/v1.0/summary) produces a grounded AI answer over the site's search index, and Data Summarization (/_api/summarization/data/v1.0/<entitySet>(<id>)?$select=...) produces an AI summary of one record or a collection. Microsoft also documents a ready-made Data Summarization configuration for the standard incident table (the support-case Copilot summary recipe) — that's an example of Data Summarization, not a separate API.
How it works: Same browser-to-portal auth model as Web API — cookie session + CSRF token from /_layout/tokenhtml. Data Summarization reads via the portal's _api layer, so it respects the same table permissions, column permissions, and Web API field settings as regular Web API. The server adds a generative-AI pass that returns a text summary instead of raw records. Read-only by definition — neither endpoint mutates Dataverse.
Best for:
- AI summary of a record on its detail page (e.g., "summarize this case including its portal comments")
- AI summary of a list of records (e.g., "summarize open cases on the dashboard", "trends in this week's orders")
- Related-record discovery on a detail page (e.g., "suggest KB articles relevant to this case", "similar products")
- AI-grounded site search with citations (replaces or augments keyword-only search UIs)
Not suitable when:
- The output needs to be deterministic or exact (AI output is probabilistic; use Web API for exact data)
- The operation writes data (these endpoints are read-only — use Web API or Server Logic)
- The data isn't in Dataverse (Search Summary indexes site content; Data Summarization requires a Dataverse entity set)
- The tenant doesn't have generative AI features enabled (these APIs are preview; until an admin enables them, Data Summarization returns HTTP 400 with
error.code = 90041001, while Search Summary returns HTTP 200 with an embedded{ Code, Message }envelope instead) - The site uses the Microsoft-shipped search control and just wants AI-summarised search results on it — that's a workspace toggle (
Search/Summary/Titlecontent snippet), not a code integration
Key characteristics:
- Layered on Web API — Data Summarization requires the same
Webapi/<table>/enabled,Webapi/<table>/fields, and table permissions as regular Web API reads. Search Summary has no per-table prereqs. - Code runs in the browser; the AI pass happens server-side
- Read-only — no validate-and-execute concerns (Secure Action Principle does not apply)
- Preview feature — requires tenant admin to enable generative AI, and (for Search Summary) a site-level Copilot workspace toggle. APIs and admin toggles may change before GA.
/add-ai-webapidelegates Layer 1/2 to/integrate-webapiin AI-only read mode — if a Web API item already covers the same table, the AI item detects the existing prereqs and skips the delegation
Relationship to Web API: AI Web API items for a given Dataverse table should be ordered after any regular Web API item for the same table (Web API sets up Layer 1/2; AI adds Layer 3 on top). Search Summary items can stand alone since they have no per-table prereqs.
Field-list drift caveat (when both items exist for the same table): if /integrate-webapi has already configured Webapi/<table>/fields for full CRUD (including primary key and lookup write forms), the follow-up /add-ai-webapi run detects Layer 1/2 as ready and skips its delegation — leaving the broader CRUD allowlist in place. The AI surface still works, but the fields list is broader than AI-only mode would produce. The ai-webapi-settings-architect agent flags this as an advisory in its plan; the user can narrow the allowlist after the AI surface lands by editing the YAML directly. If a strict read-only posture matters from day one, run /add-ai-webapi before /integrate-webapi on the same table — the AI skill will then write the minimal fields list and the later Web API run will widen it.
---
Server Logic (/add-server-logic)
What it is: Server-side JavaScript that runs in a sandboxed V8 engine on the Power Pages server. Exposed as REST endpoints at /_api/serverlogics/<name>. Code is hidden from the browser.
How it works: The frontend calls a server logic endpoint. The server executes the JavaScript function matching the HTTP method (get, post, put, patch, del). The function can access Dataverse, call external APIs, read site settings/environment variables, and return a computed response.
Best for (by category):
| Category | Use Case | Example |
|---|---|---|
| Security | Secure content rendering | Healthcare portal: patient data after server-side role check |
| Secret & credential management | Stripe API key stays on server; client never sees it | |
| Server-side validation | Reject order if quantity exceeds inventory | |
| Rate limiting / abuse prevention | Max 5 support tickets/hour/user, enforced server-side | |
| Authorization | Complex permissions beyond table permissions | Moderator edits only in their assigned community |
| Row-level logic | Manager approves expenses for direct reports < $1K | |
| Data Integrity | Cross-entity transactions | Order + line items + inventory: all roll back if one fails |
| Computed data | Insurance premium calculated server-side; client sees result | |
| Business rule enforcement (validate-and-execute) | Permit: Submitted > Review > Approved — server logic validates the transition AND writes the new status to Dataverse in a single call, so the client never writes the status field directly | |
| State machine transitions | Server logic reads current status, validates the target status is reachable, and performs the update — preventing clients from jumping to arbitrary states | |
| Performance | Batch operations | Dashboard: Contacts + Orders + Products in one call |
| Data aggregation | 12 monthly totals instead of 10,000 raw rows | |
| Response formatting | JSON, CSV, or XML based on caller request | |
| Integration | Third-party services | PayPal/Stripe payment via server-side call |
| On-prem services | ERP via Azure Relay for stock levels | |
| Microsoft Graph / SharePoint | Upload documents, read SharePoint lists via OAuth | |
| Wrapping Dataverse Custom APIs/Actions | Expose existing Dataverse custom actions (both Custom APIs and Custom Process Actions) to the portal via InvokeCustomApi. Discover available actions with list-custom-actions.js before recommending building from scratch — the customer may already have actions that do what's needed. |
Not suitable when:
- The operation is purely async/background (no immediate response needed) — use Cloud Flows instead
- The scenario only needs simple Dataverse CRUD with no extra logic — Web API is simpler
- The workflow spans multiple systems with built-in connectors (e.g., send email + create record + notify Teams) — Cloud Flows have 400+ connectors
- The operation takes longer than 120 seconds (platform maximum timeout)
Key characteristics:
- Code runs on the server (hidden from browser)
- Can call external APIs via
Server.Connector.HttpClient - Can access Dataverse via
Server.Connector.Dataverse(respects table permissions) - Can read site settings and environment variables for credential management
- 5 functions only: get, post, put, patch, del — each must return a string
- ECMAScript 2023 sandbox, no npm packages, no browser APIs
- 120-second maximum timeout, 10 MB default memory
- CSRF token required for non-GET requests
---
Cloud Flows (/add-cloud-flow)
What it is: Power Automate cloud flows triggered from the Power Pages frontend. The flow runs asynchronously in the Power Automate service and has access to 400+ connectors.
How it works: The frontend calls a registered cloud flow endpoint. The flow runs in the background on Power Automate infrastructure. The user does not wait for the flow to complete — the trigger returns immediately with a confirmation.
Best for:
- Background/async processing where the user doesn't need an immediate result
- Sending emails or notifications after a form submission
- Processing orders, approvals, or multi-step business workflows
- Integrating with systems that have Power Automate connectors (Teams, Outlook, SharePoint, Dynamics 365, SAP, ServiceNow, etc.)
- Long-running processes that exceed the 120-second server logic timeout
- Orchestrating multi-step workflows across multiple systems
- Scenarios where no-code/low-code maintainability is important (business users can modify flows)
Not suitable when:
- The frontend needs an immediate, computed response (use Server Logic)
- The operation is simple Dataverse CRUD (use Web API)
- The logic needs to return data that the UI renders immediately (use Server Logic or Web API)
- Low latency is critical — flow trigger has overhead compared to direct API calls
Key characteristics:
- Runs asynchronously in Power Automate (fire-and-forget from the frontend)
- 400+ pre-built connectors
- No-code/low-code — modifiable by business users in the Power Automate designer
- Output is not immediately consumed by the user
- Registered via
.cloudflowconsumer.ymlmetadata files - Requires web role assignments for authorization
---
Decision Matrix
Use these questions to narrow down the recommendation:
| Question | Web API | AI Web API | Server Logic | Cloud Flow |
|---|---|---|---|---|
| Does the UI need to display data from Dataverse? | Yes | Possible | Possible | No |
| Does the UI need an AI-generated summary of Dataverse data? | No | Yes | No | No |
| Does the UI need AI-grounded search with citations? | No | Yes | No | No |
| Does a detail page need related-record discovery (e.g., suggested KB articles)? | No | Yes | Possible | No |
| Does it call external APIs (non-Dataverse)? | No | No | Yes | Possible |
| Are credentials/secrets involved? | No | No | Yes | Possible |
| Must business logic be hidden from the browser? | No | No | Yes | N/A |
| Does the write depend on a business rule that must be tamper-proof? | No | N/A (read-only) | Yes (validate-and-execute) | No |
| Is the operation async/background (user doesn't wait)? | No | No | No | Yes |
| Is it a simple Dataverse CRUD with no extra logic? | Yes | Overkill | Overkill | Overkill |
| Does it need 400+ connectors (Teams, Outlook, SAP)? | No | No | No | Yes |
| Should the response render immediately in the UI? | Yes | Yes | Yes | No |
| Does it batch multiple queries for performance? | No | No | Yes | No |
| Is it a long-running process (>120 seconds)? | No | No | No | Yes |
| Should non-developers be able to modify the logic? | No | No | No | Yes |
| Does the output need to be deterministic / exact? | Yes | No (probabilistic) | Yes | Yes |
The Secure Action Principle
When server logic validates a business rule, it must also execute the resulting action.
This is the most important architectural principle for secure backend integration. Splitting validation from execution — where server logic validates a rule and then a separate client-side Web API call performs the write — creates a security gap because the client can skip the validation call and write directly via Web API.
Anti-Pattern: Validate-Only Server Logic + Client-Side Write
❌ INSECURE — Do NOT use this pattern for security-sensitive operations:
1. Browser calls /_api/serverlogics/validate-transition (Server Logic checks Draft → Submitted is valid)
2. Server Logic returns { valid: true }
3. Browser calls /_api/cr65f_orders(id) with PATCH { status: "Submitted" } (Web API writes the change)
Problem: A user can skip step 1 and go directly to step 3 via browser dev tools,
bypassing all server-side validation.Correct Pattern: Validate-and-Execute Server Logic
✅ SECURE — Server logic validates AND executes:
1. Browser calls /_api/serverlogics/transition-order with POST { orderId: "...", newStatus: "Submitted" }
2. Server Logic reads current record from Dataverse, validates Draft → Submitted is allowed
3. Server Logic writes the status change to Dataverse via Server.Connector.Dataverse.UpdateRecord
4. Server Logic returns { status: "success", previousStatus: "Draft", newStatus: "Submitted" }
The browser never makes a direct Web API write for this operation.When Does This Apply?
Use validate-and-execute (server logic performs the write) whenever any of these are true:
| Condition | Example |
|---|---|
| The operation enforces a state machine or lifecycle | Order status: Draft → Submitted → Approved → Fulfilled |
| The write depends on a business rule that must be tamper-proof | "Only allow bid submission before the deadline" |
| The operation spans multiple tables atomically | Award a bid + reject all others + update RFx status |
| The write involves computed or derived values the client shouldn't control | Server calculates a discount or score and writes it |
| The operation requires authorization beyond table permissions | "Managers can only approve expenses for their direct reports" |
| The client should not have write access to the field at all | Status fields that follow strict transitions |
Use Web API for the write (validation-only server logic is fine) when all of these are true:
| Condition | Example |
|---|---|
| The write is simple CRUD with no business rules | Editing a name or description field |
| Table permissions alone enforce the access control | User edits their own profile |
| The fields being written have no restricted value constraints | Free-text fields, dates the user picks |
| Skipping validation would not cause a security or data integrity issue | Updating a contact's phone number |
Impact on Plan Design
When building integration plans, this principle affects how items are assigned to approaches:
1. State transitions — The server logic endpoint should accept the entity ID and target status, validate the transition, and write the new status to Dataverse. The frontend calls only the server logic endpoint — there is no separate Web API PATCH for the status field.
2. Multi-step operations — When an action involves validation + write + side effects (e.g., award a bid, reject losers, update event status), the entire sequence belongs in one server logic endpoint. The frontend makes a single call.
3. Mixed operations on the same table — A table may use Web API for some fields (e.g., editing a description) and server logic for others (e.g., changing status). This is expected and correct. Table permissions should grant read access broadly but restrict write access to fields that are safe for direct client writes.
4. Phase ordering — Server logic endpoints that validate-and-execute should be built before any dependent frontend work. The frontend for these operations calls server logic, not Web API.
---
Common Combinations
Many real-world scenarios use multiple approaches together:
| Combination | When to use | Example |
|---|---|---|
| Web API + AI Web API | UI displays raw Dataverse records directly and also surfaces an AI-generated summary of the same data. Most common AI pattern. | Case detail page shows incident fields and portal comments via Web API, plus a Copilot summary card via AI Web API. Dashboard shows a list of orders and an AI summary of trends. |
| Web API + Server Logic | UI reads/writes non-sensitive fields directly, but security-sensitive operations go through server logic that validates and executes | Dashboard displays records via Web API; status transitions go through server logic that validates and writes |
| AI Web API + Server Logic | AI-augmented read surface over data that was computed or prepared by server-side logic | Server logic aggregates raw claims into a curated incident record (validate-and-execute); AI Web API summarises the curated record on the detail page |
| Server Logic + Cloud Flow | Real-time endpoint validates and executes the action, then async processing follows | Server logic validates transition and writes the new status, then a Cloud Flow sends the notification email |
| Web API + Cloud Flow | UI manages data directly (no business rules on the write), and some actions trigger background workflows | User edits a description via Web API; a separate "Submit for Approval" action triggers a Cloud Flow |
| Web API + AI Web API + Cloud Flow | Users browse and edit records, see AI summaries, and trigger async workflows | Support portal: list/edit cases (Web API), Copilot case summary with suggested KB articles (AI Web API), async assignment email (Cloud Flow) |
| All four | Complex application with safe direct writes, AI-augmented reads, secure server-side actions, and automation | Web API for browsing/editing non-sensitive fields, AI Web API for summaries and grounded search, Server Logic for state transitions and payment processing, Cloud Flow for notifications |
Mapping User Intent to Approach
| User says... | Likely approach | Reasoning |
|---|---|---|
| "Show data from Dataverse" / "display records" / "CRUD" | Web API | Direct data access, real-time UI binding |
| "Filter and sort products" / "search contacts" | Web API | Standard OData queries, no server logic needed |
| "Summarize this case" / "AI summary on the detail page" / "Copilot summary" | AI Web API | Data Summarization on a single record (the Microsoft-shipped support-case recipe is one option for the standard incident table) |
| "Summarize open cases" / "AI summary of this list" / "highlight trends in these records" | AI Web API | Data Summarization over a collection (list-summary pattern) |
| "Show relevant KB articles" / "suggest similar cases" / "related products" | AI Web API | Search Summary for related-record discovery — grounded retrieval returns AI-ranked citations from the site's search index |
| "AI search" / "semantic search" / "search with summary" | AI Web API | Search Summary endpoint returns a grounded AI answer plus citations for a user query |
| "Call an external API" / "integrate with Stripe/Twilio/etc." | Server Logic | External API calls with credential protection |
| "Add validation on the server" / "prevent bypassing" | Server Logic | Server-side enforcement (security) |
| "Rate limit submissions" / "prevent abuse" | Server Logic | Server-side enforcement (security) |
| "Calculate premium/price/discount on the server" | Server Logic | Computed data — logic hidden from browser |
| "Enforce a workflow sequence" / "status transitions" | Server Logic (validate-and-execute) | Server logic validates the transition AND writes the new status — the client never writes status directly via Web API |
| "Only let managers approve their team's expenses" | Server Logic | Row-level authorization logic |
| "Connect to our on-prem ERP" / "Azure Relay" | Server Logic | On-prem integration via server-side call |
| "Return data as CSV/XML" / "format the response" | Server Logic | Response formatting (performance) |
| "Send an email when..." / "notify the team when..." | Cloud Flow | Async notification, no immediate UI response |
| "Process orders in the background" | Cloud Flow | Background processing, user doesn't wait |
| "Batch multiple API calls" / "dashboard loads too slow" | Server Logic | Combine multiple queries into one endpoint |
| "Upload to SharePoint" / "call Microsoft Graph" | Server Logic | External API with OAuth credentials |
| "Use an existing custom action" / "call a Dataverse custom API" / "wrap a custom action" | Server Logic | Existing Dataverse custom action exposed to portal via InvokeCustomApi — discover available actions first |
| "Add an approval workflow" | Cloud Flow | Multi-step workflow with connectors |
| "Hide pricing logic from the browser" | Server Logic | Code hidden from client |
| "Bulk import CSV" / "process file in background" | Cloud Flow | Long-running background processing |
| "Create a record and send a confirmation email" | Web API + Cloud Flow | CRUD is immediate, email is async |
| "Validate inventory and process payment" | Server Logic (both) | Server-side validation + external API call |
| "Submit form, assign to team, and email confirmation" | Web API + Cloud Flow | Dataverse write + async assignment/email |
Related skills
FAQ
What does integrate-backend do?
>-
When should I use integrate-backend?
Invoke when >-.
Is integrate-backend safe to install?
Review the Security Audits panel on this page before installing in production.