Forms are one of the most powerful tools you can add to an object page. Instead of relying on users to interpret and enter data manually, a well-built form guides them through a structured set of questions and does the work for them. In this post, we are going to build a Priority Assessment form for the Change Request object that scores five criteria and automatically assigns a priority band the moment a user makes a selection.
To do that, we need to start in the embedded builder.
If you've only worked in Cirrus Builder, the embedded builder might feel familiar at first glance, but it's a different tool. Rather than living in your schema configuration, it runs inside a Form object instance at runtime. You build directly inside the form, laying out widgets and writing interactions without touching anything outside of it.
The Change Request form on the Change Request page.
Before we start building, there are two things you need to know upfront.
First, once you link a form to an object record and save it, it locks. You can't edit it after that point, so build it, test it, and make sure it's working exactly the way you want before you link it to anything.
Second, the form by itself does not hold onto data. When a user fills it in, their answers sit temporarily in the Embedded Builder Page (EBP) component on the parent Change Request page. Persisting that data to the actual record is handled on the object side, which is what Part 2 covers.
The Scenario
Your IT Change Management team is tired of manually deciding how urgently a change request needs to be prioritized. Two people looking at the same request can come to completely different conclusions about whether it is Low priority or Critical, and that inconsistency is causing real problems: under-resourced critical changes, over-escalated minor ones, and a review process that relies too much on whoever happens to be in the room.
The solution is a standardized Priority Assessment that lives directly on the Change Request page. When a requestor raises a change, they answer five structured questions. The form adds up their answers automatically and assigns a priority label: Low, Medium, High, or Critical.
By the end of this post, you will have a fully working form with five scoring dropdowns, an automatic total score, a color-coded result, and an interaction that does all the calculation the moment a user makes a selection.
What We're Building
To get this form working, there are five things we need to do:
Let's work through each one.
Step 1: Creating the Form Instance
Go to your Forms object manager table and create a new Form instance.
You need three things:
Save the draft.
Adding Text Labels
Scroll down until you find the embedded form builder. This is where you design the layout.
This form will have a 2-column layout: column 1 is for the Rich text labels and column 2 will include the dropdowns.
In both columns, there will be 4 main sections: Impact, Urgency, Risk, and Priority Score.
For this form, we will be using these components:
Section 1: IMPACT (column 1)
IMPACT
Business Impact
How broadly does this change affect business operations?
Repeat these steps for the following sections:
| Section | Component | ID | Text Content |
| IMPACT | Rich Text | Text_Impact |
IMPACT Business Impact
How broadly does this change affect business operations? |
| IMPACT | Rich Text | Text_Systems |
Systems Affected
How many systems or integrations are involved? |
| URGENCY | Rich Text | Text_Time |
URGENCY
Time Sensitivity
How time-critical is this change?
|
| URGENCY | Rich Text | Text_Compliance |
Regulatory or Compliance Driver
Is this change required to meet a regulatory deadline? |
| RISK | Rich Text | Text_Risk |
RISK
Implementation Risk
How likely is this change to cause disruption if it fails?
|
| PRIORITY SCORE | Rich Text | Text_Score | PRIORITY SCORE |
You can also add the subtext as Help text. You can do so in the Properties menu of the rich text component.
Click Save.
After adding each component, your builder panel should like something like this:
The next part is adding in the dropdown menus and their values.
Step 2: Adding Dropdowns with Values
Since we are expecting an output score based on the dropdown selections, we will need to assign number values to each dropdown option.
After adding each dropdown, click Save.
| ID | Label |
Option 1 Label |
Option 1 Value |
Option 2 Label |
Option 2 Value |
| dd_businessImpact | Business Impact | 1 - Minimal | 1 | 5 - Enterprise | 5 |
| dd_systemsAffected | Systems Affected | 1 - Isolated | 1 | 5 - Cross-System | 5 |
| dd_timeSensitivity | Time Sensitivity | 1 - Flexible | 1 | 5 - Immediate | 5 |
| dd_complianceDriver | Compliance Driver | 1 - None | 1 | 5 - Hard Deadline | 5 |
| dd_implementationRisk | Implementation Risk | 1 - Low | 1 | 5 - High | 5 |
Click Save.
Your form should look something like this:
Step 3: Output Components
The last piece of the front end puzzle is adding the output components to the form. One is for the number score and the other is the label associated with the total score.
Priority Score
Add an Input Number component in the same row as the Priority Score rich text.
ID: priorityScore
Label: Priority Score
Check the box next to ‘Display this input number as read-only text’.
Click Save.
Result Label
Add a Color Dropdown component under the Input Score dropdown.
ID: labelResult
Label: Result
Check the box next to ‘Display this dropdown as read-only text’.
Add four dropdown options:
| Label | Value | Color Hex Code |
| Low | Low | #7AF787 |
| Medium | Medium | #F7D27A |
| High | High | #FF9999 |
| Critical | Critical | #EB502A |
Click Save.
Now, that we have the front end components, the next part is adding functionality to these dropdowns and output labels. This is achieved using Interactions.
Step 4: Create the Interaction
Click the business impact dropdown component.
From the right-hand menu, select Interactions.
For the OnChange item action, create a new interaction called calculateScore. This means every time a user changes any answer, the interaction runs immediately and recalculates the result. The user sees a live score as they work through the form.
Here is the full interaction, broken into four parts. Each part does a specific job, and understanding what each function does and why will help you adapt this pattern to other forms. We will be using the Script Editor to create the interaction steps.
Part 1: Read Each Dropdown and Convert It to a Number
Before the interaction can do any math, it needs to read each dropdown and make sure the value is actually a number. This part handles both of those things: it reads the value, checks whether the user has selected anything yet, and converts the result to a number if they have.
local.isImpactNull = CirrusLibGeneral.isNullOrUndefined(local.rawImpact);
if (local.isImpactNull) {
local.rawImpact = 0;
} else {
local.rawImpact = CirrusLibMath.toNumber(local.rawImpact);
}
Repeat this block for all five dropdowns, changing the variable names each time: rawSystems, rawTime, rawCompliance, rawRisk.
Remember to click Save after each line of code written.
What each function does:
data.dd_businessImpact reads the current value of that dropdown. Inside the embedded builder, data. is how you access your own widget values. This is different from field. , which is used to read fields on the parent object page.
CirrusLibGeneral.isNullOrUndefined(value) checks whether the value is null or undefined. In plain terms, it asks: has the user selected anything yet? This function returns true if nothing has been selected, and false if a value exists.
Why do you need this check?
If a user opens the form and changes only one dropdown, the other four have not been answered yet. Their values are null. If you try to run the math on a null value, the calculation breaks entirely. The null check lets you default unanswered questions to 0, so the sum still works correctly as the user works through the form.
CirrusLibMath.toNumber(value) converts the dropdown value into an actual number. Even though you configured the option values as 1 and 5, the embedded builder passes them back as strings. "1" and "5" are not numeric values. This is a subtle but critical distinction. If you try to add strings here, it concatenates them instead of summing them. For example, five answers of "1" would give you "11111", not 5. That result passes every threshold check and always shows Critical. Converting with toNumber turns "1" into 1 so the arithmetic works as expected.
Part 2: Sum the Scores
Once each dropdown value has been read and converted, this part collects all five numbers and adds them up into a single total score.
local.scoreArr = CirrusLibArray.createNewNumericArray();
CirrusLibArray.push(local.scoreArr, local.rawImpact);
CirrusLibArray.push(local.scoreArr, local.rawSystems);
CirrusLibArray.push(local.scoreArr, local.rawTime);
CirrusLibArray.push(local.scoreArr, local.rawCompliance);
CirrusLibArray.push(local.scoreArr, local.rawRisk);
local.getFinalScore = CirrusLibMath.sum(local.scoreArr);
What each function does:
CirrusLibArray.createNewNumericArray() creates a new, empty array that is typed for numbers. You need this because CirrusLibMath.sum only accepts an array as its input. It cannot take individual numbers directly. Think of it like a bag: you cannot hand it five separate items, you have to put them all in the bag first and hand in the bag.
You can find this information by hovering over the function in the Script Editor.
CirrusLibArray.push(array, value) adds a value to the end of an array. You call this once per converted score to load all five values into the array before summing.
CirrusLibMath.sum(numbers) takes a numeric array and returns the total of all values in it. The result is stored in local.getFinalScore, which you will use in the next part.
Part 3: Determine the Priority Band
Now that we have a total score, this part decides what that score means. It works by setting "Low" as the default and then checking upward through each band, overwriting the result if the score qualifies.
local.Result = "Low";
local.isMedium = CirrusLibMath.ge(local.getFinalScore, 9);
if (local.isMedium) {
local.Result = "Medium";
}
local.isHigh = CirrusLibMath.ge(local.getFinalScore, 16);
if (local.isHigh) {
local.Result = "High";
}
local.isCritical = CirrusLibMath.ge(local.getFinalScore, 21);
if (local.isCritical) {
local.Result = "Critical";
}
What each function does:
CirrusLibMath.ge(number1, number2) means greater than or equal to. It returns true if number1 is greater than or equal to number2. Using strictly greater than (gt) would mean a score of exactly 9 stays Low instead of becoming Medium, because the boundary score would fall through. ge makes sure the boundary is included in the correct band.
Why start with "Low" and overwrite upward? You set "Low" as the default first. Each check then overwrites it only if the score qualifies for a higher band. By the end of the three checks, local.Result holds the highest band the score reached. The checks run sequentially rather than as if/else chains, which means a score of 22 passes through all three checks and correctly ends up at Critical.
Part 4: Write the Results to the Output Widgets
data.priorityScore = local.getFinalScore;
data.labelResult = local.Result;
The same data. scope you used to read values is used to write them back. Setting data.priorityScore updates the Input Number widget with the total score. Setting data.Result sets the Color Dropdown to the matching option, which is why the option values had to match the values exactly.
Close the Interactions Editor.
Important: Change the OnChange item action for each dropdown to the calculateScore interaction we just finished creating.
Save the form.
Step 5: Test Before You Link and Save
Before saving this form to any Change Request object, test the calculation inside the object page.
Open the out of the box Change Request page and create a new instance.
From the Approval Checklist selector, change the form to the IT Change Priority Assessment form.
If everything works as you'd expect, the form is now ready to be used as an object field.
Part 2 of this series covers how to wire this form into the Change Request object page so that when a requestor fills it in, the score persists on the record and can drive further behavior, such as auto-populating a Priority field or triggering a workflow.
Interaction Functions Used in This Post
For reference, here is a summary of every library function used in this tutorial and what it does:
| Function | Library | What It Does |
| isNullOrUndefined(value) | CirrusLibGeneral | Returns true if the value is null or undefined; false otherwise |
| toNumber(value) | CirrusLibMath | Converts a value (such as a string "1") to a numeric data type |
| createNewNumericArray() | CirrusLibArray | Creates a new, empty array typed for numeric values |
| push(array, value) | CirrusLibArray | Adds a value to the end of an existing array |
| sum(numbers) | CirrusLibMath | Returns the sum of all numbers in a numeric array |
| ge(number1, number2) | CirrusLibMath | Returns true if number1 is greater than or equal to number2 |
You can download the full interaction and Form used in this post by clicking here.
How to Use:
Next up: Part 2 - Using the Form in the Change Request Object Page. We will cover how to add the Embedded Builder Page component to the page, load the form, capture the score that the form sends up via triggerParent, and persist it back to a field on the Change Request record.
For more articles on SAS Model Risk Management, click here.
Find more articles from SAS Global Enablement and Learning here.
Dive into keynotes, announcements and breakthroughs on demand.
Explore Now →The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.