Model-driven Apps | Dynamically Updating Dropdown Values with Custom JavaScript in BPFs

Recently, I was working on a project where I came across an interesting requirement:

👉 The allowed dropdown values of a field needed to change dynamically depending on the active Business Process Flow (BPF) stage.

Sounds simple, right? But here’s the catch — this isn’t possible out of the box.

While business rules in Dataverse can control field updates, errors, visibility, or editability, they can’t control which values are available in a choice/dropdown field.

So, here’s how I approached the problem 👇

🎯 The Requirement (Example)

  • Process Stage: BPF Stage 1 → Allowed Values: Value A, Value B

  • Process Stage: BPF Stage 2 → Allowed Values: Value C, Value D

Since no OOTB feature could achieve this, I explored Power Platform’s flexibility and ended up solving it with a custom JavaScript web resource attached to the form.

If you’re facing something similar, here’s how you can do it step by step.

🛠️ Pre-requisites

  1. Dataverse Table – I used a custom table called Inspection (you can use any existing/custom table).

  2. Two Choice Columns

    • Inspection Status

    • Inspection Status Reason

  3. Business Process Flow (BPF) – Stages should align with Inspection Status values, with a data step under each stage pointing to Inspection Status Reason.

⚙️ Choice Values Setup

We’ll use the following example:

Inspection Status: New; Inspection Status Reason: Unassigned, Assigned

Inspection Status: Active; Inspection Status Reason: Scheduled, In Progress, On Hold

Inspection Status: Inactive; Inspection Status Reason: Passed, Failed, Cancelled

🏗️ Creating the Business Process Flow

  1. Create a new BPF with stages that match your Inspection Status values (New, Active, Inactive).

  2. Under each stage, add a Data Step and select the Inspection Status Reason column.

  3. Save and activate the BPF.

🔨 Adding the JavaScript Logic

We’ll now write a JavaScript function that does two things:

1. Automatically set the Inspection Status field

  • Detect the active BPF stage.

  • Map the stage to the correct Inspection Status value.

  • Update the Inspection Status field automatically.

Example:
If the stage = ActiveInspection Status is automatically set to 2.

2. Filter the Inspection Status Reason values

  • Grab the Inspection Status Reason dropdown (from the BPF header).

  • Clear all existing options.

  • Add back only the allowed options for the current stage.

Example:
If the stage = Active → the user will only see:

  • Scheduled

  • In Progress

  • On Hold

This prevents users from selecting invalid options and keeps the form aligned with the BPF.

⚡ Making the Logic Run Automatically

We’ll add a second function to ensure the logic runs:

  • Immediately when the form loads.

  • Every time the BPF stage changes.

function applyStageBasedStatusLogic(executionContext) {
  const formContext = executionContext.getFormContext();
  console.log('Running applyStageBasedStatusLogic');

  const { process } = formContext.data;
  if (!process) {
    console.log('No process found on form.');
    return;
  }

  const activeStage = process.getActiveStage();
  if (!activeStage) {
    console.log('No active BPF stage.');
    return;
  }

  const stageName = activeStage.getName();
  console.log('Active stage name:', stageName);

  // --- 1. Auto-update Inspection Status field ---
  const statusField = formContext.getAttribute('crbd7_inspectionstatus');
  if (statusField) {
    // Map stage names to option values of crbd7_inspectionstatus field
    const statusOptionMap = {
      New: 1,
      Active: 2,
      Inactive: 3,
    };

    const newStatusValue = statusOptionMap[stageName];

    if (newStatusValue !== undefined && statusField.getValue() !== newStatusValue) {
      console.log('Updating status to:', newStatusValue);
      statusField.setValue(newStatusValue);
      statusField.setSubmitMode('always'); // Ensure it's saved
    }
  } else {
    console.log('Status field not found.');
  }

  // --- 2. Filter Inspection Status Reason in BPF Data Step ---
  const reasonField =formContext.getControl('header_process_crbd7_inspectionstatusreason');

  if (!reasonField) {
    console.log('Status Reason control not found in BPF.');
    return;
  }

  reasonField.clearOptions();

  // Define available options for each stage
  const reasonOptionMap = {
    New: [
      { text: 'Unassigned', value: 1 },
      { text: 'Assigned', value: 2 },
    ],
    Active: [
      { text: 'Scheduled', value: 3 },
      { text: 'In Progress', value: 4 },
      { text: 'On Hold', value: 5 },
    ],
    Inactive: [
      { text: 'Passed', value: 6 },
      { text: 'Failed', value: 7 },
	  { text: 'Cancelled', value: 8 },
    ],
  };

  const allowedReasons = reasonOptionMap[stageName];

  if (allowedReasons && allowedReasons.length > 0) {
    allowedReasons.forEach((opt) => {
      reasonField.addOption(opt);
      console.log('Added reason option:', opt.text);
    });
  } else {
    console.log('No matching reason options found for stage:', stageName);
  }

  console.log('Finished applyStageBasedStatusLogic');
}

function registerStageChangeHandler(executionContext) {
  const formContext = executionContext.getFormContext();
  const { process } = formContext.data;

  if (!process) {
    console.log('Process control not found.');
    return;
  }

  // Run logic immediately on load
  applyStageBasedStatusLogic(executionContext);

  // Run logic every time stage changes
  process.addOnStageChange(() => {
    console.log('Stage changed, reapplying logic.');
    applyStageBasedStatusLogic(executionContext);
  });
}

✅ In Summary

  • The script syncs the Inspection Status field with the active BPF stage.

  • It filters the Inspection Status Reason dropdown so only valid options appear.

  • This ensures clean data entry and a smooth user experience in model-driven apps.

👩‍💻 Adding the Script to Your Solution

  1. Add the JS file as a Web Resource in your solution.

  2. Open the Inspection table’s Main Form → Form LibrariesAdd Library → select your web resource.

  3. Add the function on the On Save event (and optionally On Load) via Event Handlers.

  4. Save & publish.

  5. Open a record → try switching BPF stages → watch the Inspection Status Reason dropdown update dynamically 🎉

🚀 Wrap Up

With just a little bit of JavaScript, we’ve extended the power of model-driven apps beyond what’s available OOTB. This pattern can be reused across other tables and scenarios where you need dynamic dropdown values tied to process stages.

Stage: New

Stage: Active

Stage: Inactive

Next
Next

GPT-4o powered AI summaries: Boosting efficiency in Model-Driven Apps