5 min readRishi

Building Your First PCF Control for Power Apps

Sometimes a standard Power Apps control is almost right but not quite production-grade for the experience you need. You need richer rendering, tighter interaction, or a reusable component that behaves consistently across apps. That is where the PowerApps Component Framework earns its place.

PCF is for controls that deserve engineering discipline

Use PCF when configuration is no longer enough. The PowerApps Component Framework lets you build custom controls using TypeScript, a manifest, and platform lifecycle methods. The control can run in model-driven apps and canvas apps, depending on how you define and package it.

PCF is not the first answer for every UI problem. Try modern controls, canvas components, custom pages, command bar customization, and low-code configuration first. Reach for PCF when the behavior must be reusable, tested, performant, and closer to web component engineering than formula authoring.

Examples include masked inputs, timeline visualizations, compact status indicators, custom map interactions, advanced sliders, rating controls, and controls that integrate with approved JavaScript libraries. The value is not just prettier UI. The value is a governed control that ships through solutions and can be reused by multiple makers.

The tooling setup is small but specific

Install the prerequisites before generating files. You need Node.js, npm, the Power Platform CLI, and an environment where you can authenticate with pac auth. Use a supported Node version for the current PCF tooling and keep it aligned with your organization’s build agents.

node --version
npm --version
pac --version
pac auth create --url https://contoso-dev.crm.dynamics.com

Create a workspace folder under your solution source tree, then initialize the control. The namespace and name become part of the generated structure, so choose names you can live with in source control and solution components.

pac pcf init --namespace AugmentedDev --name StatusBadge --template field
npm install
npm run build

The generated project includes ControlManifest.Input.xml, index.ts, styling, generated type definitions, and build configuration. Most first controls only need serious edits in the manifest, index.ts, and CSS.

The manifest is the contract with Power Apps

Treat the manifest as the public API. It declares the control namespace, version, display names, bound properties, resources, feature usage, and supported platform behavior. If you bind to a Dataverse column, the manifest describes what type of value the framework passes into your control.

<manifest>
  <control namespace="AugmentedDev" constructor="StatusBadge" version="1.0.0" display-name-key="StatusBadge" description-key="Shows a colored status badge" control-type="standard">
    <property name="statusText" display-name-key="Status" description-key="Current status text" of-type="SingleLine.Text" usage="bound" required="true" />
    <resources>
      <code path="index.ts" order="1" />
      <css path="css/StatusBadge.css" order="1" />
    </resources>
  </control>
</manifest>

Keep the first version boring. One bound text property, one visual output, no external service call, no ambitious rendering library. Once the lifecycle is familiar, the next control can be more interesting.

The TypeScript lifecycle is where the control behaves

Understand the four core methods. init runs once when the control is created. updateView runs when context changes and should render the current state. getOutputs returns values for output properties when your control changes data. destroy cleans up event handlers, timers, subscriptions, and DOM references.

import { IInputs, IOutputs } from "./generated/ManifestTypes";

export class StatusBadge implements ComponentFramework.StandardControl<IInputs, IOutputs> {
  private container!: HTMLDivElement;

  public init(
    context: ComponentFramework.Context<IInputs>,
    notifyOutputChanged: () => void,
    state: ComponentFramework.Dictionary,
    container: HTMLDivElement
  ): void {
    this.container = container;
    this.container.className = "status-badge";
  }

  public updateView(context: ComponentFramework.Context<IInputs>): void {
    const value = context.parameters.statusText.raw ?? "Unknown";
    this.container.textContent = value;
    this.container.dataset.status = value.toLowerCase().replace(/\s+/g, "-");
  }

  public getOutputs(): IOutputs {
    return {};
  }

  public destroy(): void {
    this.container.replaceChildren();
  }
}

Do not do expensive work in every updateView call. The framework can call it frequently. Cache DOM references, diff meaningful values, and clean up after yourself.

Local testing catches control mistakes before solution packaging

Use the test harness for fast feedback. The generated project can run locally and display the control with configurable inputs. It is not a perfect simulation of model-driven or canvas runtime behavior, but it catches rendering errors, basic property handling, and TypeScript issues quickly.

npm start
npm run build

After local validation, push to a development environment for real app testing. pac pcf push is convenient during early development because it creates a temporary solution behind the scenes.

pac auth select --index 1
pac pcf push --publisher-prefix adv

Use this for inner-loop testing, not as your final ALM path. Production-bound controls should be packaged in a proper solution and promoted through managed solution deployment.

Solutions make the control usable by makers

Package the control like any other application component. Create or use an existing solution, add the PCF project reference, build, and import the solution into the target environment. In model-driven apps, makers can add the control to a column on a form. In canvas apps, the control can be enabled and inserted where supported.

ArtifactPurposeSenior engineer check
ManifestDefines properties and resourcesNames, types, versioning are stable
index.tsImplements lifecycle behaviorNo leaks, no repeated heavy rendering
CSSStyles the rendered controlScoped enough to avoid app-wide impact
SolutionALM containerManaged for test and production
PublisherPrefix and ownershipMatches team convention

The first PCF control should teach the lifecycle, not prove every JavaScript skill you have. Keep the contract tight, render predictably, package through a solution, and you will have a control that feels native instead of a one-off script hiding inside Power Apps.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

No comments yet. Be the first.