If you are new to Power Apps component framework , please refer to the PCF Beginner Guide and PCF React Control blog posts.

In this article I will be covering following topics related to PCF aka Code Component

  • Create a PCF control from scratch
  • Using Dataverse Web API to retrieve records.
  • Packaging PCF control into a solution
  • Deploying PCF control to an Environment
  • Creating a Custom Page with PCF control
  • Test the control

Lets get started with the steps.

Create a PCF Project:

  • Create a new PCF project by triggering pac pcf init command.
pac pcf init -ns pcfdv -n pcfdvcrud -t field -npm
  • You will see the following folders created with the pac pcf init command.
  • Next, create a new folder named Helper and add a new TypeScript file called DataverseHelper.ts. This file will be used to handle the Dataverse retrieval operations.
  • In this example, I will query the Account table by filtering based on AccountNumber and Name.
  • Add the following fetchAccountRecord function to the DataverseHelper.ts file.
export const fetchAccountRecord = async (
    webAPI: ComponentFramework.WebApi,
    accountName: string,
    accountNumber: string
): Promise<{ name: string; accountnumber: string; accountid: string } | null> => {
    console.log(`Fetching Account record for Name: ${accountName}, Account Number: ${accountNumber}`);

    try {
        // Construct the OData filter query
        const filterQuery = `?$filter=name eq '${accountName}' and accountnumber eq '${accountNumber}'`;
        
        console.log("filter query : ",filterQuery);

        // Fetch the data from Dataverse
        const response = await webAPI.retrieveMultipleRecords("account", filterQuery);

        if (response.entities.length > 0) {
            const account = response.entities[0]; // Retrieve the first matching record
            console.log("Matching Account record found:", account);

            // Return the mapped result
            return {
                name: account.name,
                accountnumber: account.accountnumber,
                accountid: account.accountid,
            };
        } else {
            console.log("No matching Account record found.");
            return null; // No record found
        }
    } catch (error) {
        console.error("Error fetching Account record:", error);
        throw new Error("Failed to fetch Account record. See console for details.");
    }
};

  • The fetch logic is ready. Next, we need to reference the fetchAccountRecord function in the index.ts file.
  • In the index.ts file, import the fetchAccountRecord function using the following syntax:
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { fetchAccountRecord } from "./Helper/DataverseHelper";
  • Next, we need to create the UI elements, including two textboxes for capturing the user’s filters for Account Name and Account Number, and a button to execute the fetchAccountRecord function.
  • Here is the complete code of index.ts file.
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { fetchAccountRecord } from "./Helper/DataverseHelper";

export class pcfdvcrud implements ComponentFramework.StandardControl<IInputs, IOutputs> {
    private container: HTMLDivElement;
    private accountNameInput: HTMLInputElement;
    private accountNumberInput: HTMLInputElement;
    private fetchAccountButton: HTMLButtonElement;
    private resultContainer: HTMLDivElement;
    private context: ComponentFramework.Context<IInputs>;

    /**
     * Empty constructor.
     */
    constructor() {}

    /**
     * Initializes the control instance.
     */
    public init(
        context: ComponentFramework.Context<IInputs>,
        notifyOutputChanged: () => void,
        state: ComponentFramework.Dictionary,
        container: HTMLDivElement
    ): void {
        console.log("Initializing PCF control for Account CRUD operations.");
        this.context = context;
        this.container = container;

        // Initialize UI components
        this.initializeUIComponents();
    }

    /**
     * Called when any value in the property bag has changed.
     */
    public updateView(context: ComponentFramework.Context<IInputs>): void {}

    /**
     * Returns object[s] for property marked as "bound" or "output."
     */
    public getOutputs(): IOutputs {
        return {};
    }

    /**
     * Called when the control is to be removed from the DOM tree.
     */
    public destroy(): void {}

    /**
     * Initializes the UI components for the control.
     */
    private initializeUIComponents(): void {
        // Create input field for Account Name
        this.accountNameInput = this.createInputField("Enter Account Name");
        this.container.appendChild(this.accountNameInput);

        // Create input field for Account Number
        this.accountNumberInput = this.createInputField("Enter Account Number");
        this.container.appendChild(this.accountNumberInput);

        // Create a button for fetching account
        this.fetchAccountButton = this.createButton("Fetch Account", this.handleFetchAccount.bind(this));
        this.container.appendChild(this.fetchAccountButton);

        // Result container
        this.resultContainer = document.createElement("div");
        this.resultContainer.style.marginTop = "10px";
        this.container.appendChild(this.resultContainer);
    }

    /**
     * Creates an input field with the specified placeholder.
     * @param placeholder - The placeholder text for the input field.
     * @returns The created HTMLInputElement.
     */
    private createInputField(placeholder: string): HTMLInputElement {
        const input = document.createElement("input");
        input.type = "text";
        input.placeholder = placeholder;
        input.style.marginBottom = "10px";
        input.style.display = "block";
        return input;
    }

    /**
     * Creates a button with the specified label and click handler.
     * @param label - The text to display on the button.
     * @param onClickHandler - The function to call when the button is clicked.
     * @returns The created HTMLButtonElement.
     */
    private createButton(label: string, onClickHandler: () => void): HTMLButtonElement {
        const button = document.createElement("button");
        button.innerText = label;
        button.onclick = onClickHandler;
        button.style.marginBottom = "10px";
        console.log(`Button "${label}" initialized.`);
        return button;
    }

    /**
     * Handles fetching of the Account record based on user input.
     */
    private async handleFetchAccount(): Promise<void> {
        const accountName = this.accountNameInput.value.trim();
        const accountNumber = this.accountNumberInput.value.trim();

        // Validate inputs
        if (!accountName || !accountNumber) {
            this.resultContainer.innerHTML = "<p>Please provide both Account Name and Account Number.</p>";
            console.error("Validation failed: Account Name or Account Number is missing.");
            return;
        }

        console.log(`Fetching Account record for Name: ${accountName}, Number: ${accountNumber}...`);

        try {
            const result = await fetchAccountRecord(this.context.webAPI, accountName, accountNumber);

            if (result) {
                this.resultContainer.innerHTML = `
                    <h3>Account Found:</h3>
                    <p><strong>Name:</strong> ${result.name}</p>
                    <p><strong>Account Number:</strong> ${result.accountnumber}</p>
                    <p><strong>ID:</strong> ${result.accountid}</p>
                `;
                console.log("Account fetched successfully:", result);
            } else {
                this.resultContainer.innerHTML = "<p>No matching account found.</p>";
                console.log("No matching account found.");
            }
        } catch (error) {
            console.error("Error fetching account:", error);
            this.resultContainer.innerHTML = `<p>Error: ${error instanceof Error ? error.message : "Unknown error"}</p>`;
        }
    }
}

  • At this point, you can build the project using the npm run build command and open the test harness with the npm start watch command. However, please note that Dataverse Web API operations cannot be tested in the test harness.
  • Let’s package the PCF control and import it into the Dataverse environment to test the fetch operation.

Packaging PCF control into a solution:

  • Create a new folder named DVSolution, which will be used for PCF control packaging as a solution.
  • Point the Terminal to DVSolution folder, as shown below.
  • Run the pac solution init command to create a shell (empty) solution folder, which includes a file with the .cdsproj extension.
pac solution init -pn "rajeev" -pp "raj" -o "D:\Practice\PCFDVCRUDOperations\pcfdvcrud\DVSolution"
  • Open the .cdsproj file and make the following change. This setting will generate both Managed and Unmanaged solutions for deploying the PCF component.
  <PropertyGroup>
    <SolutionPackageType>Both</SolutionPackageType>
    <SolutionPackageEnableLocalization>false</SolutionPackageEnableLocalization>
  </PropertyGroup>
pac solution add-reference -p "D:\Practice\PCFDVCRUDOperations\PCFDVCRUDOperations.pcfproj"
  • The PCF packaging into the solution is complete. Run the dotnet build command, which generates both Managed and Unmanaged solutions under the bin\Debug folder.

  • The solution is ready. Now, we need to import it into the desired environment.

Deploying PCF control to an Environment:

  • Open the PowerApps maker portal, select the desired environment, and import the solution.
  • Wait for the solution to import successfully.
  • With this, we have successfully built and deployed the PCF project. Now, it’s time to host the PCF control and test it.

Creating a Custom Page with PCF control:

For this blog, I am using a Custom Page to host the PCF control.

  • Create or open an unmanaged solution. From the menu, choose New > App > Page
  • In the Custom Page, add the PCF control as shown below.
  • Next add the PCF control (i.e., Code Component) to the screen.
  • We can’t directly test the PCF control in a Custom Page. To test it, we need to add the Custom Page to a Model-Driven App.
  • From the solution, choose New > App > Model-driven app
  • Provide a Name to the App and click Create.
  • Choose Add Page > Custom Page
  • Select the Custom Page we created in previous step. Click Add
  • Custom Page looks as below
  • Play the Model-Driven App. Enter the Name and Account Number, then click on Fetch Account. You should see the matched record.

Hope you now understand the basics of packaging a PCF control and hosting it on a Custom Page.

🙂

Advertisements
Advertisements

3 responses to “[Step by Step] Beginner : Create a PCF control and add it to a custom page”

  1. [Code Snippet] PCF | Calling Dataverse ‘Custom API’ | Rajeev Pentyala - Microsoft Power Platform Avatar

    […] To understand PCF Controls, refer to this article: [Step by Step] Beginner : Create a PCF control  […]

  2. Mak Avatar
    Mak

    In my case I am getting Null for ComponentFramework.Context.userSettings.userId what could be reason?

  3. Pradeep Avatar
    Pradeep

    why we can’t test directly in custom page ? In order to test directly in custom page what we need to do

Leave a reply to Mak Cancel reply