Multi-select new list action in change request list
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday - last edited yesterday
Implement a multi-select List Action on the Change Request list to allow users to mass-cancel Change Requests that were automatically created from Standard Change templates.
It must be available only for change manager role.
The action must:
Allow selecting multiple Change Requests.
Prompt the user for a comment (mandatory).
Set the state to Cancelled and record the comment in the Work Notes or Closure Notes.
Be reusable for future similar needs.
This prevents abandoned Change Requests when the wrong Standard Change is opened.
The requirements are:
When triggered:
- User sees a prompt asking for a mandatory comment.
- Selected Change Requests change to state Cancelled.
- The comment is written into work notes or closure notes.
Only visible to appropriate roles (change managers / fulfiller roles).
No impact on existing Standard Change workflows.
What would be the ideal way to create this? How would you do it?
Ui Action? Ui builder?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
where you want this? Native or Workspace?
what did you start with and where are you stuck?
💡 If my response helped, please mark it as correct ✅ and close the thread 🔒— this helps future readers find the solution faster! 🙏
Ankur
✨ Certified Technical Architect || ✨ 9x ServiceNow MVP || ✨ ServiceNow Community Leader
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
Native.
Ive created a UI action and a Script include.
Ui action
function onClick() {
// Get selected sys_ids from the list (works in List v2/v3)
var listObj;
try {
listObj = (typeof g_list !== 'undefined') ? g_list : GlideList2.get(g_list.getListID());
} catch (e) {
listObj = GlideList2.get(g_list.getListID());
}
var sysIds = listObj.getChecked(); // comma-separated sys_ids
if (!sysIds) {
gsftInform('Please select one or more Change Requests to cancel.');
return;
}
// Open a modal UI Page to capture the mandatory comment
var dlg = new GlideModal('mass_cancel_comment_ui'); // UI Page name (create in step 2)
dlg.setTitle('Cancel selected Standard Changes');
dlg.setPreference('sys_ids', sysIds);
dlg.setPreference('table', 'change_request');
dlg.render();
}
Script include
var MassChangeActions = Class.create();
MassChangeActions.prototype = {
initialize: function() {},
// === Mass cancel for Change Requests ===
cancelChanges: function() {
var result = {
updated: 0,
skipped: [],
errors: []
};
// Role guard: only change managers
if (!gs.hasRole('change_manager')) {
result.errors.push('Insufficient privileges: change_manager role required.');
return JSON.stringify(result);
}
var sysIdsStr = (this.getParameter('sysparm_sys_ids') || '').trim();
var comment = (this.getParameter('sysparm_comment') || '').trim();
var requireStandard = (this.getParameter('sysparm_require_standard') || 'false') === 'true';
if (!sysIdsStr) {
result.errors.push('No records selected.');
return JSON.stringify(result);
}
if (!comment) {
result.errors.push('Comment is mandatory.');
return JSON.stringify(result);
}
// Resolve "Canceled" internal value for change_request.state, by label
var canceledValue = this._getChoiceValue('change_request', 'state', 'Canceled');
if (!canceledValue) {
result.errors.push('Could not resolve the "Canceled" state choice on change_request.state.');
return JSON.stringify(result);
}
// Batch query for performance
var sysIds = sysIdsStr.split(',').map(function(x) {
return x.trim();
}).filter(Boolean);
var gr = new GlideRecord('change_request');
gr.addQuery('sys_id', 'IN', sysIds);
if (requireStandard) {
// Default filter: type == Standard
// Change this if your instance uses a different field to mark "Standard template origin"
gr.addQuery('type', 'standard');
}
gr.query();
while (gr.next()) {
try {
// Skip if already canceled
if (gr.getValue('state') == canceledValue) {
result.skipped.push(gr.getUniqueValue());
continue;
}
// Journal entry + closure notes
gr.work_notes = 'Mass-canceled by ' + gs.getUserDisplayName() + ' — ' + comment;
try {
gr.setValue('close_notes', comment);
} catch (e) {}
// Set state
gr.setValue('state', canceledValue);
gr.update();
result.updated++;
} catch (ex) {
result.errors.push('Error on ' + gr.getDisplayValue() + ': ' + ex.message);
}
}
// Identify selected records that were filtered out by requireStandard
if (requireStandard) {
var touched = result.updated + result.skipped.length;
var selectedCount = sysIds.length;
var notFoundCount = selectedCount - touched;
if (notFoundCount > 0) {
result.errors.push(notFoundCount + ' selected records were not Standard Changes (and were ignored).');
}
}
return JSON.stringify(result);
},
// === Generic mass state changer (reusable for other tables) ===
genericMassStateChange: function() {
var result = {
updated: 0,
skipped: [],
errors: []
};
if (!gs.hasRole('change_manager')) {
result.errors.push('Insufficient privileges: change_manager role required.');
return JSON.stringify(result);
}
var tableName = (this.getParameter('sysparm_table') || '').trim();
var sysIdsStr = (this.getParameter('sysparm_sys_ids') || '').trim();
var comment = (this.getParameter('sysparm_comment') || '').trim();
var targetLabel = (this.getParameter('sysparm_target_state_label') || '').trim();
var extraQuery = (this.getParameter('sysparm_extra_query') || '').trim(); // encoded query, optional
if (!tableName || !sysIdsStr || !targetLabel) {
result.errors.push('Missing required parameters (table, sys_ids, target_state_label).');
return JSON.stringify(result);
}
if (!comment) {
result.errors.push('Comment is mandatory.');
return JSON.stringify(result);
}
var targetValue = this._getChoiceValue(tableName, 'state', targetLabel);
if (!targetValue) {
result.errors.push('Could not resolve target state "' + targetLabel + '" for ' + tableName + '.');
return JSON.stringify(result);
}
var sysIds = sysIdsStr.split(',').map(function(x) {
return x.trim();
}).filter(Boolean);
var gr = new GlideRecord(tableName);
gr.addQuery('sys_id', 'IN', sysIds);
if (extraQuery) gr.addEncodedQuery(extraQuery);
gr.query();
while (gr.next()) {
try {
if (gr.getValue('state') == targetValue) {
result.skipped.push(gr.getUniqueValue());
continue;
}
// Journal + closure notes (if field exists)
try {
gr.work_notes = '[Mass change] ' + comment;
} catch (e) {}
try {
gr.setValue('close_notes', comment);
} catch (e) {}
gr.setValue('state', targetValue);
gr.update();
result.updated++;
} catch (ex) {
result.errors.push('Error on ' + gr.getDisplayValue() + ': ' + ex.message);
}
}
return JSON.stringify(result);
},
// Helper: fetch internal choice value by label
_getChoiceValue: function(table, element, label) {
var ch = new GlideRecord('sys_choice');
ch.addQuery('name', table);
ch.addQuery('element', element);
ch.addQuery('label', label);
ch.addQuery('language', gs.getSession().getLanguage());
ch.setLimit(1);
ch.query();
if (ch.next()) return String(ch.getValue('value'));
// Fallback any language
var ch2 = new GlideRecord('sys_choice');
ch2.addQuery('name', table);
ch2.addQuery('element', element);
ch2.addQuery('label', label);
ch2.setLimit(1);
ch2.query();
if (ch2.next()) return String(ch2.getValue('value'));
return null;
},
type: 'MassChangeActions'
};
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
so what's not working?
what debugging did you do?
Ankur
✨ Certified Technical Architect || ✨ 9x ServiceNow MVP || ✨ ServiceNow Community Leader
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
Hi @NMMZ10
We faced something similar in a previous implementation for Incident Management.
Before you start, there are a few things you need to consider:
-
A UI Action or List Action would be the best option to implement this.
-
You need to build a clear process for what should happen when a change is moved to the Canceled state.
-
If there are multiple changes in a list view, how will the user select the required change?
-
Should this apply to all change records, or only specific ones?
If my response proves useful, please indicate its helpfulness by selecting " Accept as Solution" and " Helpful." This action benefits both the community and me.
Regards
Dr. Atul G. - Learn N Grow Together
ServiceNow Techno - Functional Trainer
LinkedIn: https://www.linkedin.com/in/dratulgrover
YouTube: https://www.youtube.com/@LearnNGrowTogetherwithAtulG
Topmate: https://topmate.io/dratulgrover [ Connect for 1-1 Session]
****************************************************************************************************************
