Understanding Dynamic Filters & Checking a Record Against a Filter Using GlideFilter

NOTE: This article discusses undocumented APIs. Undocumented APIs may (but probably won't) disappear or function differently in future releases of ServiceNow, so it is recommended that you use caution when dealing with them. 


I recently found myself in a situation where I had to check if a given record (the 'current' object in my case) matched a filter associated with another record (a client script, in my case). If you find yourself needing to do something similar, it might help you to know about an undocumented Glide API called "GlideFilter".

GlideFilter takes two arguments: 

  1. A glide record containing the record you'd like to check
  2. The query string (aka "encoded query") you'd like to check it against. 

The first argument may be self-explanatory - it's a GlideRecord object containing a single record.
The second argument, if you're not familiar with encoded queries, is a string of text that represents a query. If you've ever built a query in a condition builder, you've built an encoded query.

Now, if I were to click "Run" below this condition builder, I would see something like this: 

Right-clicking the last condition in this condition string will allow me to select "Copy Query". The alert that pops up will have the encoded query text that represents this query:

nameSTARTSWITHti^active=true^NQcityNOT LIKEpickles

Let's break this string down. Just like our condition builder, there are three conditions here: 

The first condition is "nameSTARTSWITHti". As you can probably guess, this query is checking if the field 'name' starts with the letters 'ti'. This is followed by a carat ("^"), which means "and...". 

The second condition is also pretty obvious. "active=true". This is checking if the record is active. This is followed by "^NQ" which is indicating a new query. This is a "new query" rather than a condition in the first query because of the way databases and querying/SQL works. You can't do one search that checks both one set of conditions, and another set of conditions. For this reason, our query string (or "encoded query") has ^NQ to indicate that a second query should be done, and the results concatenated together. Luckily though, ServiceNow does all that for us, seamlessly. 

The final condition in our query (or you might say, the only condition in our second query, because of the "^NQ"), is "cityNOT LIKEpickles". "NOT LIKE" is the SQL equivalent of "Does not contain". 

So, now we've got a query string. But hang on, this only helps us if we already know the query string we're going to compare a record against. What if we need to get the encoded query used by another record, such as a script object? 

Let's have a look at a Business rule as an example.     

This business rule has two conditions, but one of them is dynamic. Let's see how that works. 

We can get the query string corresponding to that condition builder in a couple of ways. Since we're scripting client-side for now, let's start by pressing CTRL+SHIFT+J. This will open up your browser's console, as well as opening the JavaScript Executor window in ServiceNow. If you're not familiar with this, it's just a box that lets you enter and execute arbitrary client-side javascript. 

Probably the easiest way to get it to spit out the encoded query, is to enter the following code, and then click "Run my code": 

 console.log(getFilter('element.sys_script.filter_condition'));
//returns state>2^assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe^EQ 

As you can see, the encoded query string will display in your console (which is on the right in the screenshot).

Note: If the encoded query doesn't show up in your console, make sure that your console is set to show "All", and not just "Errors" or "Warnings". 

What's strange about this encoded query is that it's got a sys_id in it, but I didn't specify a particular record:

assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe^EQ

This is because of the way dynamic filter options work. That sys id is referencing a record in the Dynamic Filter Options (sys_filter_option_dynamic) table with the label "Me". 

This dynamic filter option has a script value: "gs.getUserID()". This script is run, and the value is returned in the query whenever a dynamic query is run. We could just as well use a query like this:

assigned_to=javascript:gs.getUserID();^EQ

Just for your edification, there is another way we could've done this. We could've used "g_form.getValue('filter_conditions');". This is because the condition builder is really just an interface for a field called "conditions" which holds the encoded query. 

Here's some code that will let you retrieve the encoded query from a server-side script: 

var sysId = '' //Enter the sys_id of the business rule or other record containing the conditions field.
var table = 'sys_script'; //This is the business rule table. Change this to select another table, but be sure the field name is correct. 
var fieldName = 'filter_conditions'; //This is the name of the field that contains the filter. 

var gr = new GlideRecord(table);
gr.get(sysId);
var encodedQueryString = gr.getValue(fieldName); 

If you fill in the sys_id in the above script, this should set the variable encodedQueryString to the query corresponding to the condition builder on that record. Once you've got that, you can finally call GlideFilter's checkRecord method, like so: 

var sysId = '6e20e124c611228e00e44dd37ad1b842' //The sys_id of the business rule 
var table = 'sys_script'; //This is the business rule table.  
var fieldName = 'filter_conditions'; //This is the name of the field that contains the filter. 
var scriptGR = new GlideRecord(table);
scriptGR.get(sysId);
var encodedQueryString = scriptGR.getValue(fieldName);

if (GlideFilter.checkRecord(current, encodedQueryString)) {
    gs.addInfoMessage('This will execute if the \'Current\' record matches the condition in the condition builder for the selected business rule.');
} 

As you can see, we simply call GlideFilter.checkRecord(), and we pass in "current" or any other GlideRecord (it could be the result of a separate query, or whatever else), and the string containing the encoded query. 

The result is that checkRecord returns a boolean that is true if the record matches the condition, and false if it does not. 

EDIT: One final note on the use of undocumented APIs. Since it is possible that they could behave differently or even disappear in future releases of ServiceNow, I personally recommend that you put them into a try { } catch() { } block. That would look something like this: 

try {
    //Use undocumented API
    var x = undocumentedFunctionCall(args);
}
catch (ex) {
    //If use fails or breaks somehow, throw an error. 
    gs.log('Undocumented API call has failed. Error text: ' + ex.message);
    //Insert code to handle this error, and assume that the above API call has failed. 
    var x = '';
    //Insert additional code to back out and break off whatever function or work was being done. If this error is critical, you may try the following line:
    throw new Error('Undocumented API call has failed critically! Error: ' + ex.message);
}