Selva Arun
Mega Sage
Mega Sage

Intro:

I was tasked with creating a script to fetch Knowledge Articles linked to Application CIs, but there was a catch - I only wanted the latest version of each article. When KB articles get updated, ServiceNow creates new versions but keeps the old ones, so I was getting duplicates.

The Problem:

  • KB0001234 Version 1.0
  • KB0001234 Version 2.0
  • KB0001234 Version 3.0 ← Only want this one!

My Big Challenge: Too Many Comic Books! 📚

Imagine you have a huge pile of comic books, but some comics have multiple versions:

  • Spider-Man #100 Version 1 (old and torn) 📖
  • Spider-Man #100 Version 2 (better) 📗
  • Spider-Man #100 Version 3 (brand new and shiny!) 📘
  • Batman #200 Version 1 (old) 📖
  • Batman #200 Version 2 (new and shiny!) 📗

My job: Pick only the newest, shiniest version of each comic for my friend!

But in ServiceNow, these "comic books" are called Knowledge Articles, and they work the same way - when someone updates an article, ServiceNow keeps the old versions too!

My First Idea: "I Saw This Trick!" 🤔

I was looking at the ServiceNow screen where all the Knowledge Articles are listed, and I saw something cool - they were grouped together by number! It looked like this:

📚 Spider-Man #100
   - Version 1
   - Version 2  
   - Version 3

📚 Batman #200
   - Version 1
   - Version 2

I thought: "Perfect! I saw groupBy('number') in the code that makes this happen. I'll use the same trick!"

// My first try (WRONG!)
grKB.groupBy('number'); // I saw this working on the screen!

Why I thought this would work:

  • It was literally working on the ServiceNow screen
  • The articles looked nicely grouped by number
  • I figured if it works on the screen, it should work in my code
  • It seemed super obvious!

Why My First Idea Didn't Work 😢

Here's the tricky part I learned:

Looking at comics on a shelf vs. Picking comics to take home are DIFFERENT things!

  • On the shelf (List View): groupBy() organizes comics so you can see them nicely
  • Picking comics (My Script): I need to actually grab individual comics to take home

The Problem: When I tried to "pick up" comics using my script, groupBy() was like asking the librarian "How many Spider-Man comics do you have?" instead of "Give me the newest Spider-Man comic!"

I needed actual comic books to take home, not just a count of how many there were!

My Working Solution: Line Them Up + Pick Smart! 🎯

I figured out a better way - like organizing a comic book store:

Step 1: Line Up All Comics (Newest First!) 📏

grKB.orderByDesc('number');     // Group same comics together
grKB.orderByDesc('version');    // Put newest versions first in line

Now my line looks like:

Spider-Man #100 Version 3 ← NEWEST! (first in line)
Spider-Man #100 Version 2 ← older
Spider-Man #100 Version 1 ← oldest
Batman #200 Version 2 ← NEWEST! (first in line)  
Batman #200 Version 1 ← older

Step 2: Use My Memory Box! 🧠📦

var processedNumbers = {}; // My magic memory box (starts empty)

This is like having a notebook where I write down which comics I already picked!

Step 3: Walk Down the Line and Pick Smart! 🚶‍♂️

while (grKB.next()) { // Walk to each comic in line
    var articleNumber = grKB.number.toString(); // What comic is this?
    
    // Check my memory box: "Have I seen this comic number before?"
    if (!processedNumbers[articleNumber]) {
        // "Nope! Never seen this comic number!"
        processedNumbers[articleNumber] = true; // Write it in my memory box ✍️
        
        // Take this comic! (It's the newest because it's first in line!)
        articles.push({
            sys_id: grKB.sys_id.toString(),
            number: articleNumber,
            // ... other comic details
        });
    }
    // If I already have this comic number, skip it!
}

Let's Watch It Work! 👀

Memory box starts empty: {}

Comic 1: Spider-Man #100 Version 3

  • Me: "Have I seen #100 before?"
  • Memory box: {} (empty - nope!)
  • Me: "Take it!" 📘
  • Memory box now: {100: true}

Comic 2: Spider-Man #100 Version 2

  • Me: "Have I seen #100 before?"
  • Memory box: {100: true} (yes, I have it!)
  • Me: "Skip it!"

Comic 3: Spider-Man #100 Version 1

  • Me: "Have I seen #100 before?"
  • Memory box: {100: true} (yes, still have it!)
  • Me: "Skip it!"

Comic 4: Batman #200 Version 2

  • Me: "Have I seen #200 before?"
  • Memory box: {100: true} (nope, no #200!)
  • Me: "Take it!" 📗
  • Memory box now: {100: true, 200: true}

Comic 5: Batman #200 Version 1

  • Me: "Have I seen #200 before?"
  • Memory box: {100: true, 200: true} (yes, I have it!)
  • Me: "Skip it!"

My Complete Working Code! 🎉

var KBArticleHelper = Class.create();
KBArticleHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getKBArticles: function() {
        var parentId = this.getParameter('sysparm_parent_id');
        var articles = [];

        // Get all the knowledge articles
        var grKB = new GlideRecord('kb_knowledge');
        grKB.addQuery('cmdb_ci', parentId);
        grKB.addQuery('active', true);
        grKB.addQuery('workflow_state', 'published');
        
        // Step 1: Line them up (newest first!)
        grKB.orderByDesc('number');
        grKB.orderByDesc('version'); 
        grKB.query();

        // Step 2: Get my memory box ready
        var processedNumbers = {}; 

        // Step 3: Walk down the line and pick smart!
        while (grKB.next()) {
            var articleNumber = grKB.number.toString();
            
            // Check memory box: "Have I seen this number before?"
            if (!processedNumbers[articleNumber]) {
                processedNumbers[articleNumber] = true; // Remember it!
                
                // Take this article (it's the newest!)
                articles.push({
                    sys_id: grKB.sys_id.toString(),
                    number: articleNumber,
                    short_description: grKB.short_description.toString(),
                    category: grKB.kb_category.getDisplayValue(),
                    validTo: grKB.valid_to.getDisplayValue(),
                    updatedOn: grKB.sys_updated_on.getDisplayValue(),
                    owner_group: grKB.ownership_group.getDisplayValue(),
                });
            }
            // If I already have this number, just keep walking!
        }

        return JSON.stringify(articles);
    },
    type: 'KBArticleHelper'
});

What I Learned (So You Don't Make My Mistakes!) 🎓

1. Screen Magic ≠ Code Magic

Just because something works on the ServiceNow screen doesn't mean it works the same way in your code!

  • Screen groupBy = "Show me things organized nicely"
  • Code groupBy = "Count things for me"
  • What I needed = "Give me actual things to work with"

2. Simple Ideas Work Best! 💡

My fancy groupBy idea didn't work, but simple "line up + memory box" worked perfectly!

3. Test Your Ideas! 🧪

Always try your code in a safe place first. My first idea seemed super smart but didn't work!

4. It's Okay to Be Wrong First! 😊

Every expert was once a beginner who tried things that didn't work. That's how we learn!

When to Use My Trick! 🎯

Use this "Line Up + Memory Box" trick when you need:

  • Only the newest version of things
  • The first one of each type
  • To avoid duplicates but keep all the details
  • Something simple that's easy to understand

Final Thoughts! 🌟

Don't be scared to try different ideas! My first groupBy idea didn't work, but it helped me understand the problem better and find the right solution.

Remember:

  • Test your ideas in a safe place
  • Simple solutions are often the best
  • It's okay if your first idea doesn't work
  • Every expert started as a beginner just like you!

The most important thing is to keep trying until something works! 🚀


A Note from Me 💭

I know there are probably better explanations about this topic out there, but my intention is not to duplicate anyone's efforts. This is simply how I learned and thought it might be useful for someone out there just like me. I'm not an expert JavaScript developer or pro coder - I'm still learning every day!

 

This article is my way of sharing my learning journey, hoping it might help another beginner who faces the same challenge. We all learn differently, and sometimes a simple, personal explanation can click better than complex technical documentation.

 

If you believe this solution has adequately addressed a similar query you might have had, could you please mark it as 'Helpful'? This will help other community members who might have the same question find the answer more easily.

Thank you for your consideration, and happy coding! 🎈

 

Remember: Every expert was once a beginner. Keep learning, keep trying, and don't be afraid to share your journey with others!

 

Selva Arun

Comments
Eoghan Sinnott
Kilo Sage
Kilo Sage

Hi Selva,

 

Thanks for the great post — really helpful! I had a quick question about the approach you used.

In standard ServiceNow behavior, the "latest" field is automatically set to "true" on the most recent version of a knowledge article, and only one version can be published at a time (with previous versions moving to the "Outdated" state). Given that, I was wondering:

Was there a specific reason you chose not to use the latest = true or workflow = published conditions to retrieve the newest articles?

From what I understand, those fields are typically reliable and automatically maintained by the platform, so I’m curious if you encountered a scenario where they weren’t sufficient — maybe due to custom workflows or data inconsistencies?

 

Would love to hear your thoughts!

 

Regards,

Eoghan

Atul Saswat
Tera Explorer

@Eoghan Sinnott - I was exactly wondering the same.

 

While working on a client project, we did encounter a scenario where workflow was published for multiple versions and latest was not true for all articles but these were a very small sample space. Like workflow = published returned back with only 6 duplicates of 4 articles and latest = true was not sent for some 100 odd articles out of a total set of 5700+.

In our case, we had to get kb_version records and without dot walking and setting KB Article.Latest = True, it was returning back 130K+ records. In all context seen so far, Latest and Workflow State should be the primary fields to be considered and any deviations seen from it should be duly flagged and for the small subset whether you look for the latest number or filter by updated latest, it should work.

Version history
Last update:
‎07-07-2025 08:56 AM
Updated by:
Contributors