Get a first look at what's coming. The Developer Passport Australia Release Preview kicks off March 12. Dive in! 

PrasadShelar
Giga Guru

Hello Community,

 

We’ve all been there: you make a field mandatory on a form using UI Policy, only to find a week later that someone bypassed it via a List Edit, an Excel Import, or an API integration.

 

In ServiceNow, "Mandatory" doesn't always mean "Mandatory everywhere." To keep your data clean, you need to pick the right tool for the job. Here is a simplified guide to the 5 ways to enforce fields without annoying your users.


1. The "Everywhere" Rule: Dictionary Entry

What it is: A checkbox on the field’s database definition i.e. Dictionary entry.

  • Best for: Fields that must exist for the record to make sense (e.g., "Short Description" on a Task).

  • The Vibe: Permanent and non-negotiable.

  • Watch out: It’s "all or nothing." You can’t make it mandatory only for "In Progress" tickets.

  • Navigate: All > System Definition > Dictionary

    PrasadShelar_0-1771438366596.png

 

2. The "Smart Form" Rule: UI Policies

What it is: A no-code way to make fields mandatory based on conditions (e.g., "If State is Closed, Resolution Code is mandatory").

  • Best for: Guiding users through a form. It shows that helpful red asterisk instantly.

  • The Vibe: User-friendly and visual.

  • Watch out: It only works in the browser. It won't stop a bulk import or a background script.

  • Navigate: All > System UI > UI Policies
    PrasadShelar_2-1771438496115.png

3. The "Power User" Rule: Data Policies

What it is: The "Big Brother" of UI Policies. It enforces rules at the server level.

  • Best for: Blocking bad data from Imports, Web Services, and List Edits.

  • The Vibe: Robust and secure.

  • Pro Tip: Check the "Use as UI Policy" box so it also acts like a UI Policy on your forms. It's the best of both worlds!

  • Navigate: All > System Policy > Rules > Data Policies
    PrasadShelar_4-1771438657045.png

     

4. The "Complex Logic" Rule: Client Scripts

What it is: Custom JavaScript (browser-side) for when the Condition Builder isn't enough.

  • Best for: Validating specific patterns, like ensuring a "Serial Number" starts with "SN-" and has 8 digits.

  • The Vibe: Highly customizable but requires coding.

  • Watch out: Too many scripts can slow down your form load times.

  • Navigate: System Definition > Client Scripts
    PrasadShelar_5-1771438758286.png

     

5. The "Last Line of Defense": Business Rules

What it is: Server-side scripts that run right before a record hits the database.

  • Best for: Cross-table validation (e.g., "Don't allow closing this Change if the linked CI is currently 'Down'").

  • The Vibe: The ultimate gatekeeper.

  • Watch out: Users don't see the error until after they hit Save, which can be frustrating.

  • Navigate: System Definition > Business Rules
    PrasadShelar_7-1771438869116.png

     


💡Quick Cheat Sheet: Which one do I choose?

If you want to...Use this...
Always require a valueDictionary
Require it conditionally on a form                              . UI Policy
Block bad imports/API callsData Policy
Validate a Regex patternClient Script
Check other tables before savingBusiness Rule

Summary

Don't rely on just one method. For critical data, use Defense in Depth:

  1. Use a UI Policy so the user knows what to do.

  2. Use a Data Policy to catch the people trying to sneak around the UI!



What’s your favorite "Gotcha" when it comes to mandatory fields? Let’s discuss in the comments! 👇

 

If this helped you in any way, don't forget to hit the like button! 👍

Comments
LearnUseThrive
Mega Sage

We don't allow list editing except for a few power users.

MatiusR
Kilo Contributor

Good breakdown. The real issue is relying only on UI Policy in ServiceNow.

If the data matters, always pair UI Policy with Data Policy. That’s what actually stops imports, APIs, and list edits.

UI guides the user, Data Policy enforces it.

Daniel Oderbolz
Mega Sage

This is one of the most annoying aspects of ServiceNow in my opinion. 

In fact it is quite a bit worse than you describe @PrasadShelar . A dictionary entry has exactly the same "level of enforcement" as a UI Policy (this is completely non-intuitive).

(Also consider this discussion of the issue)

Just do a simple check: how many records in your instance contain empty values in fields that are defined as mandatory in the dictionary?

In a classical RDBMS, you would find 0 such records because the system enforces this using a CHECK CONSTRAINT (on the lowest possible level - no other way to circumvent it rather than dropping the CONSTRAINT and adding it - this BTW will fail if an record violates the CONSTRAINT).

 

If you dare, here is a script to do this check on a subprod instance (this script is able to sample to reduce load) :

 

/**
 * Because mandatory fields are not enforced in all cases, we want to find out how many records have NULL values in mandatory fields.
 *
 * DD.MM.YYYY - Author - description	
 * 17.09.2025 - DOD - Created (Sidekick: Github Copilot)
 *
 * @function scanForNullsInMandatoryFields
 * @param {integer} nTables - number of tables to sample
 * @param {boolean} boolRandomSampling - if true, random sampling is used, otherwise the first nTables are used
 * @returns {void} 
 * @see https://www.servicenow.com/community/admin-experience-forum/let-s-play-a-little-game-what-will-this-code-do/td-p/3381537
 * @author Daniel C. Oderbolz (use at your own risk)
 */

function scanForNullsInMandatoryFields(nTables, boolRandomSampling) {

    try {

        if (typeof nTables === 'undefined' || nTables === null) {
            nTables = 10; // Default to 10 tables
        }
        if (typeof boolRandomSampling === 'undefined' || boolRandomSampling === null) {
            boolRandomSampling = true; // Default to random sampling
        }

        // First find out which tables have mandatory fields
        var arrTablesWithMandatory = [];
        var arrTablesWithNulls = [];

        var ga = new GlideAggregate('sys_dictionary');
        ga.addAggregate('COUNT');
        // We filter out the thousands of var__ tables and syslog tables
        ga.addEncodedQuery('mandatory=true^nameNOT LIKEvar__^nameNOT LIKEsyslog');
        ga.groupBy('name');
        ga.addHaving('COUNT', '>', 0);
        ga.query();

        while (ga.next()) {
            arrTablesWithMandatory.push(ga.getValue('name'));
        }

        gs.log("Tables with mandatory fields: " + arrTablesWithMandatory.length);

        if (nTables > arrTablesWithMandatory.length) {
            gs.log("Requested number of tables to sample (" + nTables + ") exceeds the number of tables with mandatory fields (" + arrTablesWithMandatory.length + "). Sampling all available tables.");
            nTables = arrTablesWithMandatory.length;
        }

        gs.log("Sampling " + nTables + " tables..." + (boolRandomSampling ? " (random sampling)" : " (first n tables)"));

        var arrSampledTables = [];


        // Create an array of nTables random table names in arrSampledTables
        for (var i = 0; i < nTables; i++) {

            if (boolRandomSampling) {
                var nRandomIndex = Math.floor(Math.random() * arrSampledTables.length);

                // Ensure we do not sample the same table twice
                while (arrSampledTables.indexOf(arrTablesWithMandatory[nRandomIndex]) !== -1) {
                    nRandomIndex = Math.floor(Math.random() * arrTablesWithMandatory.length);
                }

                arrSampledTables.push(arrTablesWithMandatory[nRandomIndex]);
            } else {
                arrSampledTables.push(arrTablesWithMandatory[i]);
            }

        }

        // Loop through the sampled tables and check for NULLS in mandatory fields
        for (var j = 0; j < arrSampledTables.length; j++) {
            var strTableName = arrSampledTables[j];
            gs.log("Checking table: " + strTableName);

            // Get the mandatory fields for the table
            var arrMandatoryFields = [];
            var grDict = new GlideRecord('sys_dictionary');
            grDict.addEncodedQuery('name=' + strTableName + '^mandatory=true^internal_type!=collection');
            grDict.query();
            while (grDict.next()) {
                arrMandatoryFields.push(grDict.getValue('element'));
            }

            // Check for NULLS in mandatory fields
            var grTable = new GlideRecord(strTableName);
            var strNullQuery = '';
            for (var k = 0; k < arrMandatoryFields.length; k++) {
                if (k > 0) {
                    strNullQuery += '^NQ';
                }
                strNullQuery += arrMandatoryFields[k] + 'ISEMPTY';
            }

            if (strNullQuery != '') {
                if (grTable.isValidEncodedQuery(strNullQuery)) {
                    grTable.addEncodedQuery(strNullQuery);
                    grTable.setLimit(1); // We only need to know if there is at least one record
                    grTable.query();
                    if (grTable.next()) {
                        gs.log("Table " + strTableName + " has NULLS in at least one of those mandatory fields: " + arrMandatoryFields.join(', '));
                        arrTablesWithNulls.push(strTableName);
                    }
                }
            }
        }

        // Final report
        gs.log("Sampled " + arrSampledTables.length + " tables.");
        gs.log(arrTablesWithNulls.length + " tables have NULLS in mandatory fields: " + arrTablesWithNulls.join('\n'));



    } catch (error) {
        gs.logError("Error in script :", error);
        gs.log(strTableName + " - " + strNullQuery);
    }
}

// Test Run (100 Samples, random sampling)
scanForNullsInMandatoryFields(100, true);

// Time the real run
var sw = new GlideStopWatch();
scanForNullsInMandatoryFields(5052 , false);
gs.log("Finished in: " + sw.getTime() + " ms");

 

This means:

  • Never assume in your code that "mandatory" fields have data in them. You need to guard against empty fields using gs.nil() or GlideElement.nil()
  • If you want to be (almost 🙄) sure, work with Data Policies as mentioned by @MatiusR 
Version history
Last update:
3 weeks ago
Updated by:
Contributors