🟢
Updated recently
Last updated:

Quick Answer: ServiceNow GlideRecord server side JavaScript is the platform’s primary way to read and write records from server contexts — business rules, script includes, scheduled jobs, scripted REST — and the whole job comes down to three habits: query once with an explicit condition set, aggregate instead of looping, and never issue a query per row inside a while loop. Get those right and your code survives production; get them wrong and the instance gets slow long before your code gets wrong.

Where ServiceNow GlideRecord Server Side JavaScript Actually Runs

Before the API, the context — because the same script behaves differently depending on where it executes, and half of the “why is this slow” questions on real projects are really “why is this running 600 times” questions.

Context Runs Typical Use Watch Out For
Before business rule Synchronously, before the DB write Validation, field defaults, derived values current only; no current.update() here — you are already in a write
After business rule Synchronously, after the write Notifications, cascade updates, integrations Fires per row on bulk operations. This is where most performance damage originates
Async business rule Queued after the transaction commits Work that does not need to block the user Runs once per transaction batch, not per row — your friend for anything heavy
Script include On demand, wherever called Reusable logic, GlideAjax endpoints Cacheable includes are instantiated once; non-cacheable ones are rebuilt per call
Scheduled job / background script On a schedule or manually Batch fixes, reports, data hygiene The right home for anything that touches more than a few hundred records
Scripted REST / Flow Designer script step Per inbound request or flow step APIs, orchestration glue Runs under the caller’s context — check ACLs and avoid unbounded queries

The pattern worth internalising: anything per-record and cheap belongs in a business rule; anything per-set and expensive belongs in an async rule or a scheduled job. Most ServiceNow performance incidents are one of the two placed in the wrong row of that table.

The Query You Will Write Fifty Times a Week

Every GlideRecord follows the same shape — instantiate, condition, order, limit, query, iterate. The discipline is in the condition and the limit:

var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('assignment_group', current.assignment_group);   // sys_id, not display name
gr.addQuery('state', 1, 2);                                  // state IN (1, 2)
gr.orderByDesc('sys_created_on');
gr.setLimit(100);                                            // always bound it
gr.query();

while (gr.next()) {
    gs.info(gr.getValue('number') + ' :: ' + gr.getShortDescription());
}

Notes that matter in practice. Pass sys_ids, not display names — addQuery('assignment_group', 'Support L2') resolves against the label and breaks the moment someone renames the group. Use addQuery('state', 1, 2) for an IN list rather than chaining OR conditions, and reach for addEncodedQuery() when the condition comes from a URL or a UI action — but never build it by string-concatenating user input, because an encoded query is a query language and user input is not a safe fragment of one. Bound every query with setLimit() or a real date range. Unbounded queries over tables like incident or sys_audit are the single most common cause of a slow list view becoming a slow instance.

Fields are pulled lazily: only the fields you touch are actually fetched, so asking for eight columns costs more than asking for two, and calling getValue() on a field you never selected triggers a round trip. Select what you use.

Count Without Looping: GlideAggregate

If your instinct after “query these records” is a while loop with a counter, stop — the database already knows how to count, and asking it once is orders of magnitude cheaper than asking once per row:

var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addQuery('opened_on', '>=', gs.daysAgoStart(7));
ga.groupBy('assignment_group');
ga.addAggregate('COUNT');
ga.addHaving('COUNT', '>=', 25);        // only groups with 25+ open incidents
ga.query();

while (ga.next()) {
    var group  = ga.getValue('assignment_group');
    var count  = ga.getAggregate('COUNT');
    gs.info(group + ': ' + count + ' open incidents in the last 7 days');
}

GlideAggregate supports COUNT, MIN, MAX, AVG and SUM, with groupBy() and addHaving() doing the grouping and filtering work. Where it gets awkward: you cannot reliably pull display values of the grouped field without a follow-up lookup, and complex two-level aggregations sometimes need a scripted fix rather than a clean API. When that happens, take the hit consciously — one extra query to resolve sys_ids into names is fine; one query per row is not.

Performance Rules That Separate Junior From Senior

Anti-Pattern Why It Hurts Do This Instead
Query inside a while loop N queries for N rows; transaction time grows linearly Collect sys_ids first, run one query with an IN list
Query on an unindexed field across millions of rows Full table scan on a hot table Filter on indexed fields first (state, active, sys_created_on), then narrow
Heavy work in an “after” rule Runs synchronously, per row, inside the user’s transaction Move to an async rule or a scheduled job
gr.update() inside a loop One write per row; also re-triggers rules gr.updateMultiple(), or stage rows and write once
current.update() from an after rule on the same table Recursion until the platform stops you Guard with current.changesTo() or a handler-level flag
Loose matching on short_description Wildcard scans with unpredictable cost Structured fields, or accept the scan happens in a scheduled job, not a request

Two smaller habits worth the ink. Use current.changesTo('state') or current.previous() so your rule only fires on a genuine transition — most “this fires too often” complaints are rules running on every keystroke-level update. And in a before rule you are editing a record that has not been written yet, so validation there is free and write-based validation afterwards costs a round trip plus a rollback.

Scope, Security and the Query You Should Be Using

Scoped applications change the rules: a script running in a scoped app can only touch tables and fields its application scope is permitted to touch, and new GlideRecord() in that context behaves as a secured query by default. In global scope — where a lot of legacy Indian enterprise code still lives — you get raw access and inherit the ACLs of whoever’s context triggered the script. Where you must honour ACLs explicitly, use GlideRecordSecure, which applies ACL evaluation to the query itself rather than trusting the caller.

The practical version: write as if every record your script touches must pass a user’s ACLs, because in a delegated-view or portal context it does. Scripts that “work in the background” while quietly bypassing field-level security are the kind of thing an audit finds, and in BFSI and telecom tenants the audit is not hypothetical.

What Actually Happens at Work

The incident I keep coming back to: a financial-services tenant, month-end, with an integration team under pressure. A developer wrote a business rule on the account table so that when an account’s risk rating changed, the rule would loop through the account’s open cases, find each case’s owner, and append a note to a related record. Elegant in isolation. In the dev instance it processed an account with two cases in a blink.

At month-end an overnight job updated 1,400 accounts. Each update ran the rule; each rule run executed three queries in its loop; the total was over 4,000 queries in transactions that were already doing real work. The instance did not throw a friendly error — it simply took longer and longer, background jobs started timing out, and the morning stand-up opened with “is ServiceNow down?” It was not down. It was doing exactly what it had been told, four thousand times.

The repair was conceptually simple and organisationally harder. The queries collapsed into one pass keyed on the changed account ids; the work moved into an async business rule; a guard ensured the rule only ran when the risk rating had actually changed. The organisational part was agreeing that bulk-load paths needed testing with bulk-sized data — not four records clicked through by hand — which is a habit, not a technology. That is the difference between a developer who reads as senior on paper and one who is senior in a production incident: not the API knowledge, but the instinct to ask “how many times will this run?” before shipping. For the promotion discipline around getting a fix like this safely into production, the update sets guide covers the mechanics, and the admin vs developer comparison explains why that diagnosis is rarely one person’s job.

Debugging Without Guessing

When a script misbehaves, resist the urge to add gs.info() everywhere and redeploy. A cheaper ladder: run the query first in a background script against the same conditions and see the row count — often the logic is fine and the data is not what you assumed. Check whether the rule is firing more often than you think by logging current.sys_created_on and the trigger condition. Read the stack trace properly: ServiceNow tells you the rule, the line and the table, and more teams should believe it instead of re-reading their own code. Then codify the fix as an Automated Test Framework test, so the next developer gets told rather than discovered. ATF coverage is also the thing that most reliably separates mid-level developers in interviews, which brings us back to money: it is a large part of what the bands in the ServiceNow developer salary guide are paying for.

Training Resources

You learn query discipline by writing queries against a real instance and watching them behave badly. SkilBrill ServiceNow Training covers server-side scripting, GlideRecord and GlideAggregate patterns, Flow Designer and integrations across the Admin and Developer tracks, with hands-on instances, career support and placement assistance.

FAQ

What is GlideRecord in ServiceNow server side JavaScript?

The API for querying and manipulating records from server-side code — business rules, script includes, jobs and scripted REST endpoints. You instantiate it against a table, add conditions, call query(), then iterate with next().

How do I avoid slow GlideRecord queries in production?

Bound every query with conditions and a limit, filter on indexed fields first, never query inside a loop, and use GlideAggregate for counting. Move bulk work out of synchronous business rules into async rules or scheduled jobs.

GlideRecord or GlideAggregate — which should I use?

GlideRecord when you need the records themselves; GlideAggregate when you need counts, sums or averages. If your script’s first instinct is a counter variable in a loop, GlideAggregate is the right answer almost every time.

Where does server-side JavaScript run in ServiceNow?

In before and after business rules, async rules, script includes, scheduled jobs, background scripts, scripted REST APIs and Flow Designer script steps. The execution context determines what data you can see and how often your code runs.

Do I need to know Java or only JavaScript for ServiceNow development?

JavaScript, plus the platform’s own API surface — GlideRecord, GlideAggregate, GlideSystem, GlideRecordSecure and the async APIs. The Java-style OOP conventions in script includes matter; Java the language does not.