logo

NJP

Background Scripts: The First Place to Go When Something Breaks in ServiceNow!!

New article articles in ServiceNow Community · Feb 27, 2026 · article

 

Background Scripts: The First Place to Go When Something Breaks in ServiceNow

Real Production War Stories and Why This Tool Deserves MVP Status

Background Scripts Troubleshooting ITOM Developer Tips Production Stories

Let me ask you something. When something breaks in your ServiceNow instance — an assignment group stops rotating tickets, a scheduled job silently disappears, or certificates start flipping fields overnight — what's the first thing you do?

If your answer isn't "open Background Scripts," this article might give you a reason to try it.

I've been a ServiceNow Developer for several years, and in my previous role I supported a healthcare environment working across ITOM, ITSM, and Certificate Management. In that time, Background Scripts has saved me more hours than any other single tool in the platform. Not flows. Not business rules. Not even ServiceNow support cases. Background Scripts — a tool that I think deserves a lot more love from the community than it currently gets.

This article is my way of sharing what I've learned about this tool, told through real production stories from my environment. I hope it gives you some new ideas for your own troubleshooting workflow.

Why Background Scripts? Why Not Just Check the Logs?

Logs tell you what happened. Background Scripts tell you why it happened. That's the difference.

When something goes wrong in production, you need answers fast. You need to query data, test theories, validate business rule behavior, and prove root cause — all without touching a single production record. Background Scripts lets you do exactly that. It's your investigation lab, your proof-of-concept workspace, and your emergency toolkit all in one place.

Navigate to System Definition → Scripts - Background, and you have immediate access to the full server-side GlideRecord API. No deployment. No update set. No waiting. Just answers.

War Story #1: The Round-Robin That Wasn't Round at All

The Problem

Our infrastructure team noticed that one technician — let's call him Jason — was getting assigned every single ticket for a particular assignment group. Jason was overwhelmed. His five teammates had empty queues. The round-robin was supposed to distribute work evenly across six people, but Jason was getting 100% of the assignments.

The business rule looked fine. The assignment group had round-robin enabled with the correct task types configured. ServiceNow support would take 48 hours to respond. Meanwhile, Jason was drowning in tickets.

So I opened Background Scripts.

First, I needed to understand what was actually happening in the data. I wrote a quick script to analyze the last 50 assignments for that group:

// Who's getting all the tickets? Let's find out. var gr = new GlideRecord('incident'); gr.addQuery('assignment_group.name', 'SYSADM-AD-Infrastructure'); gr.orderByDesc('sys_created_on'); gr.setLimit(50); gr.query(); var assignmentCount = {}; while (gr.next()) { var assignedTo = gr.assigned_to.getDisplayValue() || 'Unassigned'; assignmentCount[assignedTo] = (assignmentCount[assignedTo] || 0) + 1; } for (var person in assignmentCount) { gs.print(person + ': ' + assignmentCount[person] + ' tickets'); }

The output confirmed what we suspected:

*** Script: Jason P.: 48 tickets *** Script: Unassigned: 2 tickets

48 out of 50 tickets going to one person. That's not a round-robin — that's a monopoly.

Next, I dug deeper. Our environment uses a custom round-robin business rule with per-group exclusion fields on the user record. I wrote a script to check every member's eligibility:

// Check every member — who's actually eligible for round robin? var groupId = '<assignment_group_sys_id>'; var members = new GlideRecord('sys_user_grmember'); members.addQuery('group', groupId); members.query(); while (members.next()) { var user = members.user.getRefRecord(); var globalExcluded = user.u_available_of_round_robin_ticket_assignment + ''; var groupExclusions = user.u_round_robin_group_exclusion + ''; var excludedFromThisGroup = groupExclusions.indexOf(groupId) > -1; gs.print(user.name + ' | Global Excl: ' + globalExcluded + ' | Group Excl: ' + excludedFromThisGroup + ' | Last Assigned: ' + user.u_ticket_last_assigned); }

And there it was. Five out of six group members had the group's sys_id listed in their u_round_robin_group_exclusion field. Someone had added those exclusions during a configuration change months ago and never removed them. The round-robin business rule was working perfectly — it checked each user's exclusion list, skipped everyone who was excluded, and Jason was the only eligible person left in the queue.

The fix was straightforward: remove the group sys_id from the exclusion field for those five users. Within minutes, the round-robin started distributing evenly. Problem solved.

Total investigation time: 15 minutes. Without Background Scripts? I'd still be reading through business rule logic trying to figure out why the code was "broken" when it wasn't.

War Story #2: The Certificate Management Bug That ServiceNow Confirmed

The Problem

Our Certificate Inventory and Management module started behaving strangely. Certificates that our team had explicitly marked as "Do not create renewal tasks" kept flipping back to "Create priority 3 tasks" after every discovery run. Hundreds of unwanted renewal tasks were being generated overnight — for expired certificates, decommissioned servers, certificates we'd deliberately excluded. Our security team was drowning in false-positive tasks.

To be clear — ServiceNow Support ultimately identified this as a product defect (PRB1935566) in the PopulateCmdbCiCertificate script include and provided the fix. But Background Scripts was how I built the evidence to escalate the case, proved the scope of the impact, and validated the fix before deploying it.

I started by identifying how widespread the problem was:

// How many certificates had their renewal_tracking flipped? var cert = new GlideRecord('cmdb_ci_certificate'); cert.addQuery('renewal_tracking', 'priority3'); cert.query(); gs.print('Certificates set to create renewal tasks: ' + cert.getRowCount()); // Now check which ones were MANUALLY set to "none" but got flipped back var history = new GlideRecord('sys_audit'); history.addQuery('tablename', 'cmdb_ci_certificate'); history.addQuery('fieldname', 'renewal_tracking'); history.addQuery('oldvalue', 'none'); history.addQuery('newvalue', 'priority3'); history.query(); gs.print('Certificates flipped from none → priority3: ' + history.getRowCount());

The audit trail told the whole story — certificates that our team had deliberately configured were being silently overwritten every time discovery re-processed them. That data gave me concrete numbers to include in my ServiceNow case.

I also used Background Scripts to trace the timeline — correlating the flips with discovery schedule runs and plugin updates. Each script gave me one more piece of the puzzle to present to ServiceNow Support.

When ServiceNow identified the root cause — a function called setDefaultsForUserDefinedFields() inside the PopulateCmdbCiCertificate script include that ran for ALL certificates instead of just new ones — I used Background Scripts to reproduce the exact behavior and validate their diagnosis:

// Reproduce what discovery does — confirm the field flip var certGR = new GlideRecord('cmdb_ci_certificate'); certGR.addQuery('subject', 'CONTAINS', 'myserver'); certGR.query(); if (certGR.next()) { gs.print('BEFORE: ' + certGR.getDisplayValue('renewal_tracking')); // This is what PopulateCmdbCiCertificate does on every discovery run var populator = new sn_disco_certmgmt.PopulateCmdbCiCertificate(); populator.setDefaultsForUserDefinedFields(certGR); certGR.update(); certGR.get(certGR.sys_id); gs.print('AFTER: ' + certGR.getDisplayValue('renewal_tracking')); }

BEFORE: Do not create renewal tasks. AFTER: Create priority 3 tasks. Every single time. That Background Script output confirmed the diagnosis and proved the fix was needed.

And after ServiceNow provided the fix — wrapping the function call with an isNewRecord() check — I used Background Scripts again to validate that the fix actually worked before deploying it to production. Same script, same test. This time: BEFORE: Do not create renewal tasks. AFTER: Do not create renewal tasks. Fix confirmed.

Could I have found the root cause myself in the script include code? Maybe. But the point is this: Background Scripts gave me the tools to quantify the impact, build an evidence-based case for ServiceNow Support (case CS8548442), reproduce the problem on demand, and validate the fix before it touched production. Every piece of data I presented came from scripts I wrote in that simple text box.

War Story #3: The Business Rule That Silently Blocked Everything

The Problem

Users reported that they couldn't change round-robin settings for group members. They'd go to User Administration → Groups → Group Members, change the round-robin field from false to true, click Save — and the field would silently flip back to its original value. No error message. No warning. Just... nothing happened.

This one is a perfect example of how a Business Rule in one place can silently block an operation you'd never expect it to affect — and how Background Scripts is the only way to see what's really happening on the server side.

My first instinct was that something was wrong with the round-robin configuration. But when I went to the underlying sys_user record and tried to update anything directly, I got an "Invalid update" error. That was the clue. This wasn't a round-robin problem — something was blocking ALL user record updates.

So I opened Background Scripts and started hunting for active before Business Rules on sys_user:

// What before BRs are active on sys_user? var br = new GlideRecord('sys_script'); br.addQuery('table', 'sys_user'); br.addQuery('active', true); br.addQuery('when', 'before'); br.query(); while (br.next()) { gs.print('BR: ' + br.name + ' | Order: ' + br.order); }

One name jumped out: "Prevent invalid country code." I pulled up its script:

// The "Prevent invalid country code" BR: var country = current.country; var coreCountryGR = new GlideRecord("core_country"); coreCountryGR.addQuery("iso3166_2", country); coreCountryGR.query(); if (coreCountryGR.getRowCount() == 0) { gs.warn("Attempt to insert/update an invalid value for sys_user.country: " + country); current.setAbortAction(true); }

This BR validates the country field against the core_country table on every user record update. If the country code isn't a valid ISO 3166-2 code, it calls setAbortAction(true) — which silently aborts the entire record update , not just the country field. So if a user had "USA" in their country field (which isn't a valid ISO code — the correct code is "US"), every single update to that user record would fail. Including round-robin changes.

I confirmed the scope of the problem:

// How many users have the invalid country code? var gr = new GlideRecord('sys_user'); gr.addQuery('country', 'USA'); gr.query(); gs.print('Users with invalid USA country code: ' + gr.getRowCount()); // What's the correct code? var cc = new GlideRecord('core_country'); cc.addQuery('name', 'United States of America'); cc.query(); if (cc.next()) { gs.print('Correct ISO code: ' + cc.iso3166_2); }

Hundreds of users had "USA" instead of "US." The fix was a simple data correction — update the country codes to the proper ISO standard. Once we did that on DEV, tested it, and then applied the same fix on PROD through a change request, round-robin worked perfectly again.

The lesson here is critical: A Business Rule with setAbortAction(true) silently kills the entire record update — not just the field it's validating. Users don't see why. The UI just swallows the failure. Background Scripts was the only way to identify that a country code validation BR was blocking round-robin changes. These are the kinds of connections you'd never make just by looking at the UI.

These war stories are just examples from my own experience. You'll find your own. The point is: always test on DEV first, be extra cautious on PROD , and remember that Background Scripts gives you the server-side visibility you need. Be careful though — a careless script can delete or update records you didn't intend to touch. Double-check your queries, verify your scope, and treat every Background Script execution with the respect it deserves.

The Safety Rules I Live By

Before I go any further, I want to be very clear about something: Background Scripts is one of the most powerful — and most dangerous — tools in ServiceNow. One wrong script can update thousands of records in seconds. There's no "Are you sure?" popup. There's no undo button unless you've checked the rollback box. I've seen well-intentioned scripts cause production incidents because someone didn't double-check their query scope, ran against the wrong table, or forgot they were logged into PROD instead of DEV.

Every single time I open Background Scripts, I treat it with the same caution I'd give to running commands as root on a production server. Double-check everything. Triple-check your query. Verify which instance you're connected to. Read your script one more time before you hit "Run script." This tool has no guardrails — you are the guardrail.

Here are the rules I never break:

Rule #1: Always test your query BEFORE your update. Comment out the .update() line first. Run the script with just gs.print(gr.getRowCount()) to verify you're targeting exactly the records you think you are. If the count surprises you, stop. Investigate. Only uncomment the update when you're 100% confident.

Rule #2: Use setWorkflow(false) when you don't want side effects. If you're doing a data fix, you probably don't want every business rule, notification, and flow firing. Add gr.setWorkflow(false) and gr.autoSysFields(false) before your update.

Rule #3: Always check "Record for rollback." This checkbox at the bottom of Background Scripts is checked by default. Leave it checked. If something goes wrong, you can undo it from Rollback & Recovery → Script Execution History. Entries are kept for 7 days.

Rule #4: NEVER run update scripts directly in production without testing in sub-production first. I don't care how confident you are. Test in DEV. Test in QA. Then — and only then — run in PROD. And before you hit Run, confirm which instance tab you're on. I always double-check the URL in my browser bar. A simple mistake of running in the wrong environment can turn a harmless test into a production incident.

Rule #5: If you're not sure, don't run it. There's no shame in asking a colleague to review your script before execution. I've caught my own mistakes just by re-reading a script five minutes later with fresh eyes. Background Scripts gives you zero confirmation dialogs and zero second chances. The only safety net is the one you build yourself through discipline and double-checking.

Features Worth Exploring

Script Execution History: Every script you run is saved in the sys_script_execution_history table. Lost your browser tab? Crashed your session? Type "Script Execution History" in the Application Navigator. Your script is there — along with when you ran it, how long it took, and the SQL count and processing time. It's your personal backup and audit trail.

Rollback Capability: If you checked "Record for rollback" (which you should always do), you can literally undo your script's changes. Open the execution history record and click "Rollback Script Execution" in the Related Links. Note: the history is auto-flushed after 7 days, so don't wait too long.

The New Modern Editor: If you're still seeing the old editor, check your URL. The new one is at sys.scripts.modern.do and includes line numbers, syntax highlighting, IntelliSense, and error indicators. Delete your old bookmarks and create new ones.

My Background Scripts Diagnostic Toolkit

Over the years, I've built up a collection of diagnostic scripts that I keep handy. Here are a few patterns I use constantly:

Quick Record Count with Conditions

// How many records match my criteria? var gr = new GlideRecord('cmdb_ci_certificate'); gr.addQuery('expiration_date', '<', gs.daysAgoStart(0)); gr.query(); gs.print('Expired certificates: ' + gr.getRowCount());

Find Business Rules on a Table

// What business rules are active on this table? var br = new GlideRecord('sys_script'); br.addQuery('table', 'sc_task'); br.addQuery('active', true); br.orderBy('order'); br.query(); while (br.next()) { gs.print(br.when + ' | Order: ' + br.order + ' | ' + br.name); }

Check Field-Level Write Access

// Can I actually write to specific fields? var gr = new GlideRecord('incident'); gr.get('INC0012345'); var fields = ['priority', 'assigned_to', 'state', 'short_description']; for (var i = 0; i < fields.length; i++) { gs.print(fields[i] + ': ' + (gr[fields[i]].canWrite() ? '✅ Writable' : '❌ Blocked')); }

Why I'm Writing This Article

I looked through the ServiceNow Community before writing this. There are some great resources out there — solid introductory tutorials, a helpful comparison between Fix Scripts and Background Scripts, and Chuck Tomasi's excellent post about the modern editor upgrade. What I wanted to add to the conversation is the diagnostic side — using Background Scripts as a troubleshooting methodology, not just a place to paste code.

That's the perspective I'm hoping to share. Background Scripts isn't just a tool. It's a mindset. When something breaks, my first instinct is: "Let me go prove what's actually happening with data." Not guess. Not assume. Prove it.

Every war story in this article started with me opening Background Scripts and asking the data a question. The data always told me the truth.

What's Next

I'll be recording a video walkthrough of these techniques on our NowDivas YouTube channel, demonstrating live troubleshooting with Background Scripts including reading output types, recognizing chatty vs. silent errors, and building diagnostic scripts in real time. I've also submitted a session on this topic for Knowledge 2026 / CreatorCon — I'd love to share these stories with a wider audience.

If even one person reads this article and thinks "I should try Background Scripts first next time something breaks" — then writing it was worth every minute.

Have your own Background Scripts war story? I'd love to hear it in the comments. And if you've got a favorite diagnostic script you keep in your back pocket, share it — let's build a community toolkit together.

— Selva Arun 💚

ServiceNow MVP 2026 | Rising Star 2024 & 2025 | Co-founder, NowDivas

🎥 NowDivas YouTube  |  💬 ServiceNow Community  |  🔗 LinkedIn

View original source

https://www.servicenow.com/community/developer-articles/background-scripts-the-first-place-to-go-when-something-breaks/ta-p/3498872