Every field on a ServiceNow form is a row in the dictionary (sys_dictionary). That row says what type the field is, how long it can be, whether it’s mandatory, what its default value is, which table a reference points to and a set of attributes that tweak its behaviour. Because tables inherit from each other (incident extends task, for example), a single dictionary entry can govern a field on dozens of tables at once. Auditing sits on top of this: when it’s on for a table, each change to a field becomes a row in the audit table, and that’s what the history and activity views are built from.
Interviewers ask about both together because the decisions overlap. “Where should this change go?” is a dictionary question. “How do we prove who changed it?” is an audit question. Getting either wrong is expensive to undo once a table has a few million rows.
What the dictionary entry controls
Open any field’s dictionary entry and the main settings are easy to read: type, column label, column name, max length, mandatory, read only, default value, reference and reference qualifier, plus a free-form attributes field. A table also has its own dictionary entry, the collection record with an empty column name, and that’s where table-wide settings such as auditing live.
The entry is defined on the table that introduces the field. assigned_to, short_description, state and assignment_group are all defined on task. When you look at the assigned_to field on an incident form, you’re looking at the task dictionary entry. So if you edit that entry to make the field mandatory, you’ve made it mandatory on incident, problem, change request, catalog task, HR case and every other table that extends task on the instance. That surprise is the classic mistake in this area, and it usually shows up as a broken integration that creates catalog tasks without an assignee.
Some changes are also hard to reverse. Shortening a max length on a table with data can truncate values, and only certain type changes are allowed once a column holds data (ServiceNow documents which conversions are supported). Treat edits on parent tables as platform changes, not form tweaks.
Dictionary overrides: change one child only
A dictionary override (sys_dictionary_override) changes selected properties of an inherited field for one child table. You pick the base table and field, the child table, and which properties to override. Typical ones are the default value, mandatory, read only, the reference qualifier, and attributes.
Here’s a realistic case. The service desk wants assigned_to mandatory on incidents once they leave the New state, and they want new incidents to default to the Service Desk assignment group. Nobody wants either rule on change requests.
task.assignment_group dictionary entry default: (none)
incident.assignment_group dictionary override override default: javascript:gs.getProperty('acme.incident.default_group')
task.assigned_to dictionary entry mandatory: false
incident.assigned_to dictionary override (not used: mandatory depends on state)The default value goes in an override, because it’s a static fact about incidents. The mandatory rule doesn’t, because it depends on state: a dictionary-level mandatory flag applies from the moment the record is created. That rule belongs in a UI policy for the form and a data policy (or before business rule) so the server enforces it on imports and API calls too. Knowing when not to use an override is as useful in an interview as knowing what one is.
Note
The default group above reads a property so the sys_id isn’t hard-coded in the dictionary. The value is set once per instance, which is the kind of rarely changing configuration properties are meant for.
Overrides also show up when you upgrade. Because the base entry stays untouched, ServiceNow can update task without overwriting your incident-specific behaviour, and your change is visible as its own record in the update set.
How auditing records history
Auditing is enabled on the table’s collection dictionary entry with the Audit check box. Once it’s on, inserts and updates to that table write rows into sys_audit, one row per changed field. ServiceNow’s documentation lists the main columns: table name, field name, document key (the sys_id of the changed record), the user who made the change, old value and new value. Two details catch people out. Reference fields store sys_ids in the old and new value columns, not display values. Date and time fields are stored in UTC as they are in the database, not in the viewer’s time zone. Also, to avoid loops and performance problems, business rules and workflows don’t fire on inserts into sys_audit, so you can’t hang automation off it.
The history list and activity stream you see on a form aren’t read straight from sys_audit every time. The platform builds history sets (sys_history_set and sys_history_line) for the record when someone views its history, using the audit data and journal entries. That’s why the first history view of an old record can be slower than later ones.
Choosing what not to audit
Auditing a whole table sounds safe until you count rows. Take a custom table u_vendor_contract with 40,000 records and an integration that writes u_last_synced on every record once an hour, whether or not anything else changed. With auditing on and that field included, the sync alone adds:
40,000 records × 24 updates/day × 365 days = 350,400,000 sys_audit rows per yearNone of those rows answer a question anyone will ask. Exclude the field by adding the no_audit=true attribute to its dictionary entry, and the sync stops generating history while changes to the contract value, owner and end date are still recorded. The same applies to counters, “last viewed” timestamps and fields that a scheduled job recalculates nightly.
The opposite mistake is just as real: turning off auditing on a table that compliance depends on, because “the table is slow”. If auditors need to see who changed approval amounts, that field stays audited, and the fix for performance is elsewhere (usually the queries hitting sys_audit).
Reading audit data from a script
Sometimes you do need history in code, for example to find who last moved a contract to Terminated. Always filter by table and document key so the query uses the audit table’s index, and add the field name:
// Illustrative server script: who set state to terminated on this contract?
function lastStateChangeTo(tableName, sysId, value) {
var audit = new GlideRecord('sys_audit');
audit.addQuery('tablename', tableName);
audit.addQuery('documentkey', sysId);
audit.addQuery('fieldname', 'u_state');
audit.addQuery('newvalue', value);
audit.orderByDesc('sys_created_on');
audit.setLimit(1);
audit.query();
return audit.next() ? { user: audit.getValue('user'), when: audit.getValue('sys_created_on') } : null;
}An unfiltered sys_audit query, or one that filters only on fieldname, scans one of the largest tables on the instance. If reporting needs lots of history, consider a dedicated table populated by a business rule on the source table at change time, rather than mining sys_audit afterwards.
Interview practice: the mandatory field that broke an integration
A developer needed assigned_to required on HR cases and set Mandatory on the field’s dictionary entry. The next morning, the procurement integration that creates catalog tasks started failing. What happened, and how should it have been done?
Answer
assigned_to is defined on task, so the developer’s edit changed the base entry and made the field mandatory on every task-derived table, including sc_task. The integration creates catalog tasks before anyone is assigned, so its inserts were rejected. First, revert the base entry, and check the update set so the change doesn’t get promoted. Then decide how strict the HR rule really is. If an HR case must always have an assignee from creation, a dictionary override on the HR case table with mandatory set is fine, and it touches no other table. If it’s only required after triage, use a UI policy for the form plus a data policy or before business rule so imports and APIs follow the same rule. Either way, audit assigned_to on HR cases so you can show who changed assignment, and test the integration path, not just the form.