Introduction
Power Apps, a cornerstone of Microsoft's Power Platform, enables rapid low-code app development, but for expert needs like high-performance custom UIs or complex interactions, PCF controls (Power Apps Component Framework) are essential. In 2026, with the rise of AI and massive datasets, PCF elevates low-code by providing full control via TypeScript while natively integrating with Power Fx formulas and data sources like Dataverse or SharePoint.
This expert tutorial guides you step-by-step to develop a responsive custom PCF slider control, integrate it into a canvas app with advanced formulas managing delegation and collections, and deploy to production. You'll learn to sidestep performance traps (like unnecessary re-renders) and scale for enterprise setups. At the end, your Power Apps will match native code quality, supercharging productivity and ROI. (142 words)
Prerequisites
- Node.js 20+ and npm installed (check with
node -v). - Power Apps CLI (PAC CLI) version 1.14+.
- Visual Studio Code with Power Platform Tools and TypeScript extensions.
- Power Apps account with Premium license (for Dataverse and PCF).
- Dedicated Power Apps environment (created via make.powerapps.com).
- Advanced knowledge of TypeScript, Power Fx (delegation, variables), and Dataverse.
Install Power Apps CLI
npm install -g @microsoft/powerapps-cli
pac auth create --environment-id YOUR_ENVIRONMENT_ID
pac pcf init --namespace MonControle --name SliderCustom --template field --description "Slider PCF personnalisé"This command globally installs the PAC CLI, authenticates with your Power Apps environment (replace YOUR_ENVIRONMENT_ID with the ID from make.powerapps.com > Environments), and initializes a 'field' type PCF project for reusable form controls. The 'field' template is perfect for custom inputs, avoiding the complexities of 'dataset' templates.
PCF Project Structure
Once initialized, the project includes ControlManifest.Input.xml, index.ts, bundle.d.ts, and tsconfig.json. Open /SliderCustom in VS Code. We'll customize the slider to support dynamic tooltips, real-time validation, and Dataverse bindings, optimized for mobile and desktop.
Implement the TypeScript Control
import {IInputs, IOutputs} from './generated/ManifestTypes';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Slider from './Slider';
export class SliderCustom implements ComponentFramework.StandardControl<IInputs, IOutputs> {
private _container: HTMLDivElement;
private _notifyOutputChanged: () => void;
private _value: number;
private _min: number = 0;
private _max: number = 100;
private _context: ComponentFramework.Context<IInputs>;
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: any, container: HTMLDivElement): void {
this._container = container;
this._notifyOutputChanged = notifyOutputChanged;
this._context = context;
this._value = context.parameters.sampleProperty.raw || 50;
this._min = context.parameters.minValue.raw || 0;
this._max = context.parameters.maxValue.raw || 100;
ReactDOM.render(React.createElement(Slider, {
value: this._value,
min: this._min,
max: this._max,
onChange: (val: number) => this.onValueChange(val)
}), this._container);
}
public onValueChange(value: number): void {
this._value = value;
this._notifyOutputChanged();
}
public getOutputs(): IOutputs {
return {
sampleProperty: this._value
};
}
public updateView(context: ComponentFramework.Context<IInputs>): void {
this._min = context.parameters.minValue.raw || 0;
this._max = context.parameters.maxValue.raw || 100;
// Re-render logic
ReactDOM.unmountComponentAtNode(this._container);
ReactDOM.render(React.createElement(Slider, {
value: this._value,
min: this._min,
max: this._max,
onChange: (val: number) => this.onValueChange(val)
}), this._container);
}
public destroy(): void {
ReactDOM.unmountComponentAtNode(this._container);
}
}
// Composant React Slider (simplifié)
const Slider: React.FC<{value: number, min: number, max: number, onChange: (val: number) => void}> = ({value, min, max, onChange}) => {
return (
<input
type="range"
min={min}
max={max}
value={value}
onChange={(e) => onChange(parseInt(e.target.value))}
style={{width: '100%', height: '8px', borderRadius: '4px'}}
title={`Valeur: ${value}`}
/>
);
};This complete TypeScript code implements a reactive PCF control with React, handling input/output bindings (sampleProperty for value, custom minValue/maxValue). The updateView avoids full re-renders by remounting only when params change, optimizing performance on large datasets. Pitfall avoided: always include destroy() for memory cleanup.
Configure the XML Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<control namespace="MonControle" constructor="SliderCustom" version="0.0.1" display-name-key="Slider_Custom" description-key="Slider PCF personnalisé" control-type="standard" preview-image="/img/Slider.png">
<data-set name="sampleDataset" display-name-key="Sample_Data_Set" />
<property name="sampleProperty" display-name-key="Sample_Property" description-key="Sample Property" of-type="SingleLine.Text" usage="bound" required="true" />
<property name="minValue" display-name-key="Min_Value" description-key="Valeur minimale" of-type="Whole.Number" default-value="0" />
<property name="maxValue" display-name-key="Max_Value" description-key="Valeur maximale" of-type="Whole.Number" default-value="100" />
<resources>
<code path="index.ts" order="1"/>
<css path="css/SliderCustom.css" order="1" />
<resx path="strings/SliderCustom.1033.resx" version="1.0.0" />
</resources>
<feature-usage>
<uses-feature name="BoardingRequired" required="true" />
</feature-usage>
</control>
</manifest>The manifest defines bound properties (sampleProperty linked to a Dataverse field) and unbound ones (min/max), with dataset support for future extensions. Add
Build and Test the PCF
Run npm run build to compile the bundle. Then pac pcf push --publisher-prefix monprefix to deploy to your dev environment. Create an unmanaged solution via make.powerapps.com, add the PCF to a Dataverse form (e.g., 'Account' table, custom Decimal field). Test in play mode.
Build and Push via CLI
cd SliderCustom
npm install
npm run build
pac solution init --publisher-name "Mon Org" --publisher-prefix monorg
pac solution add-reference --path .\Components\MonControle
msbuild /p:configuration=Release
pac solution export --path ./output/SliderCustom.zip
pac pcf push --publisher-prefix monorgThis script builds the PCF, initializes a solution, references the component, exports to ZIP, and pushes directly. Use msbuild for Release builds (production-optimized). Pitfall: publisher-prefix must match your org; otherwise, 401 auth error.
Integrate into an Advanced Canvas App
Create a blank canvas app on the Dataverse 'Accounts' table. Add a Form, insert the PCF Slider on a custom 'Score' field. Use Power Fx for expert logic: filtered collections, delegation over 2000 records, contextual variables.
Power Fx Formula with Delegation
ClearCollect(colAccountsFiltered, Filter(Accounts, 'Created On' >= DateAdd(Today(), -30), Score >= SliderCustom1.Value));
Set(varTotalScore, Sum(colAccountsFiltered, Score));
Set(varDelegationWarning, If(CountRows(colAccountsFiltered) > 2000, "⚠️ Délégation limitée - Ajoutez index sur 'Created On'", "OK"));
UpdateContext({locSliderMin: 0, locSliderMax: 100});This OnVisible formula loads a delegable filtered collection (Filter on indexed fields), computes an aggregate sum, and handles delegation warnings. Set/UpdateContext binds to the PCF (SliderCustom1.Value). Pitfall: without Dataverse indexes, Filter isn't delegable → loads everything, crashing performance.
Slider OnChange Formula with Validation
If(SliderCustom1.Value < 50,
UpdateContext({locStatus: "Bas"});
Patch(Accounts, LookUp(Accounts, AccountId = GUID(Parent.AccountId)), {Score: SliderCustom1.Value}),
UpdateContext({locStatus: "Haut"});
Patch(Accounts, LookUp(Accounts, AccountId = GUID(Parent.AccountId)), {Score: SliderCustom1.Value})
);
Refresh(Accounts);
Notify("Score mis à jour: " & SliderCustom1.Value, NotificationType.Success);Slider OnChange: conditional status update, delegable Patch on parent record (via GUID LookUp), data source Refresh. Notify for better UX. Pitfall: Use GUID() for primary key; otherwise, Patch fails silently.
Production Deployment
Export the solution ZIP (PCF + app) and import to prod env via make.powerapps.com > Solutions. Activate the PCF in 'Manage Components'. Share the app with Azure AD roles. For CI/CD, integrate with Azure DevOps using PAC CLI tasks.
Best Practices
- Performance: Limit PCF re-renders with shouldComponentUpdate; test Power Fx delegation via Monitor.
- Security: Validate PCF inputs client/server-side; use bound properties for Dataverse Row-Level Security.
- Responsive: Use context.mode to detect canvas/model-driven and adjust CSS.
- Versioning: Increment manifest version; use ALM solutions for managed/unmanaged.
- Testing: Add Jest unit tests to PCF; use boarding for F5 dev testing.
Common Errors to Avoid
- Ignored Delegation: Filter/Sum on non-delegable fields (long text) → timeouts; index Dataverse columns.
- PAC CLI Auth: Run
pac auth clearon env switch; always use precise--environment-id. - Infinite PCF Re-renders: Forgetting notifyOutputChanged → stack overflow; call only on real changes.
- Non-Delegable Patch: On local collections → sync issues; prioritize native data sources.
Next Steps
Dive deeper with the official PCF docs, integrate Power Automate for post-Patch workflows, or build dataset PCF for custom grids. Check our Learni Power Platform training for PL-400 certification. Explore Fluent UI React for enterprise PCF designs.