Install
$ agentstack add skill-korchard333-claude-power-platform-community-perf-optimise ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Skill: Performance Optimization
When to Use
Trigger when diagnosing or optimizing performance for Canvas Apps, Model-Driven Apps, Code Apps, Power Automate flows, Dataverse queries, or plugins. Also trigger when addressing API throttling (429 errors) or capacity limits.
Canvas App Performance
Delegation
Delegation pushes query logic to the data source. Non-delegable operations process only the first 500-2000 records locally.
| Delegable to Dataverse | NOT Delegable | |---|---| | Filter with =, <>, , = | Distinct | | Filter with in (choice columns) | CountRows (use CountIf for delegable) | | Sort (single column) | GroupBy | | Search (Dataverse only) | AddColumns in a Filter | | StartsWith | Nested Filter | | LookUp | First applied to non-delegable |
Key Canvas Optimizations
// BAD: Non-delegable — loads all records, filters locally
Filter(Contacts, Text(Phone, "[$-en-US]#") = "555-0100")
// GOOD: Delegable — server-side filter
Filter(Contacts, Phone = "555-0100")
// BAD: Loading all records then counting
CountRows(Filter(LargeTable, Status = "Active"))
// GOOD: CountIf is delegable to Dataverse
CountIf(LargeTable, Status = "Active")
Concurrent() for Parallel Data Loading
// BAD: Sequential loading
ClearCollect(colAccounts, Accounts);
ClearCollect(colContacts, Contacts);
ClearCollect(colOrders, Orders);
// Total time = T1 + T2 + T3
// GOOD: Parallel loading
Concurrent(
ClearCollect(colAccounts, Accounts),
ClearCollect(colContacts, Contacts),
ClearCollect(colOrders, Orders)
);
// Total time = max(T1, T2, T3)
Named Formulas (Pre-Computation)
// App.Formulas — computed once, cached
ActiveAccounts = Filter(Accounts, statuscode = 1);
UserDepartment = Office365Users.MyProfile().Department;
TodayDate = Today();
// These are evaluated once and cached — no re-computation on screen navigation
Collection Pre-Loading
// App.OnStart — load reference data once
Concurrent(
ClearCollect(colStatusOptions, Choices('Accounts'.Status)),
ClearCollect(colCategoryOptions, Choices('Accounts'.Category)),
ClearCollect(colCountries, Filter(Countries, IsActive = true))
);
// Galleries and dropdowns use local collections (fast) instead of Dataverse calls
Dataverse Query Performance
$select — Only Request Needed Columns
# BAD: Returns ALL columns (slow, wastes bandwidth)
GET /api/data/v9.2/contacts
# GOOD: Only requested columns returned
GET /api/data/v9.2/contacts?$select=firstname,lastname,emailaddress1
$filter — Server-Side Filtering
# BAD: Get all, filter client-side
GET /api/data/v9.2/contacts # Then filter in JS
# GOOD: Server-side filter
GET /api/data/v9.2/contacts?$filter=statecode eq 0 and contains(lastname,'Smith')&$top=50
$top — Limit Result Set
# Always use $top for paged results
GET /api/data/v9.2/contacts?$top=50&$select=fullname,emailaddress1
# For next page, use @odata.nextLink from response
Indexing
Dataverse automatically indexes primary key and common columns. For custom columns used in filters:
Power Platform Admin Center → Environments → [env] → Settings
→ Administration → System Settings → Administration tab
→ Performance: Entity-specific index recommendations
Or request custom indexes via support for high-volume tables
FetchXML Aggregate vs OData
For aggregations, FetchXML is significantly faster than OData:
# Equivalent OData (less efficient for aggregation)
GET /api/data/v9.2/contacts?$apply=filter(statecode eq 0)/aggregate(contactid with countdistinct as count, annual_revenue with sum as total_revenue)
Alternate Keys for Upsert
# Instead of: GET to check existence, then POST or PATCH
# Use: PATCH with alternate key (single call, upsert)
PATCH /api/data/v9.2/contacts(emailaddress1='john@contoso.com')
-H "If-Match: *"
-d '{"firstname": "John", "lastname": "Doe"}'
Plugin Performance
Sandbox Constraints
| Constraint | Limit | |---|---| | Execution timeout | 2 minutes | | Memory | 256 MB | | CPU | Shared (no guarantee) | | External HTTP calls | Allowed in async, risky in sync |
Optimization Patterns
// BAD: Query inside a loop
foreach (var contactId in contactIds)
{
var contact = service.Retrieve("contact", contactId, new ColumnSet("fullname"));
// N+1 query problem
}
// GOOD: Single query with IN filter
var query = new QueryExpression("contact")
{
ColumnSet = new ColumnSet("fullname"),
Criteria = new FilterExpression
{
Conditions = {
new ConditionExpression("contactid", ConditionOperator.In, contactIds.ToArray())
}
}
};
var results = service.RetrieveMultiple(query);
// BAD: Synchronous HTTP call in sync plugin (blocks user, risks timeout)
var client = new HttpClient();
var response = await client.GetAsync("https://external-api.com/validate");
// GOOD: Use Service Bus or async plugin for external calls
// Or use pre-validated data from Dataverse
// BAD: Processing all records in bulk operation trigger
// Plugin fires once per record in a CreateMultiple — 1000 records = 1000 plugin executions
// GOOD: Batch-aware plugin using IPluginExecutionContext4
var context4 = (IPluginExecutionContext4)context;
// Check if running in bulk context and optimize accordingly
Code App Performance
Tree-Shaking and Bundle Size
// BAD: Import entire library
import _ from "lodash";
// GOOD: Import only what you need
import debounce from "lodash/debounce";
Lazy Routes (Code Splitting)
// React Router with lazy loading
import { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
function App() {
return (
}>
} />
} />
);
}
TanStack Query (Stale/Cache)
import { useQuery } from "@tanstack/react-query";
function useAccounts() {
return useQuery({
queryKey: ["accounts"],
queryFn: fetchAccounts,
staleTime: 5 * 60 * 1000, // 5 min — don't refetch if fresh
gcTime: 30 * 60 * 1000, // 30 min — keep in cache
refetchOnWindowFocus: false, // Don't refetch on tab switch
});
}
Virtual Scrolling for Large Lists
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
});
return (
{virtualizer.getVirtualItems().map((virtual) => (
{items[virtual.index].name}
))}
);
}
Power Automate Performance
Apply to Each Concurrency
Apply to each → Settings → Concurrency Control
→ Turn on: Yes
→ Degree of parallelism: 50 (maximum)
| Setting | Behavior | |---|---| | Off (default) | Sequential — 1 at a time | | On, parallelism = 20 | 20 concurrent iterations | | On, parallelism = 50 | Maximum concurrency |
> Warning: High concurrency can cause API throttling (429). Monitor and adjust.
Pagination
Action: List rows (Dataverse)
→ Settings → Pagination → On
→ Threshold: 100,000 (max records to retrieve)
Default without pagination: 5,000 rows max
Child Flows
Break large flows into child flows for:
- Reusability across multiple parent flows
- Cleaner flow design
- Independent error handling
- Parallel execution of independent branches
Avoid Deep Nesting
BAD: Apply to each → Condition → Apply to each → Condition → Apply to each
(> 8 levels of nesting = flow failure)
GOOD: Use child flows, Select/Filter expressions, or batch operations
Model-Driven App Performance
Form Load Optimization
| Optimization | Impact | |---|---| | Fewer tabs and sections | Less HTML to render | | Lazy subgrids | Load on tab expand, not on form load | | Fewer columns on form | Less data fetched | | Minimize OnLoad JavaScript | Defer non-critical logic | | Quick View forms instead of subgrids | Lighter weight for related data |
JavaScript Best Practices
// BAD: Heavy processing in OnLoad
function onLoad() {
// Fetches data, updates 20 fields, calls external API
var result = Xrm.WebApi.retrieveMultipleRecords("contact", "?$top=1000");
// ...
}
// GOOD: Minimal OnLoad, defer heavy work
function onLoad(context) {
// Only essential logic
setFieldVisibility(context);
// Defer non-critical work
setTimeout(function() {
loadSecondaryData(context);
}, 100);
}
API Request Limits
Protection Limits (Hard Throttle)
| Limit | Value | Scope | |---|---|---| | Per user | 6,000 requests / 5-minute window | Per user | | Per web server | 60,000 / 5 min | Per web server (shared) | | Concurrent requests | 52 per user | Simultaneous connections |
When Throttled (HTTP 429)
# Response includes Retry-After header
HTTP/1.1 429 Too Many Requests
Retry-After: 30
# Always honor Retry-After
sleep $(curl -sI ... | grep Retry-After | awk '{print $2}')
Strategies to Avoid Throttling
| Strategy | Implementation | |---|---| | Batch operations | Use CreateMultiple/UpdateMultiple instead of individual calls | | $select | Reduce payload size, faster responses | | Exponential backoff | On 429, wait 1s → 2s → 4s → 8s | | Connection pooling | Reuse HTTP connections | | Off-peak scheduling | Run bulk operations during low-usage hours | | Service protection limit monitoring | Track via Admin Center analytics |
Performance Checklist
## Performance Review Checklist
### Canvas App
- [ ] All gallery queries are delegable
- [ ] Using Concurrent() for parallel data loading
- [ ] Named Formulas for pre-computation
- [ ] Collections for reference data (not repeated server calls)
- [ ] No non-delegable functions on tables > 500 rows
### Dataverse Queries
- [ ] $select on every query (no select-all)
- [ ] $filter for server-side filtering
- [ ] $top for pagination
- [ ] FetchXML for aggregations
- [ ] Alternate keys for upsert operations
### Code App
- [ ] Tree-shaking (no unused library imports)
- [ ] Lazy routes for code splitting
- [ ] TanStack Query with staleTime configured
- [ ] Virtual scrolling for lists > 100 items
- [ ] Dynamic imports for heavy components
### Power Automate
- [ ] Apply to Each concurrency enabled (where safe)
- [ ] Pagination enabled for large queries
- [ ] Child flows for reusable/nested logic
- [ ] No nesting deeper than 4-5 levels
- [ ] Batch operations instead of loop + individual API calls
### Plugins
- [ ] No N+1 query patterns
- [ ] No sync HTTP calls in sync plugins
- [ ] Using early-bound types (faster than late-bound)
- [ ] ColumnSet specified (never ColumnSet(true))
### API Limits
- [ ] Retry-After handling for 429 responses
- [ ] Batch operations for bulk processing
- [ ] Off-peak scheduling for heavy operations
- [ ] Monitoring API usage in Admin Center
Anti-Patterns
- Querying all columns (
$selectomitted) — wastes bandwidth, slower response - Non-delegable functions on large datasets — silent data truncation
- Synchronous HTTP calls in sync plugins — blocks user, risks timeout
- Apply to Each without concurrency — sequential processing of 1000 items
- No caching in Code Apps — refetching data on every render
- Full page loads instead of virtual scrolling — DOM bloat, slow rendering
- Plugin executing inside loops — N+1 query pattern
- Ignoring 429 errors — retrying immediately without backoff makes it worse
- Loading all reference data on app start — slow startup, use lazy loading
- No $top on OData queries — potentially returning millions of rows
Related Skills
dataverse-web-api— Query optimization, batch operationscanvas-apps— Delegation patterns, performance techniquescode-apps— React performance, bundlingplugins— Sandbox constraints, execution optimizationpower-automate— Flow concurrency, paginationobservability— Performance monitoring and alerting
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: korchard333
- Source: korchard333/claude-power-platform-community
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.