Install
$ agentstack add skill-korchard333-claude-power-platform-community-pcf ✓ 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 No
- ✓ 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.
About
Skill: PCF (Power Apps Component Framework)
When to Use
Trigger when building, debugging, or reviewing PCF controls — TypeScript React components for Model-Driven or Canvas Apps.
Project Structure
MyControl/
├── MyControl/
│ ├── index.ts # Main control class (StandardControl or ReactControl)
│ ├── MyComponent.tsx # React component
│ ├── css/styles.css # Component styles
│ ├── strings/ # Resx localization files
│ └── generated/
│ └── ManifestTypes.d.ts # Auto-generated from manifest
├── ControlManifest.Input.xml # Component manifest (properties, resources)
├── MyControl.pcfproj # Project file
├── package.json
├── tsconfig.json
└── solutions/
└── Solution.cdsproj # Solution project for deployment
Control Types
ReactControl (Virtual — Recommended for New Development)
// No DOM container — React manages rendering
export class MyControl implements ComponentFramework.ReactControl {
private notifyOutputChanged: () => void;
public init(context: ComponentFramework.Context, notifyOutputChanged: () => void): void {
this.notifyOutputChanged = notifyOutputChanged;
}
public updateView(context: ComponentFramework.Context): React.ReactElement {
return React.createElement(MyComponent, {
value: context.parameters.sampleProperty.raw ?? "",
disabled: context.mode.isControlDisabled,
onChange: (val: string) => {
this._value = val;
this.notifyOutputChanged();
},
});
}
public getOutputs(): IOutputs {
return { sampleProperty: this._value };
}
public destroy(): void { /* cleanup */ }
private _value: string = "";
}
StandardControl (Legacy — Has DOM Container)
// Gets a container div for direct DOM manipulation
export class MyControl implements ComponentFramework.StandardControl {
private container: HTMLDivElement;
public init(context, notifyOutputChanged, state, container: HTMLDivElement): void {
this.container = container;
}
public updateView(context): void {
ReactDOM.render(React.createElement(MyComponent, props), this.container);
}
public destroy(): void {
ReactDOM.unmountComponentAtNode(this.container);
}
}
Manifest Patterns
Field Control
> ⚠️ CRITICAL: Virtual controls using React/Fluent MUST declare `` elements. Without these, the PCF bundles its own React — and the platform ALSO provides React — resulting in two React instances. Fluent UI v9 hooks immediately fail with "Invalid hook call… more than one copy of React" and bundle size bloats from ~16 KB to ~2.5 MB.
With platform-library declarations:
- Move
@fluentui/react-components,react, andreact-domtodevDependencies(types only at compile time) bundle.jsdrops from ~2.5 MB → ~16 KB- The platform provides one shared React instance to all virtual controls on the page
Official docs: platform-library element reference — this is Category B, fully documented.
Dataset Control
Dataset Paging Pattern
public updateView(context: ComponentFramework.Context): React.ReactElement {
const dataset = context.parameters.dataSetGrid;
if (dataset.loading) return React.createElement("div", null, "Loading...");
const records = dataset.sortedRecordIds.map((id) => {
const record = dataset.records[id];
return {
id,
name: record.getFormattedValue("name"),
status: record.getFormattedValue("statuscode"),
};
});
const hasMore = dataset.paging.hasNextPage;
const loadMore = () => dataset.paging.loadNextPage();
return React.createElement(MyGrid, { records, hasMore, loadMore });
}
Fluent UI v9 Integration
// PERFORMANCE: Import guidance for Fluent UI v9
// The barrel import tree-shakes correctly with modern bundlers (webpack 5+, esbuild):
import { Button, Input, Label } from "@fluentui/react-components"; // OK with tree-shaking
// For smaller bundles, use individual package imports:
import { Button } from "@fluentui/react-button";
import { Input } from "@fluentui/react-input";
// Check package availability — not all components have separate packages
// Wrap in FluentProvider for consistent theming
public updateView(context): React.ReactElement {
return React.createElement(
FluentProvider,
{ theme: webLightTheme },
React.createElement(MyComponent, props)
);
}
React Platform Library Versions
> Last verified: March 2026. Check Microsoft Learn for current versions.
> IMPORTANT: The PCF platform library does NOT use React 18. The actual versions are:
| Context | React Version | Notes | |---|---|---| | Build (bundled) | 16.14.0 | Version used at compile time | | Model-Driven App runtime | 17.0.2 | Provided by the MDA shell at runtime | | Canvas App runtime | 16.14.0 | Provided by the Canvas runtime |
Do NOT use React 18-specific APIs such as useTransition, useDeferredValue, useSyncExternalStore, or concurrent rendering features — they are not available in the platform library.
Fluent UI v9 Version Constraints
| Context | Fluent UI v9 Version | |---|---| | Build (max) | ≤ 9.46.2 | | Runtime (provided) | 9.68.0 |
Do not install Fluent UI v9 packages above 9.46.2 in your project — the build will succeed but runtime mismatches may cause unexpected behavior.
ReactControl (Virtual — Recommended)
ReactControl (virtual, control-type="virtual") uses the platform-provided React automatically — no manual createRoot needed. The control-type="virtual" attribute is required for React controls. This is the recommended approach for all new controls.
StandardControl (Legacy — Has DOM Container)
For StandardControl using React, use ReactDOM.render / ReactDOM.unmountComponentAtNode (matching the React 16/17 API):
import * as ReactDOM from "react-dom";
export class MyControl implements ComponentFramework.StandardControl {
private container: HTMLDivElement;
public init(context, notifyOutputChanged, state, container: HTMLDivElement): void {
this.container = container;
}
public updateView(context): void {
ReactDOM.render(React.createElement(MyComponent, props), this.container);
}
public destroy(): void {
ReactDOM.unmountComponentAtNode(this.container);
}
}
Recommendation: Always prefer ReactControl (virtual) — it handles React lifecycle correctly and avoids DOM container issues.
Platform Support Limitations
- PCF controls are NOT supported in Power Pages — only Model-Driven Apps and Canvas Apps
- When using platform libraries, do not bundle React or Fluent UI as externals — this causes double-loading
ESLint Setup for PCF
pac pcf push runs npm install internally. Extra ESLint devDependencies cause ERESOLVE failures due to incompatible peer requirements.
Use minimal ESLint only:
// package.json — devDependencies
"eslint": "^9.0.0"
// DO NOT add: typescript-eslint, eslint-plugin-promise, @typescript-eslint/eslint-plugin, eslint-plugin-react
// These have incompatible peer requirements that cause pac pcf push to fail
// eslint.config.mjs — minimal config that satisfies pcf-scripts peer dep
export default [
{ ignores: ["node_modules/**", "out/**", "obj/**", "generated/**"] }
];
No TypeScript-ESLint, no eslint-plugin-react, no globals package needed. pcf-scripts only needs ESLint as a peer dep.
scheduler Dependency
@fluentui/react-context-selector imports scheduler directly. npm may install it nested inside react-dom/node_modules/ rather than hoisted to the project root. If webpack reports "Module not found: Can't resolve 'scheduler'", add it explicitly:
"scheduler": "^0.20.2"
Must be ^0.20.2 — @fluentui/react-components@9.46.2 requires ^0.19.0 || ^0.20.0; version 0.27.x causes peer conflict during pac pcf push.
CLI Workflow
# Create new control
pac pcf init --namespace Contoso --name MyControl --template field --framework react
# Install dependencies
npm install
# Local development with test harness
npm start watch # Opens http://localhost:8181
# Build
npm run build
# Create solution project
pac solution init --publisher-name Contoso --publisher-prefix contoso
pac solution add-reference --path ../MyControl
# Build solution
dotnet build # or msbuild /t:build /restore
# Push to environment (dev only)
pac pcf push --publisher-prefix contoso
Best Practices
- Always implement
destroy()— clean up event listeners, timers, subscriptions - Always handle
context.mode.isControlDisabled— disable inputs when form is read-only - Always handle
context.mode.isVisible— skip rendering when hidden - Diff props in
updateViewbefore re-rendering to avoid flicker - Use
control-type="virtual"for React controls (required for platform library React) - Never call
notifyOutputChanged()insideupdateView()— causes infinite loops - Use
context.webAPIfor Dataverse operations (respects user security context) - Use
context.navigationfor dialog/form opening - Always specify
required-feature-setin manifest when using WebAPI
Debugging
- Test Harness:
npm start watch→http://localhost:8181 - In Environment: Browser DevTools → Sources → webpack:// → find your .tsx files
- Fiddler/DevTools Network: Capture
api/data/v9.2calls for dataset debugging - Check
context.parameters.xxx.errorMessagefor binding errors - Check
context.parameters.xxx.securityfor field-level security restrictions
Canvas App Deployment
PCF controls can be used in Canvas Apps, but require additional manifest configuration.
Enable for Canvas Apps
Canvas App vs Model-Driven App Differences
| Feature | Model-Driven App | Canvas App | |---|---|---| | context.webAPI | Available | Available (if declared in feature-usage) | | context.navigation | Full support | Limited (no openForm) | | context.mode.isControlDisabled | Managed by form | Managed by app maker | | Dataset controls | Full support | Supported (with limitations) | | Theming | Inherits MDA theme | Inherits Canvas theme | | notifyOutputChanged() | Triggers form dirty | Triggers OnChange in Canvas |
Import to Canvas App
Canvas App → Insert → Get more components → Code tab → Import → Select your PCF control
Anti-Patterns
- Calling
notifyOutputChanged()inupdateView()(infinite render loop) - Not implementing
destroy()(memory leaks, stale listeners) - Ignoring
isControlDisabled(control remains editable on read-only forms) - Using
anytypes for context parameters - Hardcoding entity names or field names (use manifest properties)
- Not handling loading state for dataset controls
- Using React 18-specific APIs (
useTransition,useDeferredValue, concurrent features) — platform provides React 16.14.0/17.0.2 - **Missing `
declarations** in virtual control manifest — causes two React instances, "Invalid hook call" error, and ~2.5 MB bundle instead of ~16 KB. Always declare bothReactandFluent` platform libraries for virtual controls using Fluent UI v9.
Sub-Files
- [PCF API Reference](api-reference.md) — Context object properties, context.mode, context.webAPI methods, context.navigation, context.device, context.formatting, DataSet API (columns/filtering/paging/records), EntityRecord methods, custom events, platform availability matrix (MDA vs Canvas vs Portals)
Related Skills
code-apps— React/TypeScript patterns shared with PCF developmentaccessibility-ux— WCAG compliance requirements for PCF controls
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.