How to Get and Parse ServiceNow Journal Entries as Strings/HTML

I come across this question all the time in the ServiceNow community forums, and in the ServiceNow developer community Discord, Slack, and Telegram groups.

“How do I get the last N journal entries from a record in ServiceNow, in a script?”
“How do I print the last N journal entries in my notification email or mail script in ServiceNow?”
“How do I parse journal entries for HTML so special characters and line-breaks will show up properly in HTML or an email?”

I’ve helped people create so many one-off solutions to these questions that it felt more efficient to, finally, just create a single function that can do it all!

It’s certainly not the most monadic thing I’ve ever written, but it gets the job done. It’s also very well-documented so that if you want to know how it works (which, if you’re using it, you should), you can read through the code and comments, and understand exactly what it’s doing!

Scroll down to see the code, information on how to add this free tool to your instance, and instructions for use.

The code

Usage

There are examples in the JSDoc above the function, which includes what it should output given a set of arguments, but I’ll reproduce them below so you can see them in-code:

//Example 1:

var arrWorkNotes;
var grInc = new GlideRecord('incident');
grInc.setLimit(1);
grInc.query();
if (grInc.next()) {
    arrWorkNotes = getJournalEntries(
        grInc,
        'work_notes',
        true,
        true,
        2
    );
    gs.info(JSON.stringify(arrWorkNotes, null, 2));
}
/* Output:
['2022-08-16 10:31:11 - Admin (Work notes)\nThis is an example journal entry <br />\n<br    />\nwith a lot of<br />\nextraneous<br />\n<br />\n<br />\nline breaks.', '2022-08-16    10:30:58 - Admin (Work notes)\nThis is an example journal entry with <br />\nline breaks,    and &#60;HTML wokkas that&#62; need to be converted to HTML.'];
*/

//Example 2:

function myFilterFunction(strJournalEntry) {
    //Filter results: only include entries including this text, or less than 100 chars long
    return (
        strJournalEntry.includes('Some text to check for') ||
        strJournalEntry.length < 100
    );
}

var arrCommentsAndWorknotes = getJournalEntries(
    current,
    'comments_and_work_notes',
    false,
    false,
    -1, //Get ALL journal entries
    myFilterFunction
);

“But what if I want the journal entries to show up in a table in the notification email/page/whatever?”

Okay sure fine; if you need an HTML table, you can pass the resulting array into this function:

function convertToTable(arrJournalEntries) {
    var i, strJournalEntry;
    var strHTMLTable = '<table><tbody>';
    var openTrTd = '<tr><td>';
    var closeTrTd = '</td></tr>';

    if (!arrWorkNotes || arrWorkNotes.length < 1) {
        return '<table></table>';
    }

    for (i = 0; i < arrJournalEntries.length; i++) {
        strJournalEntry = arrJournalEntries[i];
        strHTMLTable += openTrTd + strJournalEntry + closeTrTd;
    }

    strHTMLTable += '</tbody></table>';
    return strHTMLTable;
}

Installation

You can add this to your instance so that it can be called from any server-side code (such as Mail Scripts or Business Rules) by creating a Script Include called “getJournalEntries”, and replacing all of the auto-generated code with this code.
Once that’s done, you can simply call getJournalEntries() from anywhere, and pass in the necessary arguments as described at the top of the function. The only required arguments are the GlideRecord object you want to get the journal field values from, and a string with the name of the journal field you want to get the entries from.

Alternatively, you can simply import this XML file into your Script Includes table (sys_script_include).