logo

NJP

Build a Transform Data Broker for Knowledge Base Category Hierarchy

New article articles in ServiceNow Community · Dec 26, 2025 · article

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 };

}

``

View original source

https://www.servicenow.com/community/next-experience-articles/build-a-transform-data-broker-for-knowledge-base-category/ta-p/3456634