Field-tested rules for building Power Apps component framework controls that survive contact with real environments β with the exact commands, the repeatable deploy process, and the failures already hit for you.
3 chapters Β· 9 rules
6 reusable commands β fill the placeholders, copy, done. Values you fill are never stored.
The one true starting point β wires manifest, tsconfig, and build together.
pac pcf init --namespace {{namespace}} --name {{component_name}} --template {{template}} --run-npm-install
Follows Rule 1
Instant render loop β the whole dev cycle lives here.
npm start watchFollows Rule 4
The fast inner loop against a real Dataverse β dev environments only.
pac pcf push --publisher-prefix {{publisher_prefix}}
Careful: Creates/updates a temporary unmanaged dev solution β never aim this at a shared or downstream environment.
Follows Rule 7
Produces the managed/unmanaged zips that actually travel between environments.
dotnet build /p:configuration=ReleaseFollows Rule 8
The sanctioned way a component reaches test or prod.
pac solution import --path {{solution_zip_path}} --environment {{environment_url}} --publish-changes
Careful: Aimed at production this changes what users run β deploy windows and rollback zip first.
Follows Rule 9
First move when a build fails on one machine but not another.
pac --version; node --version; npm --versionFollows Rule 2
1 repeatable process β read straight through, or run step by step with a private record of what you did.
The whole journey β from empty folder to a component running in a target environment β with the checks that catch the classic failures before they cost an afternoon.
pac pcf init --namespace {{namespace}} --name {{component_name}} --template {{template}} --run-npm-install
One component per folder. Template is field for single-value controls, dataset for grids.
npm install
EPERM/symlink errors on Windows almost always mean Developer Mode is off β see the attached failure note.
β pac pcf init fails with EPERM symlink on Windows
Error: EPERM: operation not permitted, symlink '...' -> '...'
Likely cause: npm needs to create symlinks; Windows only allows that for unelevated users when Developer Mode is on
Fix: Enable Windows Developer Mode (Settings β System β For developers), or run the terminal elevated once. Then delete node_modules and re-run npm install.
npm start watchExercise every property and both directions of updateView before touching an environment.
Open ControlManifest.Input.xml and increment the patch segment of version. Dataverse caches by version β an unchanged number means an unchanged component, whatever you actually shipped.
pac solution init --publisher-name {{publisher_name}} --publisher-prefix {{publisher_prefix}} pac solution add-reference --path ../{{component_folder}}
add-reference stores a relative path; if you later move folders, the build breaks silently β re-add the reference.
dotnet build /p:configuration=ReleaseRelease configuration produces both unmanaged and managed zips when configured.
pac pcf push --publisher-prefix {{publisher_prefix}}
For your own dev environment, pac pcf push is faster and fine. For anything shared or downstream, only the solution zip travels.
pac solution import --path {{solution_zip_path}} --environment {{environment_url}} --publish-changes
The managed zip goes to test/prod. Publish changes, then open a form that hosts the component.
If behavior is unchanged after import, the version was not bumped (step 4) β see the attached failure note.
β Component unchanged in the environment after a successful import
Import and publish both report success, but the form still runs the old component behavior
Likely cause: ControlManifest version was not bumped β Dataverse caches component resources by version and treats same-version as no-op
Fix: Bump the patch version in ControlManifest.Input.xml, rebuild, re-import, publish all customizations. Hard-refresh the browser (the old bundle may also be client-cached).
2 notes from the field β observations, discoveries, warnings, and lessons.
The test harness renders in a bare page with default styling. Model-driven forms inject their own font stack, spacing, and (increasingly) dark-mode tokens. A control that looks pixel-perfect in the harness can look cramped or unreadable in a real form β final visual sign-off has to happen in an environment.
pac solution add-reference writes a relative path into the .cdsproj. Reorganize your folders β even just renaming the component directory β and the solution build fails with a misleading MSBuild error about a missing project. Nothing warns you at rename time. Re-run add-reference after any move.
3 known failuresβ the approaches that didn't work, preserved so you don't repeat them.
Error: EPERM: operation not permitted, symlink '...' -> '...'
Attempted: Scaffolding a new component on a fresh Windows machine
Context: Fresh Windows 10/11 machine, non-elevated terminal, first ever npm install for a PCF project
Likely cause: npm needs to create symlinks; Windows only allows that for unelevated users when Developer Mode is on
Diagnose: Run "npm install" alone β if it reproduces, it is the OS, not the scaffold
Fix: Enable Windows Developer Mode (Settings β System β For developers), or run the terminal elevated once. Then delete node_modules and re-run npm install.
Affects: Windows 10/11 without Developer Mode
Import and publish both report success, but the form still runs the old component behavior
Attempted: Imported a rebuilt solution over an existing version
Context: Any environment that already had a previous version of the component
Likely cause: ControlManifest version was not bumped β Dataverse caches component resources by version and treats same-version as no-op
Diagnose: Compare the manifest version in your zip against the component version shown in the maker portal
Fix: Bump the patch version in ControlManifest.Input.xml, rebuild, re-import, publish all customizations. Hard-refresh the browser (the old bundle may also be client-cached).
Browser opens localhost:8181 but the page is empty; no control renders
Attempted: Started the harness after edits
Context: Mid-development, usually right after a TypeScript refactor
Likely cause: A compile error occurred but the harness keeps serving the last (broken) bundle β the real error is only in the terminal scrollback
Diagnose: Scroll the npm start terminal up past the webpack summary; the TS error is there
Fix: Fix the TypeScript error shown in the terminal; the harness live-reloads once the build is green.
Everything here was learned on real tenants: the scaffold that works, the version bump everyone forgets, and the deploy pipeline that stops 2am rollbacks. The command cards are parameterized and copy-ready; the deploy recipe runs step by step with the known failures attached where they bite.
Developers building PCF controls for model-driven or canvas apps, and the unlucky person who inherits one.
Initial publication.
Readers of this book also keep