Home / Question Hub / Scripting
Scripting Questions
Client Scripts, Business Rules, Script Includes, UI Actions, core GlideRecord scripting and hands-on scenario challenges — the rounds where most ServiceNow interviews are won or lost.
No questions match your search.
Client Scripts
21 questionsA Client Script is JavaScript that runs in the user's browser against a form (or list cell). There are four types: onLoad (form loads), onChange (a specific field's value changes), onSubmit (form is submitted — can block submission), and onCellEdit (a value is edited directly in a list).
Yes. During form load, onChange scripts fire for their field with isLoading set to true. The default template returns early on isLoading — remove or adjust that guard and the logic runs at load time too.
function onChange(control, oldValue, newValue, isLoading) {
// Removed the "if (isLoading) return;" guard so this
// also runs while the form is loading.
if (newValue === '') {
return;
}
g_form.setMandatory('assignment_group', true);
}
Use an onCellEdit Client Script on the state field. It receives the new value and a callback; passing false to the callback cancels the cell save.
function onCellEdit(sysIDs, table, oldValues, newValue, callback) {
var saveAndClose = true;
if (newValue === '7') { // 7 = Closed
g_form.addErrorMessage('Incidents cannot be closed from the list view.');
saveAndClose = false;
}
callback(saveAndClose);
}
- g_form.getReference() with a callback — quick lookup of one referenced record, but pulls the whole row.
- GlideAjax — calls a client-callable Script Include; the recommended way for anything non-trivial because you return only what you need.
- Display Business Rule + g_scratchpad — best when the data is known at form load; zero extra round trips.
Client-side GlideRecord also exists but is discouraged: it is synchronous-ish, unsupported in the Service Portal, and leaks query logic into the browser.
For the same event, Client Scripts execute first, then UI Policies run in ascending Order. Practically: if both touch the same field attribute, the UI Policy wins because it applies last. That is why "my client script makes the field editable but it stays read-only" is almost always a UI Policy applied afterwards.
- Dictionary — mandatory everywhere, every form, every API. Strongest, least flexible.
- Data Policy — enforced server-side too (imports, web services), can push to UI.
- UI Policy — conditional, declarative, form-only.
- Client Script —
g_form.setMandatory('field', true)when you need scripted logic.
Interview bonus: mention that UI-only enforcement (UI Policy / Client Script) can be bypassed by imports and APIs — Data Policy cannot.
When checked (the default for new scripts), the script runs in a sandbox with no access to window, document, DOM manipulation or jQuery — only the supported APIs like g_form. Unchecking it allows direct DOM access, which works in the classic UI but breaks in the Service Portal and is unsupported. Keep it checked unless you have a documented legacy reason.
g_form.getValue() only works for fields present on the form. Options: add the dot-walked field to the form layout (then g_form.getValue('caller_id.email') works), use g_form.getReference('caller_id', callback), or use GlideAjax for anything deeper than one hop. For form-load needs, a display Business Rule filling g_scratchpad is cleanest.
Only onCellEdit client scripts run on list edits. onLoad, onChange and onSubmit are form-only. Server-side protections (Business Rules, Data Policies, ACLs) still apply — which is exactly why validation must never live only in client scripts.
Default to a UI Policy for show/hide, read-only and mandatory driven by simple conditions — it is declarative, ordered, reversible ("reverse if false") and requires no code. Use a Client Script when you need logic a condition builder can't express: computed values, GlideAjax calls, messages, onSubmit validation, or onCellEdit.
g_form (GlideForm) is the client-side API for the current form. Five you should be able to quote cold:
g_form.getValue('state');
g_form.setValue('impact', '2');
g_form.setMandatory('short_description', true);
g_form.setVisible('u_internal_notes', false);
g_form.addErrorMessage('Assignment group is required for P1.');
function onSubmit() {
if (g_form.getValue('assignment_group') === '') {
g_form.addErrorMessage('Pick an assignment group first.');
return false; // blocks the submit
}
return true;
}
Returning false from an onSubmit script cancels the save. Remember this is client-side courtesy only — pair it with a before Business Rule or Data Policy for real enforcement.
The Global checkbox means "run on every view of this table." Uncheck it and a View field appears — pick the view the script should be limited to. One script record per view if you need several.
g_form— the current form.g_user— logged-in user:g_user.hasRole('itil'),g_user.userID.g_scratchpad— read-only data placed by display Business Rules.GlideAjax— bridge to client-callable Script Includes.g_list— in list contexts (onCellEdit, list UI actions).
Have one story rehearsed. A strong template: an onChange script on assignment_group that calls GlideAjax to fetch the group's manager and on-call roster, populates assigned_to choices, and shows a warning if the group has no active members — with the heavy lookup in a Script Include so the client code stays thin. Explain why each piece lives where it does; that's what is being tested.
Common honest answers: forgetting to mark the Script Include client callable; the asynchronous callback firing after the user already moved on; trying to return an object instead of a string (always JSON.stringify on the server and JSON.parse on the client); and ACL surprises when the SI queried tables the end user couldn't read.
Because each has a niche: getReference() only works for a reference field on the form and returns the entire record (wasteful). A display BR only helps with data known at form load. GlideAjax is the right tool when the lookup depends on user input after load, needs parameters, or must return a computed result.
Return one JSON string from the Script Include and parse it on the client:
// Script Include (client callable)
getUserInfo: function () {
var result = {};
result.name = '...';
result.email = '...';
result.vip = true;
return JSON.stringify(result);
}
// Client Script callback
ga.getXMLAnswer(function (answer) {
var info = JSON.parse(answer);
g_form.setValue('u_vip', info.vip);
});
GlideAjax is asynchronous — the onSubmit function returns before the server response arrives, so there is nothing left to cancel. Workarounds: the synchronous getXMLWait() (blocks the browser, not available in Service Portal), or the two-phase pattern: cancel the first submit, validate in the callback, set a flag, and re-submit programmatically if valid.
- Create a Script Include and check Client callable.
- Extend
AbstractAjaxProcessorviaObject.extendsObject. - Read inputs with
this.getParameter('sysparm_...')— and validate them. - Return a string (JSON.stringify for structures).
- Keep business logic in a separate, testable Script Include; the AJAX class is a thin façade.
Good candidates mention execution-order surprises (UI Policies overriding their script), that client-side validation is bypassable so the server must re-enforce, and that fewer client scripts equals faster forms — consolidating five onChange scripts into one UI Policy plus one script measurably improved form load.
Business Rules
18 questionsA Business Rule is server-side script that runs when records are queried, inserted, updated or deleted. Types by timing: before (modify/validate the record pre-write), after (act on other records post-write, same transaction), async (queued, runs later on a scheduler worker), display (runs before the form renders, feeds g_scratchpad), and query (appends conditions before a query executes — row-level security).
After runs synchronously in the user's transaction, immediately after the database write — the user waits for it. Async is queued and executed by a scheduler worker after the transaction completes — the user doesn't wait, but previous is null and there is a (usually small) delay. Rule of thumb: user-visible consequences → after; notifications, integrations, heavy work → async.
current (the record being processed), previous (its values before the change — null in async and on insert), gs (GlideSystem), and in display rules g_scratchpad for shipping data to the client.
It runs on the server when a form is requested, before rendering, and anything you put on g_scratchpad becomes readable in client scripts. Use case: show a banner on an incident when the caller has other open P1s — one query at load time in the display BR beats a GlideAjax round trip after load.
(function executeRule(current, previous /*null when async*/) {
g_scratchpad.callerOpenP1s = new IncidentUtils()
.countOpenP1sForCaller(current.getValue('caller_id'));
})(current, previous);
A before query BR mutates the query itself — extra current.addQuery() conditions are appended before the database is hit. Primary objective: row-level read security and data segregation. Use case: non-ITIL users should only see incidents where they are the caller:
(function executeRule(current, previous) {
if (!gs.hasRole('itil')) {
current.addQuery('caller_id', gs.getUserID());
}
})(current, previous);
A query BR filters rows before the database returns them — clean lists, correct row counts, cheap. Read ACLs evaluate per record and per field after retrieval — stronger (they protect every access path and individual fields) but produce the "Number of rows removed by security constraints" message and cost more. Best practice: query BR for row-level segregation, ACLs as the authoritative security layer.
Business Rules on the "Global" table: they define functions loaded into every server-side scope, callable from anywhere. They are legacy — they load on every transaction whether used or not. Use on-demand Script Includes instead; expect the interviewer to want exactly that answer.
(function executeRule(current, previous) {
if (current.getValue('assignment_group') === '') {
gs.addErrorMessage('Assignment group is mandatory for this state.');
current.setAbortAction(true);
}
})(current, previous);
A before BR calling current.setAbortAction(true) aborts the insert/update/delete. Unlike client validation, this also stops imports, APIs and background updates.
current.operation() returns 'insert', 'update' or 'delete'. On before-insert you can also use current.isNewRecord(). The cleanest approach, though, is checking the right operation checkboxes on the BR record so the script never runs for the wrong operation at all.
Fire an event with gs.eventQueue('incident.escalated', current, parm1, parm2) (register it in the Event Registry first) and create a Notification that fires on that event. You can also build the notification directly on record conditions, but event-driven is more flexible and decouples the trigger from the message.
Both conditions are appended to the same query and ANDed: active=true AND active=false matches nothing, so the reference field's lookup returns zero records. The lesson: query BRs apply to every query on the table, including reference qualifiers — a common source of "my lookup is empty" tickets.
Before Business Rules. The platform is already about to write the record — just assign the fields. Calling current.update() forces a second write and re-enters the rule engine, causing recursion and duplicate audit entries. (It's also a smell in after BRs; if you must update current after write, question the design.) This is Hard Prohibition #2 in the Now Baba style guide.
Async wins: user doesn't wait, ideal for integrations, notifications and heavy queries; failures don't roll back the user's save. After wins: runs immediately and in-transaction, has access to previous, and guarantees ordering with the save. If the logic needs old values or must complete before the user sees the result, async is disqualified.
Yes — fetch the sysauto_script record and hand it to the trigger synchronizer:
var grJob = new GlideRecord('sysauto_script');
if (grJob.get('name', 'Nightly Group Audit')) {
SncTriggerSynchronizer.executeNow(grJob);
}
Mention the design question too: if a BR needs a job, an event + Script Action or an async BR is often the cleaner pattern.
Reuse (the same logic serves BRs, jobs, fix scripts and REST), testability (you can exercise a Script Include from a background script), and maintainability — a thin BR shows when logic runs, the Script Include shows what it does. The Now Baba style guide draws the line at ~20 lines: beyond that, the logic moves to a Script Include.
gs.info()/gs.debug()with an artifact prefix, then filter syslog.- The Script Debugger — breakpoints in server scripts for your own session.
- Debug Business Rule session debug module — logs which BRs fired and in what order.
- Reproduce in a background script by faking the current record.
Any logic that needs previous (it's null in async), must modify the record before it is saved (that's before-BR territory), must abort the operation, or whose result the user must see immediately on the form after save. Example: recalculating priority from impact/urgency — has to be a before rule.
Whenever the client needs server data that is known the moment the form loads — related record counts, the caller's VIP flag, a config property. The display BR ships it in the same page load via g_scratchpad; GlideAjax would cost an extra asynchronous round trip and a flicker while the answer arrives.
previous is null when a rule runs async (and on insert). Any unguarded previous.getValue() throws a null reference at runtime — in production, from a queue, where nobody sees it fail. Guard every access: if (previous && previous.getValue('state') !== current.getValue('state')).
Script Includes
15 questionsA reusable server-side script library that loads on demand — only when called, unlike Global Business Rules. It's where business logic lives; Business Rules, scheduled jobs, flows and REST APIs should all be thin callers into Script Includes.
- Instantiable classes —
Class.create()+ prototype; created withnew. - Static / classless helpers — a plain object literal or standalone function for pure utilities.
- Client-callable — extends
AbstractAjaxProcessor, reachable from the browser via GlideAjax. - Extending classes —
Object.extendsObject(ParentClass, {...})to specialize existing behavior.
By convention, methods prefixed with an underscore (_buildEncodedQuery) are private: internal helpers that callers must not invoke directly. Rhino doesn't enforce it — the underscore is a contract. In client-callable Script Includes the platform does refuse to expose underscore-prefixed methods to GlideAjax, which makes the convention a real security line there.
// Instantiable
var engine = new GroupAuditEngine();
engine.run();
// Static helper
DateUtils.toGlideDateTime('2026-07-10');
In Global scope the name resolves automatically; from a scoped app call global.GroupAuditEngine (if it's accessible to all scopes).
var ga = new GlideAjax('PriceCalculatorAjax');
ga.addParam('sysparm_name', 'getFinalPrice');
ga.addParam('sysparm_base_price', g_form.getValue('u_price'));
ga.getXMLAnswer(function (answer) {
g_form.setValue('u_final_price', answer);
});
The Script Include must be client callable and extend AbstractAjaxProcessor. sysparm_name selects the method; other sysparm_* values arrive via this.getParameter().
Through this: this._persistFindings(). Watch the classic trap — inside a nested callback or forEach, this changes; capture it first with var self = this;.
initialize() is the constructor of a Class.create() Script Include — it runs automatically on new MyClass(args). Use it to set instance state (options, caches, property reads); keep it cheap, since every instantiation pays for it.
gs.include('ScriptIncludeName') evaluates a Script Include into the current scope — a legacy mechanism from before automatic name resolution. In modern Global-scope code you simply reference the class name; you'll mostly meet gs.include() when maintaining old code.
On the Script Include, set Accessible from: All application scopes. Then in the scoped app call it with the scope prefix: new global.IncidentUtils().getOpenCount(). Without that accessibility setting the scoped app cannot see it at all.
A client-callable SI (checkbox on the record) extends AbstractAjaxProcessor and is reachable from any user's browser via GlideAjax — which makes it an attack surface: validate every parameter, check roles, and never trust sysparm input. A server-only SI can only be invoked by other server code.
Yes. Server code can instantiate it and call methods directly. The catch: methods written for GlideAjax read inputs via this.getParameter(), which is undefined server-side — so well-designed AJAX methods accept ordinary arguments as a fallback, or better, delegate to a separate util class both paths share.
You can call it, but you cannot block the submission with an async response — the form is gone before the answer returns. Either use getXMLWait() (synchronous; desktop UI only) or the two-phase submit pattern: abort, validate in the callback, then re-submit with a flag.
Yes — instantiate the class and call the method, passing values as normal arguments rather than sysparms. If the method only reads this.getParameter(), refactor: move the logic into a plain util method both the AJAX wrapper and the server caller use.
Yes, via GlideAjax — but if the data is known at load time, a display Business Rule with g_scratchpad is faster (no extra round trip). Use onLoad + GlideAjax only when the lookup can't be predicted server-side.
var engine = new GroupAuditEngine({ batchSize: 100 });
gs.info('[BG] findings: ' + engine.run());
Exactly like any server script — which is also the standard way to test a Script Include before wiring it to rules and jobs.
UI Actions
10 questionsA configurable button, link or context-menu item on forms and lists (e.g. "Resolve", "Create Problem"). Its script can run server-side, client-side, or both — which is what makes it a favorite interview topic.
UI Actions are inherited by child tables. To change behavior on the child: create a UI Action on the extended table with the same Action name and your new logic, and add a condition on the parent's action to exclude the child table (e.g. current.getTableName() === 'task') so both don't render together.
By placement checkboxes: form button, form link, form context menu, list button, list context menu, list choice (bottom of list, acts per selected row), and list link. One record can enable several placements at once.
// Client checkbox = true, Onclick = validateAndSubmit()
function validateAndSubmit() {
if (g_form.getValue('close_notes') === '') {
g_form.addErrorMessage('Close notes are required.');
return false;
}
gsftSubmit(null, g_form.getFormElement(), 'resolve_incident');
}
// Server side of the same UI Action
if (typeof window === 'undefined') {
runServerLogic();
}
function runServerLogic() {
current.setValue('state', '6');
current.update();
action.setRedirectURL(current);
}
gsftSubmit() submits the form and re-invokes the same UI Action on the server — the third argument is the UI Action's Action name. The typeof window === 'undefined' guard ensures the server block only runs server-side.
The first parameter is the originating control (button element). Passing null lets the platform resolve it — what actually matters is the third parameter, the action name, which tells the server which UI Action's script to execute after the submit.
Client-side: window.open('/sp?id=sc_cat_item&sys_id=...') or set window.location. Server-side: action.setRedirectURL('sp?id=...') after doing any work. Pass context (like the incident sys_id) as URL parameters so the producer can pre-fill.
The UI Action — one record, both halves: an Onclick client function that validates, then gsftSubmit() hands control to the server portion of the same record. No other single artifact type does this.
Use the UI Action Visibility related list on the UI Action: add records per view with visibility "Include" (show only there) or "Exclude" (hide there). This is declarative and preferred over sniffing the view in the condition script.
g_list.getChecked() returns a comma-separated string of the sys_ids of checked rows. Split it to iterate: g_list.getChecked().split(','). It only ever returns checked rows — to act on everything matching the filter, do it server-side with a GlideRecord over the same query.
Make it a List choice UI Action — the platform then executes the server script once per selected record with that record as current. Alternatively, a list button can read g_list.getChecked() client-side and hand the sys_id list to a Script Include via GlideAjax.
General Scripting
11 questionsA Fix Script is a record: it's captured in update sets, moves through instances with your release, runs once as part of deployment, and leaves an audit trail. A Background Script is ad-hoc — typed into a module, executed immediately, tracked nowhere. Rule: exploratory poking in dev → background script; any data change tied to a release → fix script.
gr.setWorkflow(false) suppresses Business Rules, workflows, notifications, SLA engines and auditing for subsequent operations on that GlideRecord — used in bulk data operations. Per the Now Baba style guide (Hard Prohibition #13): every use requires a comment explaining why, because suppressing rules on a governed table is how audit findings get created.
gr.initialize() gives you an empty row: no default values, sys_id assigned at insert. gr.newRecord() creates the row with all default values populated and a sys_id already generated. If your table relies on dictionary defaults, newRecord() shows them immediately; with initialize() the defaults still apply at insert time, but you can't read them beforehand.
// By sys_id — fastest possible lookup
var grGroup = new GlideRecord('sys_user_group');
if (grGroup.get(groupSysId)) { /* found */ }
// By field/value — first match
if (grGroup.get('name', 'Service Desk')) { /* found */ }
It queries immediately, positions on the first match, and returns a boolean. For single-record lookups it beats the addQuery/query/next dance — and by sys_id it short-circuits query planning entirely (style guide §5.2).
grStaging.autoSysFields(false); // preserve sys_updated_on / by
grStaging.setWorkflow(false); // documented: data backfill, rules not wanted
grStaging.setValue('u_migrated', true);
grStaging.update();
autoSysFields(false) stops the platform from stamping sys_updated_on, sys_updated_by and sys_mod_count for that operation — standard for migrations and backfills where the original audit trail must survive.
They solve different problems. A Script Include is a library — it does nothing until called. A Script Action is a trigger — it runs automatically, asynchronously, whenever a specific event fires in the event queue. Typical pairing: the Script Action catches the event and delegates the actual work to a Script Include.
Client: onSubmit client script returning false. Server: before Business Rule with current.setAbortAction(true) (plus gs.addErrorMessage() so the user knows why). Always implement the server one — the client one is UX, the server one is enforcement.
getXML(callback)— async; callback receives the full XML response; you extract the answer attribute yourself.getXMLAnswer(callback)— async; callback receives just the answer string. Use this one.getXMLWait()— synchronous; blocks the browser until the response arrives, then readgetAnswer(). Not supported in Service Portal; use only when blocking is genuinely required.
var start = new GlideDateTime(grIncident.getValue('opened_at'));
var end = new GlideDateTime(grIncident.getValue('resolved_at'));
var duration = GlideDateTime.subtract(start, end); // GlideDuration
gs.info('[Demo] elapsed: ' + duration.getDisplayValue());
Server-side, GlideDateTime.subtract() returns a GlideDuration. There is no GlideDateTime in the browser — if the client needs a server-accurate difference, compute it in a Script Include via GlideAjax.
GlideSysAttachment.copy('incident', incidentSysId,
'problem', problemSysId);
GlideSysAttachment.copy(sourceTable, sourceSysId, targetTable, targetSysId) copies all attachments across in one call — no manual sys_attachment record handling needed.
Prefix the method with an underscore: _buildEncodedQuery: function () {...}. It signals "internal only," is excluded from GlideAjax exposure in client-callable Script Includes, and per the style guide, private methods may assume inputs were already validated by the public method that called them.
Scripting Scenarios
14 questionsvar gaIncident = new GlideAggregate('incident');
gaIncident.addEncodedQuery('sys_updated_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()');
gaIncident.addAggregate('COUNT');
gaIncident.groupBy('state');
gaIncident.query();
while (gaIncident.next()) {
gs.info('[Report] state ' + gaIncident.getValue('state') +
': ' + gaIncident.getAggregate('COUNT'));
}
Key point interviewers look for: GlideAggregate, not counting rows in a GlideRecord loop.
var grIncident = new GlideRecord('incident');
grIncident.addEncodedQuery('sys_created_onONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()');
grIncident.orderByDesc('sys_created_on');
grIncident.setLimit(5);
grIncident.setColumns('number,sys_created_on');
grIncident.query();
while (grIncident.next()) {
gs.info('[Report] ' + grIncident.getValue('number'));
}
Score extra with setLimit(5) and setColumns() — both straight from the style guide.
var gaIncident = new GlideAggregate('incident');
gaIncident.addAggregate('COUNT');
gaIncident.groupBy('category');
gaIncident.groupBy('subcategory');
gaIncident.query();
while (gaIncident.next()) {
gs.info('[Report] ' + gaIncident.getValue('category') + ' / ' +
gaIncident.getValue('subcategory') + ': ' +
gaIncident.getAggregate('COUNT'));
}
Show the standard instantiable form: Class.create(), an initialize() that takes an options object, one public entry point (e.g. run()), private underscore helpers for each step, and the mandatory type property. Then say the magic sentence: "the Business Rule that uses this stays under five lines." See the full skeleton in the style guide, §4.
var grSecIncident = new GlideRecordSecure('sn_si_incident');
if (!grSecIncident.canCreate()) {
gs.addErrorMessage('You do not have access to create Security Incidents.');
} else {
grSecIncident.initialize();
grSecIncident.setValue('short_description', current.getValue('short_description'));
grSecIncident.insert();
}
The two ingredients: canCreate() for the access check and GlideRecordSecure so ACLs are actually honored (style guide §10).
function findDuplicates(list) {
var seen = {};
var dupes = [];
var i;
for (i = 0; i < list.length; i++) {
if (seen.hasOwnProperty(list[i])) {
dupes.push({ value: list[i], index: i, firstIndex: seen[list[i]] });
} else {
seen[list[i]] = i;
}
}
return dupes;
}
Pure ES5 — no Set, no includes(), because Rhino (style guide §1).
var dob = new GlideDateTime(grUser.getValue('u_date_of_birth'));
var now = new GlideDateTime();
var years = gs.dateDiff(dob.getDisplayValue(), now.getDisplayValue(), true);
var age = Math.floor(years / (365.25 * 24 * 60 * 60));
gs.info('[Demo] age: ' + age);
gs.dateDiff(a, b, true) returns seconds; divide out to years. Alternatively subtract GlideDateTimes for a GlideDuration.
var grEmp = new GlideRecord('u_employee');
grEmp.orderByDesc('u_salary');
grEmp.setLimit(2);
grEmp.query();
grEmp.next(); // highest
if (grEmp.next()) { // second highest
gs.info('[Demo] ' + grEmp.getValue('name'));
}
Order descending, limit 2, skip one. Mention ties if you want to impress — a GROUP BY on distinct salary values handles duplicates.
An after Business Rule on change_request, condition "state changes to Implement", that inserts the change_task records (or better, calls a Script Include that does). Use previous.getValue('state') vs current.getValue('state') to detect the transition, and guard against duplicates by checking whether tasks already exist for the change.
An after/async Business Rule on sys_user (condition: active changes to false) that queries open incidents assigned to that user and clears or reassigns them — one batched query, not per-incident lookups:
var grIncident = new GlideRecord('incident');
grIncident.addActiveQuery();
grIncident.addQuery('assigned_to', current.getUniqueValue());
grIncident.query();
while (grIncident.next()) {
grIncident.setValue('assigned_to', '');
grIncident.update();
}
var gaUser = new GlideAggregate('sys_user');
gaUser.addAggregate('COUNT', 'email');
gaUser.groupBy('email');
gaUser.addHaving('COUNT', '>', '1');
gaUser.query();
while (gaUser.next()) {
gs.info('[Dupes] ' + gaUser.getValue('email') + ' x' +
gaUser.getAggregate('COUNT', 'email'));
}
GlideAggregate + groupBy + addHaving — the SQL HAVING COUNT > 1 pattern.
var chain = [];
var grUser = new GlideRecord('sys_user');
grUser.get(userSysId);
var managerId = grUser.getValue('manager');
while (managerId && chain.length < 10) { // depth guard
if (!grUser.get(managerId)) {
break;
}
chain.push(grUser.getValue('name'));
managerId = grUser.getValue('manager');
}
Walk the manager reference with a depth guard against cycles. For one hop only, dot-walking (grIncident.assigned_to.manager.name) is enough.
A daily Scheduled Job querying incidents in Resolved state where resolved_at is older than N days, then closing each. Key details: read N from a system property, use an encoded query with a relative date, and chunk with setLimit if volumes are large (style guide §11.7).
var days = parseInt(gs.getProperty('x.inc.autoclose_days', '3'), 10);
var grIncident = new GlideRecord('incident');
grIncident.addQuery('state', '6'); // Resolved
grIncident.addQuery('resolved_at', '<=', gs.daysAgoStart(days));
grIncident.query();
while (grIncident.next()) {
grIncident.setValue('state', '7'); // Closed
grIncident.update();
}
var grIncident = new GlideRecord('incident');
grIncident.addQuery('caller_id.name', 'STARTSWITH', 'A');
grIncident.query();
Dot-walk inside addQuery — the platform joins to sys_user for you. No second GlideRecord, no loop (Hard Prohibition #3).