- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
3 hours ago
Why Is My Choice Field Showing in Blue? Real Causes, Real Fixes
Applies to: All supported ServiceNow versions • Task-extended and custom tables
If you have spent enough time in ServiceNow, you have probably seen it. A choice field on a list or form where the value shows up in blue, while everything around it looks fine. First instinct is usually to clear the cache or blame a browser issue. It is not that.
Blue text on a choice field means one thing: the raw value stored in the database has no active matching entry in sys_choice. That much is easy to understand. What is harder is figuring out how the mismatch got there in the first place, because there are more ways than you would expect.
I ran into one of the less obvious ones on a recent project and spent longer than I would like to admit figuring it out. So I am writing up that case along with every other cause I have come across, so there is one place to check instead of hunting across KB articles and forum threads.
- First: confirm what is actually stored
- Case 1 — Transform map wrote sys_import_state into a choice field
- Case 2 — Choice moved off the Selected list in Configure Choices
- Case 3 — sys_choice record deactivated or deleted directly
- Case 4 — The choice value itself was renamed
- Case 5 — Child table stopped inheriting parent choices
- Case 6 — A flow or script wrote a raw string to the field
- Case 7 — Domain separation (choice missing from the child domain)
- Things to keep in mind regardless of cause
- Quick reference
First: Confirm What Is Actually Stored
Before going anywhere else, run this in System Definition → Scripts - Background and find out what the field actually holds at the database level. Substitute your table and field names.
var ga = new GlideAggregate('your_table_name');
ga.addAggregate('COUNT');
ga.groupBy('your_choice_field');
ga.query();
while (ga.next()) {
gs.info(
'raw value="' + ga.getValue('your_choice_field') + '"' +
' | count=' + ga.getAggregate('COUNT')
);
}
Once you know the exact string sitting in the database, you can match it against the causes below. That value is your starting point for everything else.
Case 1: Transform Map Wrote sys_import_state Into a Choice Field
This is the one that prompted this article. We had a custom field called u_sub_state on a table extending task. After running a bulk import, hundreds of records showed the word "pending" in blue. There was no "pending" anywhere in the choice list, active or inactive. Searching sys_choice turned up nothing.
The GlideAggregate script confirmed that the literal string pending was written directly into u_sub_state on every affected record. That ruled out anything display-related. Something had physically put that string in the field during the import.
I checked the transform map next. Querying sys_transform_entry for everything targeting u_sub_state showed one entry with source_field = "sys_import_state". That was it.
var fm = new GlideRecord('sys_transform_entry');
fm.addQuery('target_field', 'u_sub_state');
fm.query();
while (fm.next()) {
gs.info(
'Map="' + fm.getDisplayValue('map') + '"' +
' | source="' + fm.getValue('source_field') + '"' +
' | target="' + fm.getValue('target_field') + '"' +
' | choice_action="' + fm.getValue('choice_action') + '"'
);
}
sys_import_state is the internal processing status of each import set row. While the transform is still running, every row holds sys_import_state = "pending". A field map had been set up that picked up that column and wrote it directly into the target table's choice field.
It gets messier. We had already added our own sys_choice entries to the child table for this field. The moment you do that, the child table stops inheriting choice values from the parent table entirely for that field. So even if "pending" had existed on the parent's choice list, it would not have resolved. Every record with pending stored was now orphaned with nowhere to go.
The fix has three parts. First, go to System Import Sets → Transform Maps → [your map] → Field Maps and delete the sys_import_state → u_sub_state mapping. Map from the correct source field instead. Also set Choice action = Ignore on the mapping. That one setting alone prevents any unrecognized value from ever landing in a choice field going forward.
Second, clear the stale data from every affected record:
var gr = new GlideRecord('your_table_name');
gr.addQuery('u_sub_state', 'pending');
gr.query();
var count = 0;
while (gr.next()) {
gr.setValue('u_sub_state', '');
gr.update();
count++;
}
gs.info('Cleared u_sub_state on ' + count + ' records.');
The blue values go away as soon as the script finishes. No cache clear needed.
Third, check whether there is a stale default value sitting in a dictionary override for this table. If there is, it will silently repopulate every new record created on the table with the bad value.
var dictOverride = new GlideRecord('sys_dictionary_override');
dictOverride.addQuery('element', 'u_sub_state');
dictOverride.addQuery('name', 'your_table_name');
dictOverride.query();
while (dictOverride.next()) {
gs.info(
'default_value="' + dictOverride.getValue('default_value') + '"' +
' | override="' + dictOverride.getValue('default_value_override') + '"'
);
}
sys_choice as an inactive entry does not fix the problem. It hides the blue indicator but the bad data is still sitting in every record. Fix the data, fix the transform map, and the blue disappears cleanly.Case 2: Choice Moved Off the Selected List in Configure Choices
This is the most common cause you will find in community threads and KB articles (see KB0743723). Someone used the Configure Choices UI to move a value from the Selected side to the Available side. The value stops appearing in the dropdown, but records that already had it stored still hold that raw string. Those records now show it in blue.
To check: open a record on the affected table, right-click the field label of the choice field on the form, and select Configure Choices. This gives you the slush bucket with the Selected and Available lists. If the blue value is sitting on the Available side, this is your cause. Move it back to Selected if it should still be valid. If you are retiring it, run a data migration script to update affected records first, then leave it on the Available side.
Case 3: sys_choice Record Deactivated or Deleted Directly
Same outcome as Case 2, different path. Instead of using Configure Choices, someone went directly into the sys_choice table and either deactivated or deleted the record. Historical records holding that value now have nothing to match against.
Run this to find out which situation you are in:
var sc = new GlideRecord('sys_choice');
sc.addQuery('name', 'your_table_name');
sc.addQuery('element', 'your_choice_field');
sc.addQuery('value', 'the_blue_raw_value');
sc.query();
if (sc.next()) {
gs.info('Found | inactive="' + sc.getValue('inactive') + '"');
} else {
gs.info('No sys_choice record found for this value');
}
If it is inactive, reactivate it if the choice is still valid, or migrate the data and leave it inactive. If it was deleted, restore from an update set or recreate it, then decide whether to keep it or clean up the data and remove it properly.
Case 4: The Choice Value Itself Was Renamed
This one catches people out because renaming a choice looks harmless. But if someone changed the stored value field (not just the display label), every record that had the old value is now orphaned. The label updates on the UI but the database still holds the original string.
For example: a choice had label "Addition Old" and value "Addition Old". Both were changed to "Addition New". Records that stored "Addition Old" now have no matching sys_choice entry. The fix is to create the corrected choice entry, run a script to update all affected records from the old value to the new one, then deactivate the old entry once the data is clean. Do not delete it until you have confirmed there are no records left referencing it.
Case 5: Child Table Stopped Inheriting Parent Choices
This one is subtle and easy to walk into without realizing. When a child table has no sys_choice entries of its own for a given field, it inherits from the parent table. The moment you add even one custom choice to the child table for that field, inheritance stops completely for that field. Any values from the parent's choice list that are stored in old records on the child table are now orphaned.
You can check how many custom choices your child table has for the field:
var sc = new GlideRecord('sys_choice');
sc.addQuery('name', 'your_child_table');
sc.addQuery('element', 'your_choice_field');
sc.query();
gs.info('Child table has ' + sc.getRowCount() + ' own choice entries');
If that returns more than zero and you have blue values, check whether any of those blue raw values exist only on the parent table's choice list. The fix is to add the missing values explicitly as sys_choice entries on the child table, or migrate the affected records to a choice that already exists there.
Case 6: A Flow or Script Wrote a Raw String to the Field
Flows, Business Rules, and scripts bypass choice validation. They can write any raw string into a choice field and ServiceNow will not stop them. The most common ways this happens: a Flow action that uses a data pill from a string field to populate a choice field (the pill carries the raw string over, not a validated choice value), a Business Rule that sets the field from an unvalidated variable, or a Record Producer variable mapped to a choice field without sanitization.
To track it down, check when the affected records were created or last modified and look for any automation running at that time. Business Rule logs and Flow execution history are the right starting points.
In flows, replace the direct data pill with a script step that sets the validated choice value explicitly. You can use a helper function to verify before writing:
function isValidChoice(table, field, value) {
var sc = new GlideRecord('sys_choice');
sc.addQuery('name', table);
sc.addQuery('element', field);
sc.addQuery('value', value);
sc.addQuery('inactive', false);
sc.setLimit(1);
sc.query();
return sc.hasNext();
}
Case 7: Domain Separation (Choice Missing From the Child Domain)
If your instance uses domain separation, choice list values follow domain hierarchy the same way other records do. Once a child domain has its own set of choices for a field, it stops inheriting from the parent domain. If a value is defined in the parent domain but not in the child, any records in the child domain that stored that value will show it in blue.
The fix is to make sure all required choice values are created in the correct child domain. KB0725678 and the ServiceNow docs on domain-specific choice lists have more detail on how this inheritance works.
Things to Keep in Mind Regardless of Cause
A few things I have learned to apply every time now, no matter which of the above caused the problem:
- Blue is always a data problem, not a display problem. Check what is stored before touching anything else.
- Do not add an inactive
sys_choiceentry just to make the blue go away. That is a cosmetic patch on real bad data. - Do not turn off the "Display missing choice list entries" system property. That suppresses the indicator across the whole instance and hides problems everywhere.
- Set Choice action = Ignore on every choice field mapping in a transform map. It costs nothing and prevents this whole class of issue.
- After any import that touches a choice field, run GlideAggregate on that field before closing the ticket. You will know immediately if something unexpected got written in.
- On child tables, always check dictionary overrides for unexpected default values. A bad default silently populates every new record created on that table.
Quick Reference
| When did the blue appear? | Most likely cause | First place to check |
|---|---|---|
| After an import or transform run | sys_import_state or wrong source field mapped in transform | sys_transform_entry for that target field |
| After an admin made a config change | Choice moved to Available in Configure Choices UI | Right-click column header → Configure Choices → Available list |
| After a choice list cleanup | sys_choice record deactivated or deleted directly | Query sys_choice for the raw value (with inactive=true or no result) |
| On old records after someone edited a choice | The value field on the choice was renamed | Compare stored raw value against current sys_choice value field |
| On a child table after adding custom choices | Parent choice inheritance broken by child-table entries | Count sys_choice entries on child table for that field |
| After a flow or business rule ran | Automation wrote an unvalidated raw string | Business Rule logs or Flow execution history near the record modified date |
| Only in specific domains | Choice not created in the child domain | sys_choice records filtered by domain column |
That covers every case I have run into. If you have hit a different root cause that is not listed here, drop it in the comments and I will add it.
Thank You!!