Useful Scripts
In my years as a ServiceNow developer, I’ve collected a large number of abstract, reusable scripts for various purposes. This practice has saved me more time and headaches than almost anything else I’ve done as a developer, because just about every interesting problem I’ve solved with code, I have inevitably had to solve twice.
On this page, you’ll find a few of the scripts I’ve saved over the years, and maybe a few scripts submitted by our most clever and dashing readers. The scripts you’ll find here, are one-off blocks of code that solve one problem as efficiently as they can. You won’t find detailed deep-dive explanations here, like you would in my articles on the blog; just examples, prototypes, and quick fixes to common coding conundrums. You also won’t find any of the scripts that I’ve already written articles with detailed explanations on, such as the Journal Redactor.
Most of these scripts will be specific to ServiceNow, but not all. Some are pure-JS. Some solve problems in ES5 that could easily be solved if we had ES6 on the server. Some aren’t even clever, but I use them so frequently that I figured I’d write them down.
This page is always under construction, so keep an eye on this page as I expand upon this repository. If you’d like to contribute your own scripts (and of course, be credited), please send me an email with your script, what it does, what problem it solves, and any other info you think I ought to know about it. Don’t forget to also let me know how you’d like to be credited (your name, and if you’d like, your website that I’ll link to).
Index
Retrieve Decrypted Field Value from Password2 Field
Add One Object to Another Object or Array
Get Catalog Item Variables (Organized Into Containers)
Get URI Parameters (Portal & Classic UI)
Check if a Specific User Has Access to Something
Check if a Given String Contains Any of a List of Values
Query a Base Table - Get a Record/Fields on an Extended Table
Use Different Credentials/Mid-servers/Endpoints if Instance is Prod Vs. Sub-Prod
Retrieve the Body of a "text/plain" (or Other Unsupported) REST Message
Format Object Output Using JSON.stringify() For Prettier Logs
Get User's Date/Time Format and Validate Date/Time Field in Client
Add Variables to Catalog Task From RITM Workflow Script
Generate sys_history_set records from sys_audit data, after a clone, rebuild, or audit mod.
Get Latest Journal Entry as HTML Without Header Info
Populate an "Image" Field From a Script (Default)
Display a Form in a Dialog Window (and Set Some Values)
Show an Expanding Info Message with Functional Elements
Get and Parse ServiceNow Journal Entries as Text/HTML
Check if One Table is in the Hierarchy of Another (SCOPED)
Scripts
Retrieve Decrypted Field Value from Password2 Field
/* The following example retrieves and prints the value of a password2 from a basic auth profile, with the sys_id: "26da5b40db66e380eeb32ad94b96193e". Be careful with this! Passwords are obviously SENSITIVE! */ var encryptedPassword, decryptedPassword, Encrypter = new GlideEncrypter(), grBasicAuthProfile = new GlideRecord('sys_auth_profile_basic'); grBasicAuthProfile.get('26da5b40db66e380eeb32ad94b96193e'); encryptedPassword = grBasicAuthProfile.getValue('password'); decryptedPassword = Encrypter.decrypt(encryptedPassword); gs.print(decryptedPassword); /*****Client-callable example*****/ /*****GLIDEAJAX SCRIPT INCLUDE*****/ var PasswordDecryptor = Class.create(); PasswordDecryptor.prototype = Object.extendsObject(AbstractAjaxProcessor, { getPasswordValue: function() { if (!gs.hasRoleExactly('admin')) { return; } var decryptedPassword; var tableName = this.getParameter('sysparm_table'); var recordID = this.getParameter('sysparm_record_id'); var fieldName = this.getParameter('sysparm_field_name'); var geDecryptor = new GlideEncrypter(); var grCredential = new GlideRecord(tableName); grCredential.get(recordID); decryptedPassword = geDecryptor.decrypt( grCredential.getValue(fieldName) ); return decryptedPassword; } }); /*****CLIENT SCRIPT*****/ function onLoad() { if (g_user.hasRoleExactly('admin')) { var gaDecryptor = new GlideAjax('PasswordDecryptor'); gaDecryptor.addParam('sysparm_name', 'getPasswordValue'); gaDecryptor.addParam('sysparm_table', g_form.getTableName()); gaDecryptor.addParam('sysparm_record_id', g_form.getUniqueValue()); gaDecryptor.addParam('sysparm_field_name', 'password'); //replace with password field name gaDecryptor.getXML(cbShowPassword); } /** * * @param response */ function cbShowPassword(response) { var decryptedPassword = response.responseXML.documentElement.getAttribute('answer'); g_form.showFieldMsg( 'password', //Replace with password field name decryptedPassword, 'info', false ); } }
Add One Object to Another Object or Array
/** * Adds one object to another, nesting the child object into the parent. * this is to get around javascript's immutable handling of objects. * @param name {String} - The name of the property of the parent object, in which to nest the child object. <br />For example, if the name parameter is set to "pickles" then "parent.pickles" will return the child object. * @param child {Object} - The object that should be nested within the parent object. * @param [parent={}] {Object} - The parent object in which to nest the child object. If the parent object is not specified, then the child object is simple nested into an otherwise empty object, which is then returned. * @returns {Object} - A new object consisting of the parent (or an otherwise empty object) with the child object nested within it. * @example * //sets myNewObject to a copy of originalObject, which now also contains the original (yet un-linked) version of itself as a child, under the property name "original" * var myNewObject = addObjToObj("original", originalObj, originalObj); */ function addObjToObj(name, child, parent) { if (!parent) { parent = {}; } parent[name] = child; return parent; } //Here's another way you can add an object to an array or another object -- break the "pass-by-reference" model, by stringifying and then parsing the object. function addObjToArray(arr, obj) { arr.push(JSON.parse(JSON.stringify(obj))); return arr; } //And here's an object that has a method for adding other objects to itself! var myObj = { addObject: function(propName, obj) { this[propName] = JSON.parse(JSON.stringify(obj)); } }; //Remember that arrays are just numerically indexed objects, so this should all work with them too
Get Catalog Item Variables (Organized Into Containers)
//init var CATALOG_ITEM_SYS_ID = '', //TODO: REPLACE THIS WITH THE SYS_ID OF THE CATALOG ITEM i, currentVar, fieldName = 'sys_id', varMap = {}, currentContainer, containerLevel = [], item = GlideappCatalogItem.get(CATALOG_ITEM_SYS_ID), grVariables = item.getVariables(); //For every variable found... while (grVariables.next()) { var varName = grVariables.getValue('name'); var varSid = grVariables.getValue('sys_id'); var varType = grVariables.type.getDisplayValue(); var varQuestion = grVariables.getValue('question_text'); var varActive = grVariables.getValue('active'); if (varType === 'Container Start') { containerLevel.push(varSid); } else if (varType === 'Container End') { containerLevel.pop(); } currentContainer = varMap; for (i = 0; i < containerLevel.length; i++) { if (!currentContainer.hasOwnProperty(containerLevel[i])) { currentContainer[containerLevel[i]] = {}; } currentContainer = currentContainer[containerLevel[i]]; } currentContainer[varSid] = new Variable(varName, varSid, varType, varQuestion, varActive); } gs.print(JSON.stringify(varMap)); function Variable(varName, varSid, varType, varQuestion, varActive, varOrder) { this.varName = varName; this.varSid = varSid; this.varType = varType; this.varQuestion = varQuestion; this.varActive = varActive; this.varOrder = varOrder; }
Get URI Parameters (Portal & Classic UI)
//Note: Read from innermost, outward getAllUrlParams( decodeURIComponent( getAllUrlParams( //Get all URL params. Since SN re-encodes everything it passes into page processors like "nav_to.do" for example, this will have one key-val pair. (this.location.href ? this.location.href : window.location.href) //The document URL. Should work for SP, and desktop view. )['uri'] ) ); var yourParamValue = getAllUrlParams(this.location.href)['YOUR_PARAM_NAME']; /** * Get the values of each URI param as properties of an object (name-value pairs). * @param url * @returns {{}} * @example - In the URL www.google.com/page?urip=pickles&otheruriparam=bananas, you could use getAllUrlParams().urip to get "pickles". */ function getAllUrlParams(url) { // get query string from url (optional) or window var queryString = url ? url.split('?')[1] : window.location.search.slice(1); // we'll store the parameters here var obj = {}; // if query string exists if (queryString) { // stuff after # is not part of query string, so get rid of it queryString = queryString.split('#')[0]; // split our query string into its component parts var arr = queryString.split('&'); for (var i = 0; i < arr.length; i++) { // separate the keys and the values var a = arr[i].split('='); // in case params look like: list[]=thing1&list[]=thing2 var paramNum = undefined; var paramName = a[0].replace(/\[\d*\]/, function(v) { paramNum = v.slice(1, -1); return ''; }); // set parameter value (use 'true' if empty) var paramValue = typeof(a[1]) === 'undefined' ? true : a[1]; // (optional) keep case consistent paramName = paramName.toLowerCase(); paramValue = paramValue.toLowerCase(); // if parameter name already exists if (obj[paramName]) { // convert value to array (if still string) if (typeof obj[paramName] === 'string') { obj[paramName] = [obj[paramName]]; } // if no array index number specified... if (typeof paramNum === 'undefined') { // put the value on the end of the array obj[paramName].push(paramValue); } // if array index number specified... else { // put the value at that index number obj[paramName][paramNum] = paramValue; } } // if param name doesn't exist yet, set it else { obj[paramName] = paramValue; } } } return obj; }
Check if a Specific User Has Access to Something
var secManager = new GlideSecurityManager.get().setUser(gs.getUser().getUserByID('user_sys_id')); var checkType = 'record'; var checkTable = 'incident'; var checkOperation = 'read'; var userSysID = '' //todo: Specify user Sys ID if (hasRights(checkType, checkTable, checkOperation, userSysID)) { //todo: do stuff if the user has permissions } else { //todo: do stuff if the user doesn't have permissions } function hasRights(checkType, checkTable, checkOperation, userSysID) { var secManager = new GlideSecurityManager.get().setUser(gs.getUser().getUserByID(userSysID)); return secManager.hasRightsTo( checkType + '/' + checkTable + '/' + checkOperation //This string has the format ['record', 'table' or 'field']/[table name, such as 'incident']/[operation: read/write/delete/report] ); }
Check if a Given String Contains Any of a List of Values
/**Checks whether a given string contains any of the values in a specified array or comma-separated list. * @param stringToCheck {string} The string to check against the list of values to check for. * @param listOfValuesToCheckFor {object|string} An array or comma separated string, containing a list of strings to check. If any of these strings * @returns {object} An object with two properties: answer (a boolean indicating whether any string from the second arg was found in the first) and values (an array of the values from the second arg, that were found in the first). */ function stringContains(stringToCheck, listOfValuesToCheckFor) { var i; var response = { answer: false, values: [] }; //If the subProdKeywords variablr is a string with at least one character, convert it to an array. if (typeof listOfValuesToCheckFor == 'string' && listOfValuesToCheckFor.length > 0) { listOfValuesToCheckFor = listOfValuesToCheckFor.split(','); } else if (typeof listOfValuesToCheckFor !== 'object' || !(listOfValuesToCheckFor.length > 0)) { //If the first condition (subProdKeywords is a string) is NOT met, then we expect an object (array) with at least one value. //If either of the mentioned conditions is not met and the value was not a string, then we return undefined and halt execution while logging the error. gs.warn('Invalid value or data type passed into stringContains() method, or value is null-length. Value passed in: ' + listOfValuesToCheckFor); return undefined; } for (i = 0; i < listOfValuesToCheckFor.length; i++) { if (stringToCheck.indexOf(listOfValuesToCheckFor[i]) >= 0) { response.values.push(listOfValuesToCheckFor[i]); response.answer = true; } } return response; }
Query a Base Table - Get a Record/Fields on an Extended Table
var grChildTable; var childFieldValues = []; var baseTable = 'cmdb_ci'; //A base table that's extended var childTableFieldName = 'nic'; //The name of a field on an extended table var grBaseTable = new GlideRecord('cmdb_ci'); grBaseTable.addQuery('some_query'); grBaseTable.query(); while (grBaseTable.next()) { grChildTable = new GlideRecordUtil().getGR( grBaseTable.getValue('sys_class_name'), //Table name grBaseTable.getValue('sys_id') //sys_id ); childFieldValues.push( grChildTable.getValue(childTableFieldName) ); }
Use Different Credentials/Mid-servers/Endpoints if Instance is Prod Vs. Sub-Prod
/** * Checks the glide.installation.production property and returns true if the instance is production. <br/> * Also checks the instance name, and logs if there is a discrepancy between the instance name and glide.installation.production property. * @param [defaultValue=true] {boolean} * @returns {*} */ function isProd(defaultValue) { //set default value for defaultValue parameter to true defaultValue = (typeof defaultValue !== 'undefined') ? defaultValue : true; var prodProp = isProdPropTrue(defaultValue); var instanceNameProd = isInstanceNameProd(); if ((prodProp && !instanceNameProd) || !prodProp && instanceNameProd) { gs.logError( 'ERROR: INSTANCE NAME AND glide.installation.production PROPERTY DISAGREE ABOUT WHETHER THIS INSTANCE IS PRODUCTION.', 'isProd Script Include' ); } return prodProp; function isProdPropTrue(defaultValue) { //Hey look, JS's lack of strict typing comes in handy for once! //even if only to fix problems that it causes... var thingsThatAreTrue = [true, 'true', 1, '1']; var isProdProp = gs.getProperty('glide.installation.production', defaultValue); return (thingsThatAreTrue.indexOf(isProdProp) >= 0); } function isInstanceNameProd() { var instanceName = gs.getProperty('instance_name', null); var isDev = (instanceName.indexOf('dev') >= 0); var isTest = (instanceName.indexOf('test') >= 0); //If you have OTHER sub-prod instances like "qa", add those conditions too return (!isDev && !isTest); } }
Retrieve the body of a "text/plain" (or other unsupported) REST message
var RESTPlainTextUtil = Class.create(); RESTPlainTextUtil.prototype = { initialize: function() { }, /** * @description Get the body of a text/plain REST request as a string. * @param {sn_ws.RESTAPIRequest} request The RESTAPIRequest object (the whole request object, not just the body). * @param {boolean} [trim=false] Whether to trim the resulting body string before returning it. * @returns {string} The request body as a string. If the trim argument is specified and set to a truthy value, the request body will be trimmed before being returned. */ getTextBody: function(request, trim) { var reqBodyLine; var reqBodyString = ''; var streamBody = request.body.dataStream; var gtrRequestBodyReader = new GlideTextReader(streamBody); //get first line reqBodyLine = gtrRequestBodyReader.readLine(); while(typeof reqBodyLine == 'string') { reqBodyString += reqBodyLine + '\n'; //get next line reqBodyLine = gtrRequestBodyReader.readLine(); } return trim ? reqBodyString.trim() : reqBodyString; }, type: 'RESTPlainTextUtil' };
Format Object Output Using JSON.stringify() For Prettier Logs
var exampleObj = { "name": "Tim", "age": 31, "groovy": true, "dogs": [ { "name": "Ezri", "age": 1 }, { "name": "Seven", "age": 3 } ] }; gs.info( 'Logging JSON objects can be very useful, but they need to be stringified first. If we ' + 'just stringify them in the usual way, we get something that\'s kind of hard to read, like this: \n' + '{0}\n\n' + 'But, we can do better, by specifying some additional parameters to the .stringify() method like ' + 'so: JSON.stringify(exampleObj, null, 2) : \n{1}', JSON.stringify(exampleObj), JSON.stringify( exampleObj, null, //A function or array to filter/replace some values of the object (useful if // you want to log an object, but not log potentially sensitive elements) 2 //The number of spaces by which to indent child-elements. You can also specify a // string or escape character such as '\t' for tabs. ) ); /* Output: Logging JSON objects can be very useful, but they need to be stringified first. If we just stringify them in the usual way, we get something that's kind of hard to read, like this: {"name":"Tim","age":31,"groovy":true,"dogs":[{"name":"Ezri","age":1},{"name":"Seven","age":3}]} But, we can do better, by specifying some additional parameters to the .stringify() method like so: JSON.stringify(exampleObj, null, 2) : { "name": "Tim", "age": 31, "groovy": true, "dogs": [ { "name": "Ezri", "age": 1 }, { "name": "Seven", "age": 3 } ] } */ //More on the stringify method here: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
Get User's Date/Time Format and Validate Date/Time Field in Client Script
/** * Client Script (or Catalog Client Script) * Validates that a 'date' or 'date/time' field is no more than a number of miliseconds ago equal to the value of msAgo. * For example, to check that the value entered in the "some_date_time" field is no more than 1 hour ago, you would call: * validateChronoFieldValue("some_date_time", true, 3600000); * * @param {string} fieldName - The name of the date or date/time field to check. * @param {boolean} dateTime - If the field is a "Date" field, set this param to false. If it is a "Date/time" field, set this param to true. * @param {number} msAgo - An integer representing the number of miliseconds ago that is the maximum amount in the past that the field value can be set to. For example, one hour ago would be 3600000. * @param {boolean} [preventFutureDates=true] - Optionally, if you would like to ALLOW future dates (thus causing the function to return true if the selected date is in the future), set this param to false. * @param {boolean} [returnTrueIfEmpty=false] - Optionally, set this parameter to true if you'd like for this function to return true if the specified field is empty or not found. * * @returns {boolean} - True if the field's value is valid, or if it is empty and the returnTrueIfEmpty parameter has been set to true. */ function validateChronoFieldValue(fieldName, dateTime, msAgo, preventFutureDates, returnTrueIfEmpty) { //Set default value of returnTrueIfEmpty. returnTrueIfEmpty = (typeof returnTrueIfEmpty == 'undefined') ? false : returnTrueIfEmpty; preventFutureDates = (typeof preventFutureDates == 'undefined') ? true : preventFutureDates; var fieldVal = g_form.getValue(fieldName); if (!fieldVal) { return returnTrueIfEmpty; } var userFormat = dateTime ? g_user_date_time_format : g_user_date_format; var currentDateObj = new Date(); var currentDateStr = formatDate(currentDateObj, userFormat); var currentDateNum = getDateFromFormat(currentDateStr, userFormat); var selectedDateNum = getDateFromFormat(fieldVal, userFormat); var epochTimeAtMsAgo = currentDateNum - msAgo; return !( (selectedDateNum < epochTimeAtMsAgo) || (preventFutureDates && (selectedDateNum > currentDateNum)) ); }
Add Variables to Catalog Task From RITM Workflow Script
function addVars(taskSysid) { var variablesForTask = ['var1', 'var2', 'var3', 'var4']; //could be optimized by building a list, then iterating through that list to insert. var grTaskVariable = new GlideRecord('sc_item_variables_task'); var grVariableM2M = new GlideRecord('sc_item_option_mtom'); grVariableM2M.addQuery('request_item', current.sys_id); grVariableM2M.addQuery('sc_item_option.item_option_new.name', 'IN', variablesForTask.join(',')); grVariableM2M.query(); while (grVariableM2M.next()) { grTaskVariable.initialize(); grTaskVariable.setValue('task', taskSysid); grTaskVariable.setValue('variable', grVariableM2M.sc_item_option.item_option_new.toString()); grTaskVariable.insert(); } }
Generate History Set (sys_history_set Without Viewing a Record
This script came out of the following conversation on the community Slack (or Discord; I forget):
I am looking for a way to generate sys_history_set records for a given task, without the user having to actually visit the task.
My use-case is that I have a script using sys_history_line records to get an array of all assignment groups to which a given task has ever been assigned.
However, if the record was updated programmatically but not "viewed", the history sets/lines are not generated.
I am also open to alternative solutions, such as a more robust mechanism by which to determine all of the groups to which a task has ever been assigned.
I have tried using the sys_audit table, but there are circumstances where even this table is not populated (such as after a clone... an extreme edge case, I know)... And, I know that sys_history is built from sys_audit, but I still want to see if there's a way to trigger the generation of the sys_history_set/line records programmatically, or if that code is completely black-boxed.
var hw, grI = new GlideRecord('incident'); grI.get('88fa35901b9dd058e253eac6bc4bcb32'); var hw = new sn_hw.HistoryWalker(grI.getTableName(), grI.getUniqueValue(), false); //last arg must be false to generate history set gs.info(hw.walkTo(0)); //If true, walk was successful and history set is generated.
Get Latest Journal Entry as HTML Without Header Info
var journalFieldName = 'comments'; var journalText = current[journalFieldName] .getJournalEntry(1) .trim() .split('\n') .slice(1) .join('<br />\n');
Populate an "Image" Field From a Script (Default)
Display a Form in a Dialog Window (and Set Some Values)
Show an Expanding Info Message with Functional Elements
Get and Parse ServiceNow Journal Entries as Text/HTML
Check if One Table is in the Hierarchy of Another (SCOPED)
NOTE: This method only works in scoped apps. For a Global-scope alternative, use .getHierarchy().
/**
* Returns true if tableName is a child of parentTableName, or false otherwise.
* @param {string} tableName - The table to check. Must be a string. Will be converted to
* lowercase. Will return true if this table is a child of parentTableName.
* @param {string} parentTableName - The parent table to check. Must be a string. Will be
* converted to lowercase. Will return true if tableName is a child of this table.
* @returns {boolean} - True if tableName is a child of parentTableName, or false otherwise.
*
* @example
* //Returns true
* isTableInHierarchy('incident', 'task');
* @example
* //Returns false
* isTableInHierarchy('task', 'incident');
* @example
* //returns true
* isTableInHierarchy('incident', 'incident');
* @example
* //returns false if EITHER table is invalid
* isTableInHierarchy('incident', 'invalid_table_name');
* @example
* //Prints true to the log
* var tableToCheck = 'sc_req_item';
* var parentTableToCheck = 'task';
* gs.info(
* 'Is table ' + tableToCheck + ' a child of table ' + parentTableToCheck + '?: ' +
* isTaskTable(
* tableToCheck,
* parentTableToCheck
* )
* );
*/
function isTableInHierarchy(tableName, parentTableName) {
var tableUtil, tableParentHierarchy;
tableName = (typeof tableName === 'string') ? tableName.toLowerCase() : '';
parentTableName = (typeof parentTableName === 'string') ? parentTableName.toLowerCase() : '';
if (!tableName || !parentTableName) {
throw new Error(
'isTableInHierarchy(): tableName and parentTableName must be specified ' +
'and set to strings.'
);
}
tableUtil = new GlideTableHierarchy(tableName);
tableParentHierarchy = tableUtil.getTables();
return (tableParentHierarchy.indexOf(parentTableName) >= 0);
}
-
2024
- Mar 28, 2024 How to Identify Duplicate Records by Multiple Fields in ServiceNow Mar 28, 2024
- Mar 7, 2024 How to Merge Personal & Company ServiceNow Accounts Mar 7, 2024
- Feb 12, 2024 5 Lessons About Programming From Richard Feynman Feb 12, 2024
-
2023
- Jul 5, 2023 Managing Instance-Specific System Properties for Dev/Test/Prod in ServiceNow Jul 5, 2023
- May 11, 2023 5 Ways to Check your ServiceNow Instance for DANGEROUS CODE in Less Than 5 minutes May 11, 2023
- Apr 28, 2023 Your ACLs and Business Rules are Broken (Here's How to Fix Them) Apr 28, 2023
-
2022
- Dec 13, 2022 ServiceNow Developers: BE THE GUIDE! Dec 13, 2022
- Oct 19, 2022 A Faster, More Efficient Client-side GlideRecord (Free tool!) Oct 19, 2022
- Oct 9, 2022 Animated Loading Message & Collapsible Details on ServiceNow Form or Field (Client-side) Oct 9, 2022
- Aug 23, 2022 Using .addJoinQuery() & How to Query Records with Attachments in ServiceNow Aug 23, 2022
- Aug 18, 2022 Free, Simple URL Shortener for ServiceNow Nerds (snc.guru) Aug 18, 2022
- Aug 16, 2022 How to Get and Parse ServiceNow Journal Entries as Strings/HTML Aug 16, 2022
- Aug 14, 2022 New tool: Get Latest Version of ServiceNow Docs Page Aug 14, 2022
- Mar 4, 2022 How to Set or Change ServiceNow Application's Repository URL, Credentials, or SSH Key Mar 4, 2022
- Feb 7, 2022 How to return a CSV file from a Scripted REST API (SRAPI) in ServiceNow Feb 7, 2022
-
2021
- May 3, 2021 Adding a Guided Setup to Your ServiceNow Application May 3, 2021
- Apr 27, 2021 Use Automated Tests to Validate "Guided Setup" Completion & Functionality. Apr 27, 2021
- Feb 11, 2021 "Processors", SRAPIs, and How to Run a Script and Redirect a User From a URL in ServiceNow Feb 11, 2021
-
2020
- Nov 17, 2020 SN Guys is now part of Jahnel Group! Nov 17, 2020
- Sep 14, 2020 Better ServiceNow Notifications (& Another FREE Tool!) Sep 14, 2020
- Jul 31, 2020 Debugging Client & Catalog Client Scripts in ServiceNow Jul 31, 2020
- Jan 20, 2020 Getting Help from the ServiceNow Community Jan 20, 2020
-
2019
- Dec 18, 2019 Can ServiceNow Script Includes Use the "current" Variable? Dec 18, 2019
- Nov 18, 2019 Handling 'text/plain' and Other Unsupported Content Types in ServiceNow Scripted REST APIs Nov 18, 2019
- Apr 21, 2019 Understanding Attachments in ServiceNow Apr 21, 2019
- Apr 10, 2019 Using Custom Search Engines in Chrome to Quickly Navigate ServiceNow Apr 10, 2019
- Apr 4, 2019 Set Catalog Variables from URL Params (Free tool) Apr 4, 2019
- Apr 1, 2019 Outlook for Android Breaks Email Approvals (+Solution) Apr 1, 2019
- Mar 11, 2019 GlideFilter is Broken - Free Tool: “BetterGlideFilter” Mar 11, 2019
- Feb 27, 2019 Making Update Sets Smarter - Free Tool Feb 27, 2019
-
2018
- Nov 29, 2018 How to Learn ServiceNow Nov 29, 2018
- Nov 6, 2018 ServiceNow & ITSM as a Career? Nov 6, 2018
- Oct 19, 2018 Asynchronous onSubmit Catalog/Client Scripts in ServiceNow Oct 19, 2018
- Oct 11, 2018 How to do Massive, Slow Database Operations Efficiently With Event-Driven Recursion Oct 11, 2018
- Sep 18, 2018 Broken Queries & Query Business Rules in ServiceNow Sep 18, 2018
- Sep 7, 2018 JournalRedactor - Easily Redact or Delete Journal Entries in ServiceNow! Sep 7, 2018
- Jul 23, 2018 Admin Duty Separation with a Single Account Jul 23, 2018
- Jun 19, 2018 Improving Performance on Older Instances with Table Rotation Jun 19, 2018
- Jun 4, 2018 New Free Tool: Login Link Generator Jun 4, 2018
- May 29, 2018 Learning ServiceNow: Second Edition! May 29, 2018
- Apr 17, 2018 Upgrading From Express to Enterprise: What's Missing Apr 17, 2018
- Apr 12, 2018 If a Genie Gave Me Three Wishes, I'd Use Them All to "Fix" Scope Apr 12, 2018
- Mar 19, 2018 Service Catalog "Try in Portal" button Mar 19, 2018
- Mar 15, 2018 Video: Custom Output Transition Conditions From a Single Workflow (Script) Activity Mar 15, 2018
- Feb 11, 2018 We have a new book! Feb 11, 2018
-
2017
- Nov 6, 2017 Requiring Attachments (& Other Miracles) in Service Portal Nov 6, 2017
- Sep 12, 2017 Handling TimeZones in ServiceNow (TimeZoneUtil) Sep 12, 2017
- Jul 27, 2017 How to Enable DOM Manipulation in ServiceNow Service Portal Catalog Client Scripts Jul 27, 2017
- Jun 25, 2017 What's New in ServiceNow: Jakarta (Pt. 1) Jun 25, 2017
- Jun 4, 2017 Powerful Scripted Text Search in ServiceNow Jun 4, 2017
- May 9, 2017 Work at Lightspeed: ServiceNow's Plan for World Domination May 9, 2017
- Apr 9, 2017 Avoiding Pass-By-Reference Using getValue() & setValue() Apr 9, 2017
- Apr 4, 2017 "Learning ServiceNow" is Now Available for Purchase! Apr 4, 2017
- Mar 12, 2017 reCAPTCHA in ServiceNow CMS/Service Portal Mar 12, 2017
-
2016
- Dec 20, 2016 Pro Tip: Use updateMultiple() for Maximum Efficiency! Dec 20, 2016
- Dec 2, 2016 We're Writing a Book! Dec 2, 2016
- Nov 10, 2016 Chrome Extension: Load in ServiceNow Frame Nov 10, 2016
- Sep 7, 2016 Force-Include Any Record Into an Update Set Sep 7, 2016
- Sep 1, 2016 GlideRecord Pagination - Page through your GlideRecord query Sep 1, 2016
- Jul 17, 2016 Granting Temporary Roles/Groups in ServiceNow Jul 17, 2016
- Jul 15, 2016 Scripted REST APIs & Retrieving RITM Variables via SRAPI Jul 15, 2016
- May 17, 2016 What's New in Helsinki? May 17, 2016
- Apr 27, 2016 Customizing UI16 Through CSS and System Properties Apr 27, 2016
- Apr 5, 2016 ServiceNow Versions: Express Vs. Enterprise Apr 5, 2016
- Mar 28, 2016 Update Set Collision Avoidance Tool: V2 Mar 28, 2016
- Mar 18, 2016 ServiceNow: What's New in Geneva & UI16 (Pt. 2) Mar 18, 2016
- Feb 22, 2016 Reference Field Auto-Complete Attributes Feb 22, 2016
- Feb 6, 2016 GlideRecord & GlideAjax: Client-Side Vs. Server-Side Feb 6, 2016
- Feb 1, 2016 Make Your Log Entries Easier to Find Feb 1, 2016
- Jan 29, 2016 A Better, One-Click Approval Jan 29, 2016
- Jan 25, 2016 Quickly Move Changes Between Update Sets Jan 25, 2016
- Jan 20, 2016 Customize the Reference Icon Pop-up Jan 20, 2016
- Jan 7, 2016 ServiceNow: Geneva & UI16 - What's new Jan 7, 2016
- Jan 4, 2016 Detect/Prevent Update Set Conflicts Before They Happen Jan 4, 2016
-
2015
- Dec 28, 2015 SN101: Boolean logic and ServiceNow's Condition Builder Dec 28, 2015
- Dec 17, 2015 Locate any record in any table, by sys_id in ServiceNow Dec 17, 2015
- Dec 16, 2015 Detecting Duplicate Records with GlideAggregate Dec 16, 2015
- Dec 11, 2015 Array.indexOf() not working in ServiceNow - Solution! Dec 11, 2015
- Dec 2, 2015 Understanding Dynamic Filters & Checking a Record Against a Filter Using GlideFilter Dec 2, 2015
- Oct 20, 2015 Bookmarklet: Load the current page in the ServiceNow frame Oct 20, 2015
- Aug 27, 2015 Easily Clone One User's Access to Another User Aug 27, 2015