Skip to content

PCF Controls

Peter McDonald edited this page Aug 12, 2026 · 3 revisions

PCF Controls

Scaffold, build, test and deploy a Power Apps component framework control — including debugging the deployed control on a live form with hot reload, so you edit your code and watch the real form update.

Before you start

You need Why
pac (Power Platform CLI) and Node.js Scaffolding, building and pushing go through pac and the project's local npm tooling (Requirements)
A connected environment For Push, Add to Solution and live-form debugging
Microsoft Edge Only for Debug on live form — the extension drives it to reach the deployed control

1. Create the control

Initialise Project → PCF Control, or + Add Component → PCF Control in an existing repo. Two questions decide the scaffold, and they map straight onto pac pcf init:

Question Options What it means
Template Field / Dataset A field control binds to one column; a dataset control binds to a grid or view
Framework None / React Plain DOM rendering, or React (pac pcf init --framework react)

The result is a standard pac pcf init layout — worth knowing, because the manifest lives one level down: ControlManifest.Input.xml sits inside a <Constructor>/ folder, and the project root (with package.json and the .pcfproj) is its parent. Every command resolves the root from the manifest, so the control works whichever of its folders you are in.

2. The card's buttons

Button What it does
Push to {environment} Builds and pushes the control straight into the environment — the quickest loop while developing
Local Build Builds the bundle only; no environment involved
Run harness (hot reload) The PCF test harness in a browser, rebuilding as you edit — no Dataverse needed
Debug on live form (hot) Serves your local bundle to the deployed control on a real form (below)
Refresh Types Regenerates the typings for your manifest's properties after you edit the manifest
Add to Solution Adds the control to your solution so it ships with it
Add service-layer structure (overflow) Adds a service/state scaffold plus snippets, so logic does not accumulate in the control class
Stop live-form debug (overflow) Ends the live-form session and stops its watcher

3. Two ways to test: harness or live form

Run harness (hot reload) is the fast one. The harness renders your control in a browser with mock data and rebuilds on save. Nothing is deployed and no environment is touched — use it for layout, rendering and property behaviour.

Debug on live form (hot) is the honest one. Your control runs in the real app, on a real form, with real data and real context — but the bundle it loads is the one on your machine, so a save rebuilds and the form picks it up. Use it when the harness cannot reproduce something: form context, related records, security, or an interaction with another control.

The control has to be deployed once first (Push, then put it on a form), because the extension intercepts the request for that deployed bundle and answers with your local build.

A model-driven app serves web resources through a service worker, which caches them — so intercepting at page level alone is not enough. The live-form debugger bypasses the service worker, which is what makes the swap actually take effect.

Run Stop live-form debug when you are done: it ends the session and tree-kills the file watcher, so no webpack --watch process is left running.

4. Ship it

  1. Add to Solution — the control becomes part of your solution.
  2. Export/import that solution as usual, or keep a Solutions component in the same repo and pack/deploy from there.

Troubleshooting

"Couldn't find a ControlManifest.Input.xml" — the command ran outside a PCF component. Scaffold one, or select the PCF card in the panel first.

Types do not match the manifest — run Refresh Types after editing ControlManifest.Input.xml.

The live form does not pick up my changes — check that Stop live-form debug has not already been run, that the control really is on the form you are looking at, and that you are looking at the browser the extension launched (it uses its own Edge profile).

A build fails with "'webpack' is not recognized" — the toolchain is a project-local dependency, not a global one. Let the component finish its install/restore (the extension runs it when scaffolding).

See also

Clone this wiki locally