- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
3 weeks ago - edited 3 weeks ago
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
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
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
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
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
💡Quick Cheat Sheet: Which one do I choose?
| If you want to... | Use this... |
| Always require a value | Dictionary |
| Require it conditionally on a form . | UI Policy |
| Block bad imports/API calls | Data Policy |
| Validate a Regex pattern | Client Script |
| Check other tables before saving | Business Rule |
Summary
Don't rely on just one method. For critical data, use Defense in Depth:
Use a UI Policy so the user knows what to do.
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! 👍
- 749 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
We don't allow list editing except for a few power users.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
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

