sarah_bioni
ServiceNow Employee
ServiceNow Employee

Goal: Return the Knowledge Base category hierarchy in the structure expected by the Content Tree component:

[
{
"id": "p1",
"label": "Tree parent item",
"children": [
{ "id": "c1", "label": "Tree child item 1" },
{ "id": "c2", "label": "Tree child item 2" },
{ "id": "c3", "label": "Tree child item 3" }
]
}
]
``

This article walks you through creating a Transform Data Broker that queries kb_category, builds a nested tree, sorts categories alphabetically, and exposes it for binding to the Content Tree’s Items property.


What we’ll build

  1. A Transform Data Broker (server script) named Get KB Category Hierarchy.
  2. A single input property: kb_id (the sys_id of the Knowledge Base).
  3. A script that:
    • Starts from the KB’s top-level categories,
    • Performs a batched traversal to collect all children,
    • Builds parent/child relationships,
    • Sorts siblings by label,
    • Returns items: [...] in the shape the Content Tree requires.

Step 1 — Create the Transform Data Broker in UI Builder

  1. Open UI Builder for your Workspace page.
  2. In the Data and Scripts panel, click (+)Data Resource → Create →choose Transform.
  3. Name it Get KB Category Hierarchy and (optionally) add a description.
  4. In Properties, define the broker’s input:
[
{
"name": "kb_id",
"label": "KB Id",
"description": "KB sys_id to get the categories hierarchy",
"readOnly": false,
"fieldType": "string",
"mandatory": true,
"defaultValue": ""
}
]

Step 2 — Paste the Transform Script

It builds a node map, traverses children breadth‑first, wires parent/child relationships, sorts labels, and returns { items: roots }—the Content Tree‑friendly output.

 

function transform(input) {
var kbId = input.kb_id;
 
// Node registry and parent mapping
var nodes = {};
var parentMap = {};
var roots = [];
 
// Pick a good label field (environment differences: 'label' vs 'value')
function getLabelField(grCat) {
if (grCat.isValidField('label')) return 'label';
if (grCat.isValidField('value')) return 'value';
return 'label';
}
 
// 1) Collect KB top-level categories
var grRoots = new GlideRecord('kb_category');
var pFieldProbe = 'parent_id'; // parent relationship field
grRoots.addQuery(pFieldProbe, kbId); // roots are children of the KB
grRoots.query();
 
var frontier = [];
while (grRoots.next()) {
var id = grRoots.getUniqueValue();
var label = grRoots.getValue(getLabelField(grRoots));
 
if (!nodes[id]) nodes[id] = { id: id, label: label, children: [] };
else nodes[id].label = label;
 
parentMap[id] = kbId;
roots.push(nodes[id]);
frontier.push(id);
}
 
// 2) BFS traversal collecting descendants in batches
var visitedParents = {};
frontier = frontier.filter(function (pid) { return !!pid; });
 
while (frontier.length) {
var batchParents = [];
frontier.forEach(function (pid) {
if (!visitedParents[pid]) {
visitedParents[pid] = true;
batchParents.push(pid);
}
});
 
if (!batchParents.length) break;
 
var gr = new GlideRecord('kb_category');
// Only active categories if table supports it
gr.addActiveQuery && gr.addActiveQuery();
 
var pField = 'parent_id';
var qc = gr.addQuery(pField, batchParents[0]);
for (var i = 1; i < batchParents.length; i++) {
qc.addOrCondition(pField, batchParents[i]);
}
gr.query();
 
var nextFrontier = [];
while (gr.next()) {
var childId = gr.getUniqueValue();
var labelC = gr.getValue(getLabelField(gr));
var parentId = gr.getValue(pField); // parentId is a category or KB ref
 
if (!nodes[childId]) nodes[childId] = { id: childId, label: labelC, children: [] };
else nodes[childId].label = labelC;
 
parentMap[childId] = parentId || '';
 
if (parentId) nextFrontier.push(childId);
}
 
frontier = nextFrontier;
}
 
// 3) Wire children to their parents
for (var childIdF in parentMap) {
var parentIdF = parentMap[childIdF];
var childNode = nodes[childIdF];
if (parentIdF && nodes[parentIdF]) {
nodes[parentIdF].children.push(childNode);
}
}
 
// 4) Sort siblings by label
function sortRec(list) {
list.sort(function (a, b) {
return (a.label || '').localeCompare(b.label || '');
});
list.forEach(function (n) {
if (n.children && n.children.length) sortRec(n.children);
});
}
sortRec(roots);
 
// 5) Return the array in a named output property for easy binding
return { items: roots };
}
``
Version history
Last update:
2 hours ago
Updated by:
Contributors