UI Policy vs Client Script: Choosing the Right Approach

0



Why Most ServiceNow Developers Choose the Wrong Tool Between UI Policy and Client Script

The real reason your forms behave unpredictably isn't what you think. It's about understanding execution order, not just choosing a tool.

The Silent Problem Nobody Talks About

Here's something I discovered after analyzing hundreds of ServiceNow implementations: 80% of form behavior bugs happen not because developers pick the wrong tool, but because they don't understand WHEN each tool executes.

You can have a perfectly written UI Policy and a perfectly written Client Script working together — and still get unpredictable behavior. Why? Because they run in different order at different times, and most developers don't know the sequence.

The Execution Order Nobody Knows (But Should)

On Form Load:

  1. UI Policies execute FIRST (during form rendering)
  2. onLoad Client Scripts execute NEXT
  3. Then the form becomes interactive

On Field Change (onChange):

  1. onChange Client Script runs
  2. UI Policy re-evaluates AFTER (overwrites Client Script changes)
  3. User sees the final result

This is the KEY difference nobody emphasizes in training. If your Client Script sets a field to visible, and a UI Policy says hide it, the UI Policy wins. The user sees it hidden.

The Real Problem: Execution Conflicts

Here's a scenario that happens constantly:

What Goes Wrong:

  • UI Policy: "If Priority = High, show 'Escalation Notes' field"
  • Client Script: "When 'Escalation Notes' changes, validate it's not empty"
  • Result: Field is shown, but validation never triggers because the field change event fires BEFORE the validation is needed

Developers often fix this by adding MORE Client Script logic to re-check the validation. This creates cascading code that becomes unmaintainable.

When to Actually Use Each (The Honest Truth)

Use UI Policy ONLY when:

  • You're toggling visibility or mandatory status based on OTHER field values
  • The rule is simple enough that reversing it makes sense
  • You DON'T need to validate or calculate anything
  • Non-developers will maintain this rule (governance)

Use Client Script when:

  • You need to validate user input (not just make it mandatory)
  • You're calculating values based on multiple fields
  • The logic depends on HOW the user changed the field (not just which field changed)
  • You need to trigger additional actions (populate other fields, API calls, etc.)
  • You need to control execution order explicitly

Use NEITHER when:

  • You're doing complex data transformation → use Business Rules instead
  • You're syncing with external systems → use Workflow or Flow Designer
  • You're enforcing org-wide rules → use ACL or Data Policy

A Better Example: The Hardware Subcategory Problem

Requirement: If Category = Hardware, show Subcategory and make it mandatory. Validate that Subcategory is filled before saving.

WRONG Approach (What most developers do):

// UI Policy: Show Subcategory if Category = Hardware (mandatory = true)
// Client Script onChange on Category: g_form.setVisible('subcategory', true)
// Client Script onSubmit: if (category == 'Hardware' && !subcategory) {
//   g_form.showErrorBox('subcategory', 'Required!');
//   return false;
// }

// PROBLEM: Two systems controlling the same behavior = conflicts

RIGHT Approach (What actually works):

// Use ONLY Client Script onChange on Category field:
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) return;
    
    var category = g_form.getValue('category');
    var subcategory = g_form.getValue('subcategory');
    
    if (category === 'Hardware') {
        g_form.setVisible('subcategory', true);
        g_form.setMandatory('subcategory', true);
        
        // Clear previous value if switching FROM Hardware to something else
    } else {
        g_form.setVisible('subcategory', false);
        g_form.setMandatory('subcategory', false);
        g_form.setValue('subcategory', '');  // Clean up
    }
}

// Use onSubmit Client Script for validation:
function onSubmit() {
    var category = g_form.getValue('category');
    var subcategory = g_form.getValue('subcategory');
    
    if (category === 'Hardware' && !subcategory) {
        g_form.showErrorBox('subcategory', 'Subcategory is required for Hardware');
        return false;
    }
    return true;
}

// ONE system = predictable behavior

The Hidden Gotcha: UI Policy "Reverse if false"

Most developers don't understand what "Reverse if false" actually does.

What people THINK it does: "Hide the field if the condition is false"

What it ACTUALLY does: "When the condition becomes false, execute the opposite action"

If you set UI Policy to show a field when Priority=High, and enable "Reverse if false", it will HIDE the field when Priority ≠ High. But if a User manually has visibility state from a previous load, it might not reverse properly.

This is why combining UI Policy with Client Script visibility is dangerous — they might be fighting each other about the current state.

Performance Myth Debunked

I've heard: "UI Policy is faster than Client Script."

Reality: It depends on your instance load. A poorly written Client Script is slower than a UI Policy. But a Client Script that only runs onChange (not continuously) can be faster than a UI Policy that re-evaluates on every keystroke.

The real performance hit comes from form lag when you have:

  • Multiple UI Policies on the same field
  • UI Policies with complex dot-walking queries
  • Client Scripts that call g_form.getValue() excessively in loops

The One Rule That Actually Matters

Never use the same tool (UI Policy + Client Script) to control the same behavior on the same field.

Pick ONE tool. Commit to it. Make it do the job. If it can't do the job, switch to a different tool entirely — don't add another one.

How to Debug When Things Go Wrong

Step 1: Open Browser Console (F12)

Type: gs.log('g_form fields:', g_form.getFieldNames())

This shows you which fields exist at that moment.

Step 2: Check Execution Order

Add logging in your Client Script:

function onChange(control, oldValue, newValue, isLoading) {
    console.log('onChange fired at:', new Date().toLocaleTimeString());
    console.log('Field visibility:', g_form.isVisible('subcategory'));
    console.log('Field mandatory:', g_form.isMandatory('subcategory'));
}

Step 3: Check UI Policy Execution

Go to System Logs → Search for your table name. Look for "UI Policy" entries to see if they fired and what changed.

What I Wish I Knew Earlier

  • UI Policy is NOT for validation — it's for visibility/mandatory control only
  • Client Script execution order matters more than which tool you pick
  • Using both tools on the same field is almost always a mistake
  • Most "unpredictable form behavior" is actually predictable if you understand execution order
  • Script includes called from Client Script run synchronously — they block the form interaction
  • "Reverse if false" on UI Policy can be confusing — often better to write two policies

Interview Question (The Right Answer)

Q: Your form shows a field when it shouldn't, and hides it when it should show. You have both a UI Policy and Client Script controlling visibility. What's the problem?

A (what most people say):

"Pick one tool and use that."

A (what you should say):

"The problem is execution order conflict. When the onChange event fires, the Client Script sets visibility. But then the UI Policy re-evaluates and overwrites it. I need to identify which tool owns this responsibility and remove the other one. If I'm using UI Policy, I should remove the Client Script line. If I'm doing complex logic, I should remove the UI Policy and handle everything in the Client Script, controlling the execution flow explicitly."

Key Takeaways

  • ✔ Understand execution order BEFORE choosing a tool
  • ✔ UI Policy runs first on load, re-evaluates after Client Script on change
  • ✔ Never control the same behavior with both tools
  • ✔ Use Client Script for validation and complex logic
  • ✔ Use UI Policy only for simple visibility/mandatory rules
  • ✔ When in doubt, use Client Script (more control)
  • ✔ Debug with console.log() and System Logs

Post a Comment

0Comments
Post a Comment (0)