An update set is a named container that records configuration changes in one instance so they can be moved to another: business rules, script includes, catalog items, form layouts, flows. The mechanics (mark it current, make changes, complete it, retrieve it in the target, preview, commit) take an afternoon to learn. What separates a smooth release from a broken one is knowing the scope of what moves: which changes were captured, which weren’t, and which were captured but shouldn’t be applied as-is. That’s what interviewers mean by “deployment scope”.
What an update set actually records
While an update set is current, each change to a tracked record writes a customer update (sys_update_xml) containing the complete XML of that record as it now stands, plus a version history entry. Change the same business rule five times and the update set holds one customer update with the final version. It’s a snapshot of records, not a list of steps and not a diff.
Which tables count as configuration is decided by the platform: tables tracked for customisations carry an internal update_synch attribute that customers aren’t meant to add themselves. Some configuration spans several tables, such as form sections, lists, related lists, choice lists, dictionary entries, field labels and workflows, and is packaged by special handlers into a single update so it’s applied as a unit. ServiceNow warns that some of these handlers delete and reinsert records when applied.
Two working rules follow. Changes in a scoped application go to an update set for that application, so a single story touching global and an app produces more than one update set. And the Default update set is where changes land when nobody picked one; it isn’t meant to be moved, so work found there has to be re-homed deliberately.
Configuration travels, data doesn’t
ServiceNow’s documentation draws the line clearly: update sets capture configuration but not task or process data. A catalog item’s definition, variables and variable choices move; the requests people submitted against it don’t. In practice, these don’t travel either:
- users, groups, group memberships and role assignments;
- records created by processes (incidents, changes, requests) and most reference data loaded into tables;
- home pages and content pages, which are excluded by default;
- deleting a table (removal isn’t tracked, so it must be done in the target by hand), and dictionary changes that would lose data, such as changing a column’s type, which are blocked.
The trap runs the other way as well. System properties are configuration, so a property value changed in development is captured and will overwrite the production value on commit. That’s how a production integration ends up calling a development endpoint.
Inventorying a release
Before completing an update set, it pays to list everything the story changed and check how each item will reach production. This Node.js model (plain JavaScript; the tracked-table list is a small illustrative subset, not the platform’s) does that for a “Laptop refresh” release, then simulates the target’s preview checking references:
// Which of a release's changes travel in an update set, and what would preview flag in the target?
// The table lists are a small illustrative subset, not the platform's full update_synch list.
const TRACKED = new Set(['sys_script', 'sys_script_include', 'sc_cat_item', 'item_option_new',
'sys_hub_flow', 'sys_properties', 'sys_ui_policy', 'sys_security_acl']);
const ENVIRONMENT_SPECIFIC = new Set(['sys_properties']); // tracked, but values often differ per instance
const devChanges = [
{ table: 'sys_script_include', name: 'LaptopRefreshEligibility' },
{ table: 'sys_script', name: 'Laptop refresh eligibility check' },
{ table: 'sc_cat_item', name: 'Laptop refresh', refs: { group: '7a2f0c1e1b2e3010a5b8c3d4e5f60718' } },
{ table: 'item_option_new', name: 'laptop_model (variable)' },
{ table: 'sys_hub_flow', name: 'Laptop refresh fulfilment', refs: { group: '7a2f0c1e1b2e3010a5b8c3d4e5f60718' } },
{ table: 'sys_properties', name: 'x_acme.laptop.vendor_endpoint (dev URL)' },
{ table: 'sys_user_group', name: 'Hardware Fulfilment', sys_id: '7a2f0c1e1b2e3010a5b8c3d4e5f60718' },
{ table: 'sys_user_grmember', name: '3 members added to Hardware Fulfilment' },
{ table: 'sc_req_item', name: '12 test requests' },
];
const target = { sys_user_group: new Set(['287ebd7da9fe198100f92cc8d1d2154e']) }; // prod has other groups only
const captured = [];
for (const c of devChanges) {
const tracked = TRACKED.has(c.table);
const note = !tracked ? 'NOT captured (data)'
: ENVIRONMENT_SPECIFIC.has(c.table) ? 'captured, but the value is environment-specific' : 'captured';
console.log(`${c.table.padEnd(19)} ${c.name.slice(0, 44).padEnd(45)} ${note}`);
if (tracked) captured.push(c);
}
console.log('preview in production:');
for (const c of captured)
for (const [col, id] of Object.entries(c.refs || {}))
if (!target.sys_user_group.has(id)) console.log(` ${c.table} '${c.name}': ${col} references sys_user_group ${id}, not found`);sys_script_include LaptopRefreshEligibility captured
sys_script Laptop refresh eligibility check captured
sc_cat_item Laptop refresh captured
item_option_new laptop_model (variable) captured
sys_hub_flow Laptop refresh fulfilment captured
sys_properties x_acme.laptop.vendor_endpoint (dev URL) captured, but the value is environment-specific
sys_user_group Hardware Fulfilment NOT captured (data)
sys_user_grmember 3 members added to Hardware Fulfilment NOT captured (data)
sc_req_item 12 test requests NOT captured (data)
preview in production:
sc_cat_item 'Laptop refresh': group references sys_user_group 7a2f0c1e1b2e3010a5b8c3d4e5f60718, not found
sys_hub_flow 'Laptop refresh fulfilment': group references sys_user_group 7a2f0c1e1b2e3010a5b8c3d4e5f60718, not foundThe script include, business rule, catalog item, variable and flow are captured, as expected. The vendor endpoint property is captured too, with a development URL in it. The new Hardware Fulfilment group, its members and the test requests aren’t captured, and because the catalog item and the flow both reference that group by sys_id, preview in production flags them: the update points at a record the target doesn’t have. On a real instance this appears as a preview problem on each affected update, saying the referenced record couldn’t be found.
Fixing what preview finds
Every preview problem needs a decision, and “accept remote update” without understanding the problem is how broken references reach production.
Missing data the configuration depends on. For the group, there are two clean options. Move the group record itself (an XML export and import keeps its sys_id, so references resolve), or create it in production with the same name before loading. ServiceNow documents that some tables, including sys_user_group and sys_user_role, coalesce by name in update sets, and that references to coalescing tables can be rewritten to the target’s sys_id when an update is loaded; whichever route you take, retrieve or re-preview afterwards and confirm the problem has cleared. In scripts, avoid hard-coding group sys_ids at all; look the group up by a property or by name so the code doesn’t depend on matching IDs.
Environment-specific values. Keep them out of release update sets. Create the property once, set its value per instance after deployment (or through the instance’s own configuration process), and protect it from being overwritten by clones with a clone data preserver. If a property value did get captured, skip that update in preview and set the value by hand.
Collisions. If the target has a newer version of the same record, preview reports a collision. That usually means someone changed production directly, which deserves a conversation before anyone picks a winner. The companion article on update set conflicts covers resolution order in detail.
Packaging releases
A few structural choices make update sets manageable:
- One update set per story or fix, named with the story ID, so preview problems can be traced to an owner and a story can be held back without untangling it.
- Batch update sets when several sets must ship together: child sets grouped under a parent are previewed and committed as one batch, in a defined order.
- Scoped applications for substantial features. An app moves as a versioned unit through source control or the application repository, with its own tables, scripts and access settings, which avoids most of the “did we capture everything?” question for that feature. Update sets remain the tool for global configuration.
Before committing in production
Treat the commit as a change with evidence. Preview in an instance that mirrors production (a recent clone), resolve every problem, and record what was skipped and why. Commit in the agreed order. Then run a smoke test, ideally an automated test suite, covering the stories in the release.
Plan for failure realistically. Update sets can be backed out, but back-out reverses the configuration records the set changed; it doesn’t undo data created in the meantime or changes made outside the set, so a forward fix in a new update set is often the safer recovery.
Interview practice
A release moved a new catalog item and its fulfilment flow to production with a clean preview in the test instance. In production, preview showed two problems, which the deployer accepted. Since then, requests for the item sit unassigned, and the item’s vendor integration is calling a development URL. Explain what went wrong and how you’d prevent it next time.
Answer
Two scope gaps. The flow and the catalog item reference a fulfilment group created in development; groups are data and weren’t captured, so in production the references point at a sys_id that doesn’t exist, which is what the two preview problems were saying. The test instance previewed cleanly probably because the group had been created there earlier with the same sys_id (for example by a clone or an XML import), so it hid the gap. Second, the vendor endpoint system property was edited in development while the update set was current, so its development value was captured and overwrote production’s on commit.
To recover: set the production property back to the production endpoint immediately, create or import the group (with members) and confirm the flow and item resolve to it, then reassign the stuck requests. To prevent it: inventory data dependencies for each story and move them deliberately (XML import or a documented manual step) before previewing; never accept a preview problem without recording why it’s safe; keep environment-specific properties out of release update sets and protect them with clone data preservers; preview against a fresh clone of production; and run an automated smoke test after commit that would have caught both the unassigned request and the wrong endpoint.
Related reading
- ServiceNow interview questions
- ServiceNow multiple-choice questions
- ServiceNow Update Sets and Conflicts, on collisions and resolution order
- ServiceNow Scoped Applications and Cross-Scope Access, the alternative packaging for larger features
- Customizations tracked by update sets (ServiceNow documentation)