This page is for anyone facing a ServiceNow round, whether you're going for an admin, developer or ITSM consultant role. Most rounds start with incident, problem and change and how the task table ties them together, then move to business rules, client scripts, UI policies, script includes and GlideRecord. Stronger rounds test ACLs, Flow Designer, catalog items, update sets, imports and REST integrations, and finish with a production story and a judgement call. Each question shows what the interviewer is really checking, the shape of a strong answer and a short answer you can say out loud. Try the scripts on a personal developer instance, then change the stories to your own.
Search all questions by round, difficulty and level, or save the ones you want to practise.
Incident: restore service as fast as possible; the goal is the user working again.
Problem: find and remove the root cause behind one or more incidents; track workarounds and known errors.
Change: control the fix going into production with risk review and approval.
"An incident is about getting the user or the service working again as quickly as possible. It doesn't need the root cause, just a fix or a workaround. A problem is opened when we want to know why it happened, often because the same incident keeps coming back or one outage hit many people. The problem team finds the root cause, records a workaround or a known error, and proposes a permanent fix. That fix usually needs a change request, so it goes through risk assessment and approval before it touches production. In a real outage the flow is: many incidents get linked to one parent or to a problem, the problem finds, say, a bad certificate renewal process, and a change request fixes it properly. On the platform all three extend the task table, so they share assignment, state and work notes."
Describing problem management as just a bigger incident, or saying a permanent fix goes straight into production without a change.
Standard: low risk, repeated often, pre-approved and usually raised from a template.
Normal: full path with planning, risk assessment and CAB or group approval.
Emergency: urgent fix to restore service; faster approval, often by a smaller emergency board, reviewed afterwards.
"A standard change is something low risk that we do the same way many times, like adding memory to a virtual machine or restarting a known service in a window. It's pre-approved, so people raise it from a template and it skips the approval step. A normal change is anything that needs a real look: it goes through planning, a risk assessment, and approval from the change manager or the CAB before it's scheduled. An emergency change is for when something is broken right now and waiting for the next CAB would hurt the business. It still gets approved, but by a small group quickly, and it gets reviewed after the fact. The trap I've seen is teams calling everything an emergency to skip the queue, so I'd watch the ratio and push repeat work into standard templates instead."
Saying emergency changes need no approval at all, or treating standard changes as a shortcut for any change the team is confident about.
Classes: CIs live in cmdb_ci and child tables such as servers, databases and applications.
Relationships: stored in cmdb_rel_ci as parent, child and a type like runs on or depends on.
Population: discovery and integrations load CIs through identification rules so they match instead of duplicate.
Use: link CIs to incidents and changes for impact, routing and risk.
"The CMDB is the record of what the company runs and how it fits together. Every configuration item sits in cmdb_ci or a child class, like a Linux server, a database instance or a business application, and each class adds its own fields. Relationships live in cmdb_rel_ci as a parent, a child and a type, like an application depends on a database, which runs on a server. Data mostly comes from Discovery or integrations, and it should go through the identification and reconciliation engine so the same server from two sources becomes one CI, not two. Why it matters: when an incident is logged against a CI, you can route it to the right support group and see what else is affected. For a change, you can see which services depend on the server you're about to patch, which feeds the risk assessment."
Calling the CMDB an asset spreadsheet, or having no idea how the data gets in or stays correct.
Parent table: task holds the shared fields such as number, state, assigned to, assignment group and short description.
Child tables: incident, problem, change_request, sc_req_item and sc_task extend it and add their own fields.
Inheritance: business rules and ACLs on task apply to children unless something more specific exists.
"Task is a base table for anything that gets assigned and worked. It holds the common fields: number, state, priority, assigned to, assignment group, short description, work notes. Incident, problem, change request, requested items and catalog tasks all extend it, so they inherit those fields and add their own, like caller on incident or risk on change. In the database a child record is still a task record, so a report on task can show incidents and changes together, and a business rule written on task runs for all of them. ACLs work the same way: if there's no ACL on the child table, the one on task is checked. When I build something new that gets assigned and has a lifecycle, extending task gives me SLAs, approvals and assignment for free, which is usually better than a flat custom table."
Thinking each module is a completely separate table with copied fields, or not knowing that rules on a parent table fire for child records.
Dictionary: sys_dictionary defines each field: type, length, default, mandatory, reference target.
Reference field: stores the sys_id of a record on another table and shows its display value.
Dot-walking: follow references with dots, in scripts, filters, forms and reports.
"The dictionary is where every table's fields are defined. Each entry says the column name, the type, max length, default value, whether it's mandatory, and for reference fields which table it points at. A reference field actually stores a sys_id, the unique 32 character id of the other record, but the form shows its display value, like the user's name. Dot-walking means following that link with a dot. So on an incident, caller_id.manager.email gets the email of the caller's manager without me writing a second query. I use it in scripts, in list filters, in notification templates and to add related fields to a form layout. One thing I'm careful about: long dot-walks in a loop over many records can be slow, because each hop can mean another lookup."
var inc = new GlideRecord('incident');
if (inc.get('number', 'INC0010001')) {
gs.info(inc.caller_id.manager.email.toString());
}
Saying a reference field stores the name of the related record, or querying the user table again when a dot-walk would do.
Build: new GlideRecord on the table, add conditions, order and a limit.
Run: call query, then loop with next.
Read: getValue for raw values, getDisplayValue for references.
"I create a GlideRecord on incident, add an active query and a condition for priority 1, order by created date descending and set a limit of ten so the database only returns what I need. Then I call query and loop with while next. Inside the loop I use getValue for the number and getDisplayValue for assigned to, because assigned to is a reference and getValue would give me a sys_id. I'd test this in background scripts on a sub-production instance first. For more complex filters I often build the condition in a list view, copy the query, and use addEncodedQuery, which is easier to read and matches exactly what the users see in the list."
var gr = new GlideRecord('incident');
gr.addActiveQuery();
gr.addQuery('priority', 1);
gr.orderByDesc('sys_created_on');
gr.setLimit(10);
gr.query();
while (gr.next()) {
gs.info(gr.getValue('number') + ' - ' + gr.getDisplayValue('assigned_to'));
}
Querying the whole table and filtering with if statements inside the loop, or forgetting to call query before next.
Before: runs before the database write; change fields on current, no update call.
After: runs after the write; update related records or fire events.
Async: queued to run in the background after the transaction; heavy or slow work.
Display: runs when a form loads; fills g_scratchpad for client scripts.
"A before rule runs just before the record is written, whether the save came from a form, a script or an import, so it's the right place to set or validate fields on current. If I set a field there, it's saved with the record, no update call needed, and I can stop the save with setAbortAction. An after rule runs once the record is saved, so I use it to update other records, like closing child tasks when a parent closes. An async rule is queued and runs in the background, so the user doesn't wait for it; that's where I put slow work like an outbound REST call. It doesn't have previous, though, so it can't compare old and new values. A display rule runs when the form is loaded, before it's sent to the browser, and I use it to put server data into g_scratchpad so client scripts don't need extra server calls."
Putting outbound integrations in a before rule so every save waits on another system, or updating current's own fields in an after rule.
Before rule: the record is about to be saved anyway; just set the field.
After rule: update() saves again, so every rule runs again and can loop.
Fix: move field changes to a before rule; for related records, update those records instead.
"In a before rule, the record is already on its way to the database, so calling update is redundant. I just set the field on current and it gets saved with everything else. In an after rule it's worse: current.update triggers a second save, which runs all the before and after rules again, and that can loop, fire notifications twice and write duplicate audit history. So my fix is almost always to move the logic into a before rule. If I really have to change the same record after it's saved, for example based on something only known after insert, I'd think hard about it, and use setWorkflow false on that one update so the rules don't run again, with a comment explaining why. For other records, like the parent of a task, calling update on that other GlideRecord is fine."
Saying update() is required to save changes made in a before rule, or reaching for setWorkflow(false) everywhere without knowing it also stops the other business rules and the notifications they would trigger.
Purpose: reusable server code, loaded only when something calls it.
Shapes: a class with Class.create and a prototype, or a single function named like the include.
Client callable: extends AbstractAjaxProcessor, has the client callable flag set, reached through GlideAjax.
"A script include is a library of server-side code I can call from business rules, flows, other script includes or scheduled jobs. It isn't run on its own; it's loaded when something calls it by name, which keeps it cheap. Most are written as a class with Class.create and a prototype of methods, so I can do new IncidentUtils and call a method on it. For something tiny I can write a single function with the same name as the include. A client-callable one extends AbstractAjaxProcessor and has the client callable flag ticked, so a client script can reach it through GlideAjax. Its methods read inputs with getParameter and return a value. Because anyone logged in can call it from a browser, I treat those methods like a public API: validate inputs, check the user's rights, and only return what the form needs."
Putting shared logic by copy-paste into many business rules, or making a script include client callable without any check on what it returns.
Tool: GlideAggregate with a COUNT aggregate and groupBy.
Why: the database returns one row per group instead of every record.
Reading: getAggregate for the count, getDisplayValue for the group name.
"I'd use GlideAggregate, which is built for counts, sums, averages and group-bys. I add the active condition, add a COUNT aggregate, group by assignment group and query. The database does the counting and hands back one row per group, so even with half a million incidents I only get back a few dozen rows. Looping with GlideRecord would pull every matching record into the app server and count in JavaScript, which is slow and wastes memory. The same applies to a simple 'how many' check: getRowCount on a GlideRecord still runs the full query, so for a plain count I'd use GlideAggregate without the group-by."
var ga = new GlideAggregate('incident');
ga.addActiveQuery();
ga.addAggregate('COUNT');
ga.groupBy('assignment_group');
ga.query();
while (ga.next()) {
var group = ga.assignment_group.getDisplayValue() || '(no group)';
gs.info(group + ': ' + ga.getAggregate('COUNT'));
}
Counting with a GlideRecord loop over the whole table, or using getRowCount on large tables as if it were free.
Display rule: runs on the server as the form loads, before it reaches the browser.
Scratchpad: an object filled on the server and readable in client scripts.
Use: onLoad decisions that need server data, with no GlideAjax call.
"A display business rule runs on the server when a form is being loaded, just before it's sent to the browser. In it I can do queries and set properties on g_scratchpad, for example whether the caller is a VIP or how many open incidents they already have. That object arrives with the form, so an onLoad client script can just read g_scratchpad.isVip and show a message or hide a section. The big win is there's no extra server call after the page loads, which a GlideAjax call would need. The limit is that it only runs on load, so if the value depends on something the user changes later, like picking a different caller, I'd still need GlideAjax in an onChange script. I also keep the display rule light, because it runs on every form load of that table."
// Display business rule on incident
g_scratchpad.isVip = current.caller_id.vip.toString() === 'true';
// onLoad client script
function onLoad() {
if (g_scratchpad.isVip) {
g_form.showFieldMsg('caller_id', 'VIP caller', 'info');
}
}
Using a synchronous server call on load to fetch data a display rule could have sent with the form.
onLoad: runs when the form opens; set defaults, show messages.
onChange: runs when one field changes; check isLoading to skip the initial load.
onSubmit: runs on save; return false to stop the submit.
onCellEdit: runs when a field is edited from a list.
"onLoad runs when the form opens, so I use it for things like showing an info message or hiding a section based on the state. onChange runs when a specific field changes. It gets the control, old value, new value and an isLoading flag, and I usually return early if isLoading is true, because the script also fires as the form loads. onSubmit runs when the user saves; if I return false, the save is stopped, so it's where I do final checks like making sure a close note has enough detail. onCellEdit runs when someone edits a field inline from a list, which people often forget; if I only protect the form, a user can change the value from the list and skip my logic. All of them run in the browser, so they're for user experience, not for security."
Treating client scripts as a security control, or not knowing that list edits bypass form client scripts.
UI policy: condition plus actions: mandatory, visible, read-only. No code needed.
Reverse if false: undoes the action when the condition stops being true.
Client script: when you need logic a condition can't express, server data, or messages.
"If the rule is just 'when this condition is true, make these fields mandatory, visible or read-only', I use a UI policy. It's configuration, not code: I set the condition, add policy actions, and with reverse if false ticked it undoes itself when the condition stops being true. Anyone on the team can read it, and it's less likely to break on an upgrade. I switch to a client script when I need real logic: calling the server through GlideAjax, setting values based on calculations, showing a confirm box, or reacting to a value in a way a condition can't express. UI policies can run scripts too, but if I'm writing a lot of script, a client script is clearer. One thing to know: UI policies apply after onLoad client scripts, so if both touch the same field, the policy's result is usually what the user sees."
Writing a client script for every show-hide rule, or thinking a UI policy also protects imports and API updates.
UI policy: runs in the browser on forms only; can hide fields.
Data policy: enforced on the server, so imports, web services and list edits obey it too.
Overlap: a data policy can also act as a UI policy; it only does mandatory and read-only, never visibility.
"A UI policy only lives on the form in the browser. If data comes in through an import set, the REST API or someone editing in a list, the UI policy never runs. A data policy is enforced on the server when the record is saved, so it applies no matter where the change comes from. That makes it the right tool when a field truly must be filled, like a resolution code on a closed incident. A data policy can also be used as a UI policy on the client, so I don't have to build the same rule twice. What it can't do is hide fields, since visibility only means something on a form; it only handles mandatory and read-only. For anything more complex than that, I'd use a before business rule that aborts the save with a clear message."
Saying UI policies protect data coming in through integrations.
Server: a client-callable script include extending AbstractAjaxProcessor, reading inputs with getParameter.
Client: new GlideAjax, sysparm_name for the method, extra sysparm parameters, then getXMLAnswer with a callback.
Async: the callback fills the field later; the user can keep working.
"On the server I write a client-callable script include that extends AbstractAjaxProcessor. The method reads the user sys_id with getParameter, looks up the user and returns the manager's sys_id. On the client, in an onChange on caller, I create a GlideAjax pointing at that script include, pass sysparm_name with the method name and my own parameter with the new caller, and call getXMLAnswer with a callback. The callback runs when the answer comes back and sets the field with g_form.setValue. Because it's asynchronous, the form stays responsive. I avoid getXMLWait, which blocks the browser, and I avoid GlideRecord in client scripts, which is synchronous and pulls whole records to the browser. For a simple read like this, g_form.getReference with a callback also works, but GlideAjax lets me return only the one value I need."
// Script include: CallerUtils (client callable)
var CallerUtils = Class.create();
CallerUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getManager: function() {
var user = new GlideRecord('sys_user');
if (user.get(this.getParameter('sysparm_user_id'))) {
return user.getValue('manager');
}
return '';
},
type: 'CallerUtils'
});
// onChange client script on caller_id
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === '') return;
var ga = new GlideAjax('CallerUtils');
ga.addParam('sysparm_name', 'getManager');
ga.addParam('sysparm_user_id', newValue);
ga.getXMLAnswer(function(answer) {
g_form.setValue('u_manager', answer);
});
}
Using getXMLWait or client-side GlideRecord as the normal pattern, or returning a whole record when the form needs one value.
Measure: split the time into server, network and browser; check the transaction log.
Server side: debug business rules for heavy display rules or slow queries.
Browser side: synchronous calls, many onLoad scripts, heavy related lists.
Release link: compare with what the update set changed.
"First I'd measure where the time goes. The response time breakdown on the form and the transaction logs show whether the server, the network or the browser is slow. If it's the server, I turn on business rule debugging and look at display rules and any new query rules, because a display rule doing a big GlideRecord loop runs on every form load. If it's the browser, I look at onLoad client scripts, especially synchronous calls like getXMLWait, client-side GlideRecord or getReference without a callback, and at related lists the release added. Since it started with a release, I'd open the update set and list every form-related change, which usually narrows it to one or two records quickly. The fix is normally to move data into g_scratchpad or an async GlideAjax call, add a missing index or limit, or move a related list to a tab that loads on demand."
Blaming the network or the platform without measuring, or rolling back the whole release without finding the cause.
Two checks: the user must pass the table-level ACL and the field-level ACL.
Most specific first: the exact table and field, then parent tables like task, then wildcards such as incident.* and *.
Inside one ACL: role, condition and script must all pass; any one passing ACL at the matched level is enough.
"There are two gates: the user has to pass a table-level read ACL on the record and a field-level read ACL on the field. For each gate, the platform looks for the most specific match first. For the table, that's incident, then the parent task, then the wildcard. For the field, it's incident dot that field, then task dot that field, then the wildcard rules like incident dot star. It stops at the first level where ACLs exist and evaluates those. Inside a single ACL, the requirements are ANDed: the user needs one of the listed roles, the condition must be true and the script must return true. If there are several allow ACLs at that level, passing any one of them is enough. Newer releases also have deny-unless ACLs, which are checked first and can block access even when an allow rule passes. So when a user can see the incident but a field is missing, I look at the field ACLs and use the security debugger."
Saying the platform checks every ACL and needs all of them to pass, or thinking roles alone decide access.
Read ACL: checks each row after it's fetched; lists show 'rows removed by security constraints' and uneven pages.
Query rule: adds a condition to the query itself, so hidden rows are never fetched.
Best: often both; query rule for the list experience, ACLs as the real security model for read, write and delete.
"If I only use a read ACL, the database still returns rows and the platform removes the ones the user can't read. The list then shows a message saying some rows were removed by security constraints, page counts look wrong, and on a big table it wastes work. A before query business rule adds a condition to the query before it runs, like assignment group is one of my groups, so the user simply never gets those rows and paging works. The catch is that a query rule is a filter, not a full security model, and it has to handle admins and integration users properly, usually by skipping them. So in practice I'd use the query rule for a clean list, and keep matching ACLs, because the query rule doesn't cover write or delete, and a script that turns business rules off skips it."
Choosing ACLs only and calling the 'rows removed' message expected behaviour, or treating a query rule on its own as the complete security model.
Reproduce: impersonate the agent, open the same list and filter.
Check filters: the list's filter, the module's filter, saved personal filters.
Check security: 'rows removed' message, read ACLs, query business rules, roles and group membership.
Fix: correct the data or the rule, not a blanket new role.
"First I'd impersonate the agent and open the same list, so I see exactly what they see. Often the answer is a filter: a personal filter, a module that only shows 'assigned to me', or a condition they didn't notice in the breadcrumb. If the filter is the same as the lead's, I look for the 'rows removed by security constraints' message at the bottom, which points to a read ACL. If there's no message but rows are still missing, it's likely a query business rule, often one that limits incidents to the user's own groups. Then I compare the two users: roles, group memberships, and whether the agent was just added to a group and is still on an old session. I'd use the security debugger to see which rule failed. The fix is usually a missing group membership, not a new role, and I'd confirm by impersonating again."
Jumping straight to 'give them admin' or another role without finding which rule actually hides the records.
Flow Designer: triggers, actions, subflows and flow logic; readable by process owners; the recommended default for new work.
Reuse: subflows and custom actions, plus integration spokes for other systems.
Legacy workflow: still runs many older catalog items; maintain it, migrate when there's a reason.
"For anything new I'd default to Flow Designer. A flow has a trigger, like a record created or updated, a schedule, or a catalog item being requested, and then a list of actions and flow logic like if, for each and wait for condition. It reads almost like plain language, so a process owner can follow it, and I can build reusable subflows and custom actions, and use integration spokes instead of hand-written REST code. The execution details show exactly which step ran with which values, which makes debugging much easier. The legacy workflow editor is the older drag-and-drop canvas, and lots of instances still run catalog items and approvals on it. I wouldn't rewrite those just because; I'd migrate a workflow when we're changing it significantly anyway, or when it's hard to maintain."
Saying Flow Designer is only for people who can't code, or proposing to rewrite every legacy workflow at once with no business reason.
Item: name, category, who can see it, and the flow or workflow that fulfils it.
Variables: the questions on the form; variable sets for questions shared across items.
Records: request (sc_request), requested item (sc_req_item) and catalog tasks (sc_task).
Behaviour: catalog client scripts and catalog UI policies for the form.
"I start with the item itself: a clear name, the catalog and category, a short description, and user criteria for who can see it. Then I add variables, which are the questions the user answers, picking the right type, like a reference to the user table instead of a free-text name. Questions used on many items, like 'requested for' and location, go in a variable set so I maintain them once. For form behaviour I use catalog UI policies and catalog client scripts. When someone orders, the platform creates a request, one requested item per item in the cart, and the fulfilment flow on the item creates catalog tasks for the teams doing the work, plus any approvals. In scripts and flows I read the answers from the requested item, for example current.variables.laptop_model in a business rule on sc_req_item."
Using free-text variables where a reference or choice list belongs, or not knowing the request, requested item and task hierarchy.
When: on insert or update with conditions, or when an event fires.
Who: users, groups, fields on the record, or event parameters.
What: subject and body with field variables, templates and mail scripts.
Events: registered in the event registry, queued with gs.eventQueue.
"A notification has three parts: when to send, who receives it, and what it says. The simplest trigger is a record inserted or updated with a condition, like priority changes to 1. For anything that isn't a clean field change, I fire an event instead. I register it in the event registry, then call gs.eventQueue with the event name, the record and two parameters, often a recipient in parm1. The notification listens for that event and can send to event parm1. That keeps the 'decide to notify' logic in a script and the email content in the notification, and it's reusable, since several notifications or script actions can listen to one event. For content I use field variables, email templates for shared layouts and mail scripts when I need loops or logic. To test, I check the email log and the event log to see whether the event fired and who was picked."
// In an after business rule on incident
gs.eventQueue('incident.sla.warning', current,
current.getValue('assigned_to'), current.getValue('assignment_group'));
Building an email by hand in a script with no notification record, or not knowing the email and event logs exist.
Setup: a scheduled script execution with a run time, frequency and optional condition.
Run as: jobs run as a chosen user; pick one with the right access.
Care: batch large updates, log what happened, test on sub-production first.
"I create a scheduled script execution, give it a clear name, set it to run daily at a quiet time, and write the script, for example closing resolved incidents older than a set number of days. I set the run-as user deliberately, because the job runs with that user's identity and it shows up in the audit history. There's also a conditional option if the job should only run when something is true. Before scheduling it, I test with 'Execute Now' on a sub-production instance and log what it changed. For big volumes I process in batches and use setWorkflow false only where I'm sure notifications and business rules shouldn't fire. If it's more of a process than a script, like a weekly review task, a scheduled Flow Designer trigger can be easier for others to maintain."
Running a job as an admin by default without thinking, or updating hundreds of thousands of records in one loop during business hours.
Flow: data source to import set staging table, then a transform map to the target table.
Field maps: source column to target field, with scripts when values need changing.
Coalesce: the key used to find an existing record: match updates it, no match inserts.
Scripts: onBefore, onAfter, onStart and onComplete; ignore skips a row.
"A data source says where the data comes from, like an uploaded spreadsheet, a JDBC connection or a file. Loading it fills an import set table, which is just a staging table with the raw rows. A transform map then moves each row into the target table, with field maps from source columns to target fields. Coalesce is the key setting: I mark one or more fields, like employee number, as coalesce fields, and for each row the platform looks for a target record with the same value. If it finds one, it updates it; if not, it inserts. With no coalesce, every run inserts new records, which is how people end up with duplicate users. For cleaning values I use field map scripts, and an onBefore transform script can set ignore to true to skip bad rows. The import set shows each row's result, so errors are easy to trace."
Loading data straight into the target table with no staging, or not knowing that a missing coalesce creates duplicates.
Inbound: Table API for simple record access; Scripted REST API for custom logic; import set API when data should be transformed.
Outbound: a REST Message with methods, called from a flow or with RESTMessageV2 in script.
Production concerns: a dedicated integration user with least access, OAuth or basic auth, a MID Server for systems inside the network, retries and logging.
"For inbound, the out-of-the-box Table API already lets another system create or read records, so for simple cases I give them an integration user with just the roles they need. If they need custom logic or a payload that doesn't match our tables, I build a Scripted REST API, and if the data needs cleaning and matching I send it through the import set API so transform maps and coalesce do the work. For outbound, I define a REST Message with the endpoint, authentication and methods, then call it from a flow or from script with RESTMessageV2. I run outbound calls in an async business rule or a flow, never in a before rule, check the status code, log failures and retry. If the other system sits inside the company network, the call goes through a MID Server."
var rm = new sn_ws.RESTMessageV2('Asset Service', 'get');
rm.setStringParameterNoEscape('asset_tag', current.getValue('asset_tag'));
var response = rm.execute();
if (response.getStatusCode() == 200) {
var body = JSON.parse(response.getBody());
gs.info('Owner: ' + body.owner);
} else {
gs.error('Asset Service failed: ' + response.getStatusCode());
}
Giving the integration an admin account, or making a synchronous outbound call inside a before rule so every save waits on another system.
Captures: configuration changes such as business rules, client scripts, UI policies, form layouts and dictionary changes.
Does not capture: data records like users, groups, group memberships or incidents.
Moving: mark complete, retrieve on the target, preview, fix problems, commit.
"An update set records configuration changes as I make them in the instance: business rules, client scripts, UI policies, script includes, form layouts, dictionary changes, notifications. What it doesn't pick up is data. Users, groups, group memberships, and records in tables like incident aren't captured, so if my new rule depends on a new assignment group, I have to move that group separately, usually by exporting and importing XML. Before I start, I make sure I'm in my own named update set, never Default. To move it, I mark it complete, retrieve it on the target instance from the source, and run a preview. The preview flags collisions and missing references, which I resolve before committing. For big releases I group related sets into a batch so they commit in the right order."
Doing work in the Default update set, or assuming groups and users travel with the update set.
Understand: open both versions and compare; find who made the local change and why.
Decide: accept the remote update, skip it, or merge both changes by hand.
Prevent: find why someone changed the target directly, and fix the process.
"A collision means the record on the target instance was changed after the version in my update set, so committing would overwrite someone else's work. I wouldn't just click accept. I'd compare the two versions to see what each change does, and find out who made the local change and why. Often it's a hotfix made directly in test or production. If my version already includes their fix, I accept the remote update. If their change must stay and mine isn't needed, I skip mine. If both matter, I merge them in development, capture a new version in an update set, and move that instead, so every environment ends up the same. Then I'd deal with the cause: changes made directly on a higher instance drift out of sync, so I'd push for fixes to go through development, even urgent ones."
Accepting every remote update to get past the preview, or skipping them all without checking what gets lost.
Situation: what broke, who noticed and how bad it was.
Trace: logs, debug tools and how you narrowed it to your script.
Fix and lesson: the fix, how you protected users meanwhile, and what you changed in your process.
"At my last company I wrote an after business rule on incident that updated the parent incident's work notes whenever a child changed. In testing it looked fine. In production the service desk reported duplicate emails and some saves taking several seconds. I turned on business rule debugging for my session, reproduced it, and saw my rule firing twice per save. I'd called current.update inside the after rule to set a flag, which re-ran every rule on the record, and the parent update was sending notifications as well. As a quick fix I deactivated the rule, since nothing critical depended on it. Then I moved the flag logic into a before rule, kept only the parent update in the after rule, and tested with a realistic number of child records. Since then I review every rule for update calls on current, and I test with production-like data volumes."
A story where the bug was someone else's fault, or where the fix was found by guessing rather than using logs and debugging.
Plan: sub-production first, a test plan built from the most used processes.
Skipped records: review each one; revert to base, merge, or keep and document.
Result and lesson: what you found, how you reduced customisation for next time.
"In my last role I was part of an upgrade for an instance that had been customised a lot over the years. We upgraded a sub-production clone first. The upgrade left a long list of skipped records, which are base files we'd changed, so the upgrade didn't overwrite them. We went through them in groups. Where our change no longer mattered, we reverted to the base version. Where we needed both, we merged our change into the new base version. A few we kept, with a note explaining why. Then we ran regression tests on the flows people use most: raising incidents, changes and the top catalog items. We found one client script that broke a form, fixed it, and repeated the process in the next environment. Afterwards I pushed for a rule that we copy and extend base scripts instead of editing them, which made the next upgrade much shorter."
Saying skipped records can simply be ignored, or never having looked at them after an upgrade.
Ask: what they wanted and the real need behind it.
Options: the configured or out-of-box way next to the custom one, with the long-term cost of each.
Outcome: what was built and how the owner felt about it.
"At my last company the change manager wanted a custom approval engine, with a new table and scripts, because approvals had to depend on the risk level and the affected service. When I asked what problem he was really facing, it was that high-risk changes to key services weren't getting the right approvers. I showed him that the standard change approval path could handle it with approval rules based on risk and the CI's support group, driven from a flow, with no new table and nothing that would fight the next upgrade. I built a quick demo in our dev instance using two real past changes, which made it concrete. He agreed, with one small tweak we added in a subflow. It went live in a couple of weeks instead of the couple of months the custom build was estimated at, and it upgraded cleanly later."
Either building anything the business asks for without question, or refusing flatly with no alternative.
ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.