| name | pcf-controls |
| description | Build Microsoft Power Apps Component Framework (PCF) code components in TypeScript so an agent can generate a pro-code control that drops into canvas and model-driven apps. Covers ControlManifest.Input.xml, the index.ts lifecycle (init/updateView/getOutputs/destroy), property types, dataset vs field controls, pcfproj/npm build, and solution packaging. |
| metadata | {"author":"AgentX","version":"1.0.0","created":"2026-05-30"} |
| compatibility | {"surfaces":["low-code-builder","agent-x","engineer"],"platforms":["power-platform","power-apps","pcf","typescript"]} |
PCF Code Components
Purpose: emit a TypeScript PCF control that builds with pac pcf + npm and packs into a solution -- the pro-code escape hatch when canvas/model-driven controls are not enough.
When to Use
- A required UX (custom chart, map, rich editor, signature pad) is not expressible with stock controls
- Reusing a JS/TS library inside a Power Apps form or canvas app
- Replacing a single field or a whole dataset (grid) with custom rendering
First decide low-code vs pro-code (see architecture/low-code-vs-pro-code). PCF is pro-code: it carries a build pipeline and maintenance cost.
Project Layout
<control-root>/
<ControlName>/
ControlManifest.Input.xml # manifest: type-group, properties, resources
index.ts # the control class (lifecycle)
generated/ # ManifestTypes.d.ts (pac generates)
package.json # pcf-scripts, pcf-start, build
pcfproj / .pcfproj # MSBuild project
tsconfig.json
Scaffold: pac pcf init --namespace AgentX --name IssueRating --template field (or --template dataset).
ControlManifest.Input.xml
<manifest>
<control namespace="AgentX" constructor="IssueRating" version="1.0.0"
display-name-key="Issue Rating" control-type="standard">
<property name="value" display-name-key="Value" of-type="Whole.None"
usage="bound" required="true" />
<resources>
<code path="index.ts" order="1" />
<css path="css/style.css" order="1" />
</resources>
</control>
</manifest>
usage="bound" ties the property to a column; usage="input" is a config value.
of-type must be a valid PCF type (SingleLine.Text, Whole.None, TwoOptions, OptionSet, DateAndTime.DateOnly, ...).
control-type="standard" (field) vs a data-set element for dataset/grid controls.
index.ts Lifecycle
import { IInputs, IOutputs } from "./generated/ManifestTypes";
export class IssueRating implements ComponentFramework.StandardControl<IInputs, IOutputs> {
private container: HTMLDivElement;
private notifyOutputChanged: () => void;
private value: number;
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement
): void {
this.container = container;
this.notifyOutputChanged = notifyOutputChanged;
}
public updateView(context: ComponentFramework.Context<IInputs>): void {
this.value = context.parameters.value.raw ?? 0;
}
public getOutputs(): IOutputs {
return { value: this.value };
}
public destroy(): void {
}
}
Contract:
updateView re-runs whenever any input, layout, or data changes -- keep it idempotent and cheap.
- Call
notifyOutputChanged() only when an output actually changed; then the platform calls getOutputs().
- Read all data from
context.parameters / context.mode -- never assume DOM state survives between calls.
Build & Package
npm install
npm run build
pac pcf push --publisher-prefix agx
pac solution add-reference --path <control-root>
pac solution pack ...
Anti-Patterns
- Heavy synchronous work in
updateView -- it runs often; debounce and offload.
- Mutating the bound value without
notifyOutputChanged() -- the column never updates.
- Bundling huge libraries -- inflates form load; tree-shake and lazy-load.
- Using PCF for layout a canvas/model-driven app already does -- adds a build pipeline for no benefit.
Related