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
- Dataverse Table – I used a custom table called Inspection (you can use any existing/custom table). 
- Two Choice Columns - Inspection Status 
- Inspection Status Reason 
 
- 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
- Create a new BPF with stages that match your Inspection Status values (New, Active, Inactive). 
- Under each stage, add a Data Step and select the Inspection Status Reason column. 
- 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 = Active → Inspection 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
- Add the JS file as a Web Resource in your solution. 
- Open the Inspection table’s Main Form → Form Libraries → Add Library → select your web resource. 
- Add the function on the On Save event (and optionally On Load) via Event Handlers. 
- Save & publish. 
- 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
 
             
             
             
             
             
            