The platform concepts, scripting APIs and admin practices a ServiceNow developer or admin interview checks, with the traps that catch people in real instances.
Platform basics
An instance is your own copy of the platform (typically dev → test → prod), and everything in it is a record in a table: incidents, users, even scripts and forms.
sys_db_object lists tables, sys_dictionary defines fields (type, max length, default, attributes), sys_choice holds choice values. Custom tables and fields get a u_ prefix in the global scope; scoped apps prefix them with their namespace (x_acme_app_…).
sys_id: a 32-character hexadecimal GUID, the primary key of every record. Every record also carries sys_created_on, sys_created_by, sys_updated_on, sys_updated_by and sys_mod_count.
Table extension: a child table inherits all of its parent’s fields and adds its own; sys_class_name says which class a record really is. Querying the parent returns child records too.
task is the base for work records: incident, problem, change_request, sc_request, sc_req_item, sc_task and more share number, state, assigned_to, assignment_group, priority, active.
A reference field stores the target’s sys_id; dot-walking (current.caller_id.manager.email) follows references in scripts, lists and conditions.
ITSM processes
Process
Table
Goal
Know
Incident
incident
restore service fast
priority from impact × urgency (a lookup, 1 Critical to 5 Planning); New → In Progress → On Hold → Resolved → Closed (or Canceled)
Problem
problem
find and remove the root cause
root cause analysis, workaround, known error; one problem links many incidents
Change
change_request
control the risk of changes
Normal (assess, CAB authorizes), Standard (pre-approved template, low risk), Emergency (expedited, emergency CAB)
Request
sc_request → sc_req_item → sc_task
fulfil catalog orders
REQ = the order, RITM = one per item ordered, SCTASK = fulfilment work
Normal change states: New → Assess → Authorize → Scheduled → Implement → Review → Closed. The CAB (Change Advisory Board) weighs risk, impact and schedule conflicts.
Incident vs problem: an incident is about the symptom and the user; a problem is about the underlying cause.
CMDB
cmdb_ci is the base class; classes extend it (cmdb_ci_computer → cmdb_ci_server → cmdb_ci_linux_server; cmdb_ci_appl for applications, cmdb_ci_service for services).
Relationships live in cmdb_rel_ci (parent, child, type such as Depends on::Used by, Runs on::Runs); they drive impact analysis and dependency maps.
Discovery (through a MID Server inside your network) and Service Mapping populate it; the IRE (Identification and Reconciliation Engine) matches incoming data to existing CIs by identification rules and decides which source may update which attributes, preventing duplicates.
CSDM (Common Service Data Model) is ServiceNow’s recommended structure for services and CIs. CMDB Health dashboards track completeness, correctness and compliance.
Where scripts run
Script
Runs
When / notes
Business rule: before
server
before the database write; change current directly, no update()
Business rule: after
server
after the write; update other records
Business rule: async
server, as a scheduled job
after the transaction, so the user doesn’t wait; previous isn’t available
Business rule: display
server
before the form loads; fill g_scratchpad for client scripts
Business rule: query
server
before a query runs; silently adds conditions (row-level filtering)
run once (on app install, upgrade or by hand) for data fixes
Business rules run on insert, update, delete and/or query, filtered by conditions, in ascending Order (default 100).
onChange(control, oldValue, newValue, isLoading, isTemplate): start with if (isLoading || newValue === '') return;.
onLoad client scripts run before UI policies, so on a conflict the UI policy wins. Prefer UI policies over scripts for mandatory, visible and read-only.
(function executeRule(current, previous /*null when async*/) { // before update on incident: block resolving without close notes if (current.state.changesTo(6) && current.close_notes.nil()) { gs.addErrorMessage('Close notes are required to resolve'); current.setAbortAction(true); }})(current, previous);
JavaScript
GlideRecord & GlideAggregate
Method
Does
new GlideRecord('incident')
a query object on a table
addQuery('priority', '<=', 2)
condition; operators =, !=, >, IN, CONTAINS, STARTSWITH, INSTANCEOF…
skip business rules and engines / don’t touch sys_updated_*
var inc = new GlideRecord('incident');inc.addActiveQuery();inc.addQuery('priority', '<=', 2);inc.addEncodedQuery('assignment_groupISEMPTY');inc.orderByDesc('sys_created_on');inc.setLimit(50);inc.query();var numbers = [];while (inc.next()) { numbers.push(inc.getValue('number')); // not inc.number: that's a GlideElement}
JavaScript
OR conditions: var qc = gr.addQuery('priority', 1); qc.addOrCondition('priority', 2);.
GlideRecord doesn’t enforce ACLs; GlideRecordSecure (or canRead(), canWrite()) does. GlideQuery is a newer, fluent, fail-fast query API.
Count with GlideAggregate, not getRowCount(): getRowCount fetches every matching record, while an aggregate runs a SQL COUNT. getAggregate returns a string.
var agg = new GlideAggregate('incident');agg.addQuery('active', true);agg.addAggregate('COUNT');agg.groupBy('priority');agg.query();while (agg.next()) { gs.info(agg.getValue('priority') + ': ' + agg.getAggregate('COUNT'));}
JavaScript
current, previous & gs
current is the GlideRecord being saved (business rules, UI action server code, record producer scripts). previous holds the values before this update: present in before/after update and delete rules, empty on insert, unavailable in async rules.
Field helpers: current.state.changes(), changesTo(v), changesFrom(v), current.field.nil(), current.operation() (insert/update/delete), current.isNewRecord(), current.setAbortAction(true) (cancel the write, in before rules).
gs method
Returns / does
gs.info(), warn(), error(), debug()
log (scoped-safe); gs.log() and gs.print() are global-only
gs.addInfoMessage(m), addErrorMessage(m)
message at the top of the form
gs.getUserID(), getUserName(), getUser()
current user’s sys_id, user name, a user object (isMemberOf('group'))
gs.hasRole('itil')
true if the user has the role, or admin
gs.getProperty('name', 'default')
a system property value
gs.eventQueue('name', gr, parm1, parm2)
fire an event (notifications, script actions)
gs.getMessage('key')
translated UI message
gs.nil(v), gs.isInteractive(), gs.daysAgo(n)
null/empty check, real user session?, date-time n days ago
Client APIs: g_form, g_user & GlideAjax
g_form
Does
getValue(f), setValue(f, value, displayValue)
read or set a field (pass the display value for references to save a round trip)
setMandatory(f, bool), setReadOnly(f, bool)
mandatory / read-only
setDisplay(f, bool) / setVisible(f, bool)
hide and collapse the space / hide but leave a gap (desktop UI)
g_user: userName, userID, firstName, lastName, getFullName(), hasRole('itil') (true for admins too), hasRoleExactly('itil') (no admin shortcut), hasRoleFromList('itil,admin').
GlideAjax calls a client-callable script include asynchronously: the first addParam is sysparm_name (the method), other parameters must start with sysparm_.
var UserUtils = Class.create(); // script include, "Client callable" checkedUserUtils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, { getManagerEmail: function () { var user = new GlideRecord('sys_user'); if (!user.get(this.getParameter('sysparm_user_id'))) return ''; return user.manager.email.toString(); }, type: 'UserUtils'});
JavaScript
function onChange(control, oldValue, newValue, isLoading, isTemplate) { if (isLoading || newValue === '') return; var ga = new GlideAjax('UserUtils'); ga.addParam('sysparm_name', 'getManagerEmail'); ga.addParam('sysparm_user_id', newValue); ga.getXMLAnswer(function (answer) { g_form.setValue('u_manager_email', answer); });}
JavaScript
getXMLAnswer(cb) hands you the answer string; getXML(cb) hands the XML response; getXMLWait() is synchronous, freezes the form and isn’t available to scoped apps.
Only data the form needs on load? Use a display business rule + g_scratchpad: no extra round trip.
ACLs, roles & policies
An ACL secures an object (incident for rows, incident.short_description for a field, incident.* for all fields) for an operation: read, write, create, delete (plus others such as execute, list_edit, report_on).
Inside one ACL, roles AND condition AND script (answer = true/false) must all pass. Among several ACLs on the same object, passing any one is enough.
Access to a field needs both the table ACL and the field ACL to pass. The most specific match wins: incident.field → task.field → *.field → incident.* → task.* → *.*.
Newer releases add Deny-Unless ACLs: evaluated before the usual Allow-If ones, and failing any one denies access.
Editing ACLs needs the elevated security_admin role. Grant roles to groups, not users. Debug with Session Debug → Security (or Access Analyzer).
Rows hidden by a read ACL show “rows removed by security constraints”; a query business rule filters silently.
UI policy
Data policy
Runs
browser, on forms
server, on forms, import sets and web services
Can do
mandatory, read-only, visible, plus scripts
mandatory and read-only only
Bypassed by
imports, APIs, scripts
(enforced at save)
Convert
“Convert this to Data Policy”
“Use as UI Policy on client”
Flows & integrations
Flow Designer (grouped under Workflow Studio in newer releases) is the recommended low-code engine: a trigger (record created/updated, schedule, catalog item, inbound email) → actions, flow logic, subflows, data pills. The legacy Workflow Editor still runs existing workflows; build new automation in flows.
IntegrationHub adds spokes (packaged actions for Slack, Jira, Azure AD…) and REST/SOAP steps inside flows; some spokes need a subscription.
Inbound: the Table API (GET /api/now/table/incident?sysparm_query=active=true&sysparm_limit=10&sysparm_fields=number), Import Set API, Attachment API, or a Scripted REST API with your own resources.
Outbound: sn_ws.RESTMessageV2 / sn_ws.SOAPMessageV2; execute() returns a response with getStatusCode() and getBody(). Reach on-premises systems through a MID Server.
Import sets: data source → staging table → transform map (field maps + scripts onStart, onBefore, onAfter, onComplete) → target table. ignore = true in onBefore skips a row.
Coalesce: fields marked coalesce act as the match key: a match updates that record, no match inserts. No coalesce means every row inserts (duplicates); several coalesce fields must all match.
Update sets & scoped apps
An update set records configuration changes as XML (sys_update_xml): business rules, client scripts, UI policies, ACLs, dictionary, forms, lists, catalog item definitions and variables, flows.
Not captured: data (incidents and other tasks, users, groups, CIs, test orders) and homepages unless added manually. Move data with XML export/import or import sets.
Lifecycle: complete it in dev → retrieve in the target → preview (fix collisions and missing dependencies) → commit; back out reverses a committed set. Batch related sets with a parent.
Always pick your own update set first; changes made in Default are easy to lose. Update sets don’t carry data the changes depend on.
Scoped applications get a namespace (x_acme_app), application access settings and cross-scope privileges; build them in Studio or App Engine Studio, store them in source control or the application repository. Prefer scopes for new custom apps.
Service Catalog
Catalog item: an orderable offering with variables (single-line text, select box, reference, checkbox, date, list collector, multi-row variable set…).
Variable sets reuse a group of variables across items. Catalog client scripts and catalog UI policies work on variables (g_form.getValue('variable_name')).
Record producer: a catalog form that creates a record in another table (an incident, an HR case); its script sets current from producer.variable_name.
Order guide: one form that orders several items based on rules and cascades shared variables.
Server-side, read answers with current.variables.variable_name. Fulfilment runs through a flow (or a legacy workflow) that creates approvals and SCTASKs.
SLAs, notifications & events
SLA definition: table, start, pause and stop conditions, duration and schedule. Each task gets task_sla records (in progress, paused, achieved, breached, cancelled).
SLA (with the customer), OLA (between internal teams), UC (underpinning contract, with a vendor). SLA flows notify and escalate at percentages of elapsed time.
Notifications fire on record insert/update or on an event; set recipients (users, groups, fields, event parm1/parm2) and content (${field}, ${mail_script:name}).
Events: register them in the event registry, fire with gs.eventQueue(); they’re processed asynchronously from the event queue and can trigger notifications or script actions.
ATF, portals & UI Builder
ATF (Automated Test Framework): tests built from steps (open a form, set values, submit, assert, impersonate, run server script), grouped in suites and scheduled; UI steps need a client test runner in a browser. Data a test creates is rolled back. Run it on sub-production, especially before upgrades.
Service Portal (/sp): pages → containers → rows → columns → widgets. A widget has an HTML template (AngularJS), CSS, a client controller and a server script (data, input, options, $sp); c.server.update() sends c.data to the server as input.
UI Builder (Next Experience) builds workspaces and portal pages from components, data resources, events and client scripts; configurable workspaces are the modern agent UI.
Performance, upgrades & debugging
No synchronous server calls from the browser: GlideRecord in client scripts, getXMLWait() and getReference() without a callback all block. Use GlideAjax with a callback or g_scratchpad.
Query only what you need: indexed fields in conditions, setLimit(1) for existence checks, GlideAggregate for counts; no GlideRecord queries inside loops (N+1).
Never current.update() in a before or after rule on the same table: it saves twice and can recurse. Set fields in a before rule; put heavy or integration work in async rules or events.
Don’t edit out-of-box records. Upgrades skip customized OOB files: review the skipped records list after each upgrade and choose to keep the customization, revert to base, or merge.
Upgrade path: clone production down, upgrade dev → test → prod, run the ATF regression suite, fix skipped records before prod.
Debug: System Logs (syslog), Scripts - Background for quick server tests, the Script Debugger (breakpoints in server scripts), Session Debug (business rules, security, SQL), Script Tracer, Field Watcher, the browser console for client scripts.
Gotcha
setWorkflow(false) turns off business rules, other script engines and auditing for that operation, so no history and nothing those rules would trigger. Use it only in deliberate data fixes, never to hide a recursion bug.
Quick answers
Before vs after business rule? Before changes the record being saved (no update()); after touches other records.
Async vs after? Async runs later as a scheduled job so the user doesn’t wait; previous is gone by then.
Client script vs UI policy? UI policy for declarative mandatory/visible/read-only; client script for logic, validation and server calls.
UI policy vs data policy? UI policy is client-side on forms only; data policy is enforced on the server for forms, imports and web services.
How do you call server code from a client script? GlideAjax to a client-callable script include, asynchronously.
How do you count records? GlideAggregate COUNT, not getRowCount().
What does coalesce do? Makes a field the match key in a transform: update on match, insert otherwise.
What’s not in an update set? Data: tasks, users, groups, CIs. Move data with XML export or import sets.
ACL evaluation? Roles, condition and script must all pass; one passing ACL per object is enough; table and field ACLs must both pass.
Standard vs normal vs emergency change? Pre-approved template / assessed and CAB-authorized / expedited with emergency CAB review.
What is a record producer? A catalog item that creates a record in a non-catalog table such as incident.
What is g_scratchpad? An object a display business rule fills on the server for client scripts to read on load.
What is a skipped record? A customized OOB file the upgrade didn’t overwrite, waiting for your decision.
Gotchas & traps
Pushing gr.sys_id or gr.number into an array inside a while (gr.next()) loop stores the same GlideElement reference every time; use gr.getValue('sys_id') or .toString().
gr.get() returns a boolean: check it before using the record.
An invalid field name in a query is dropped, not rejected, so the query can match every record: fatal before deleteMultiple() or updateMultiple() (glide.invalid_query.returns_no_rows changes this).
GlideRecord ignores ACLs: a script include called over GlideAjax runs for any logged-in user; check roles inside it.
g_form.getValue() returns a string (the sys_id for references); compare 'true', not true.
gs.log() and gs.print() don’t exist in scoped apps: use gs.info().
setVisible() leaves a gap on the desktop form; setDisplay() collapses it. Neither can hide a mandatory field with no value: call setMandatory(f, false) first.
Changes made in the Default update set or in the wrong scope don’t travel.
A current.update() in a business rule on the same record triggers the rules again.
Client scripts, UI policies and g_form don’t run for imports, REST calls or background scripts: enforce rules server-side too.