Skip to main content
Version: 4.6.0

Platform Views, Lists & Relationships

Guide for configuring ServiceNow views, view rules, lists, list controls, and relationships using the Fluent API.


Views (sys_ui_view)

Views define which fields, sections, and layout appear on a form or list for a given table. A view definition alone is non-functional -- it must be combined with form/list components.

Choosing the View Type

If User SaysView TypeAction
"simple", "basic", "default", "standard"Defaultimport { default_view } from '@servicenow/sdk/core' -- no Record needed
"admins", "managers", "ITIL", "role"Role-basedSet roles array
"team", "department", "group"Group-basedSet group reference
"portal", "mobile app", "API", "hidden"HiddenSet hidden: true
Named individual ("John", "Dr. Smith")User-specificSet user reference
"everyone", "all users", "no restrictions"PublicOmit access control fields

CRITICAL: Uniqueness Check

Both name and title must each be globally unique across all scopes. Query before creating:

table: sys_ui_view
query: name=<proposed_name>^ORtitle=<proposed_title>

If results > 0, change both name and title and re-query. Scope prefixes do not guarantee uniqueness.

UI View Properties

PropertyTypeRequiredDescription
$idNow.ID[string]YesUnique identifier
tablestringYesMust be "sys_ui_view"
data.namestringYesUnique technical name (max 80)
data.titlestringYesUnique display name (max 80)
data.rolesstring[]NoArray of role name strings
data.userstringNosys_id or reference to sys_user
data.groupstringNosys_id or reference to sys_user_group
data.hiddenbooleanNoHide from platform view selector

View Examples

Public view

import { Record } from '@servicenow/sdk/core';

export const mobileView = Record({
$id: Now.ID['mobile-view'],
table: 'sys_ui_view',
data: {
name: 'incident_mobile',
title: 'Mobile View',
},
});

Role-based view

import { Record } from '@servicenow/sdk/core';

export const adminView = Record({
$id: Now.ID['admin-view'],
table: 'sys_ui_view',
data: {
name: 'incident_admin',
title: 'Admin View',
roles: ['admin'],
},
});

Group-based view

import { Record } from '@servicenow/sdk/core';

export const supportGroup = Record({
$id: Now.ID['support-group'],
table: 'sys_user_group',
data: { name: 'support_team', description: 'Support Team', active: true },
});

export const supportView = Record({
$id: Now.ID['support-view'],
table: 'sys_ui_view',
data: {
name: 'incident_support_team',
title: 'Support Team View',
group: supportGroup,
},
});

Hidden view (Portal/API)

import { Record } from '@servicenow/sdk/core';

export const portalView = Record({
$id: Now.ID['portal-view'],
table: 'sys_ui_view',
data: {
name: 'sp_incident_customer',
title: 'Customer Portal View',
hidden: true,
},
});

Using default_view

import { default_view } from '@servicenow/sdk/core';
import { Record } from '@servicenow/sdk/core';

export const section = Record({
$id: Now.ID['disaster-report-section'],
table: 'sys_ui_section',
data: {
name: 'u_disaster_report',
view: default_view,
},
});

Forms Integration with Views

Hierarchy: UI View -> Form -> Sections -> Elements (fields/formatters/related lists)

A form requires explicit linking between forms and sections using the sys_ui_form_section join table. Without sys_ui_form_section records, your form will appear EMPTY.

Complete form with multiple sections

import { Record } from '@servicenow/sdk/core';
import { managerView } from './views';

// 1. Create Form
export const managerForm = Record({
$id: Now.ID['manager-form'],
table: 'sys_ui_form',
data: { name: 'incident', view: managerView, active: true },
});

// 2. Create Sections
export const detailsSection = Record({
$id: Now.ID['details-section'],
table: 'sys_ui_section',
data: { name: 'incident', view: managerView, caption: 'Case Details', position: 0 },
});

export const assignmentSection = Record({
$id: Now.ID['assignment-section'],
table: 'sys_ui_section',
data: { name: 'incident', view: managerView, caption: 'Assignment', position: 1 },
});

// 3. CRITICAL: Link Sections to Form
export const formDetailsLink = Record({
$id: Now.ID['form-details-link'],
table: 'sys_ui_form_section',
data: { sys_ui_form: managerForm, sys_ui_section: detailsSection, position: 0 },
});

export const formAssignmentLink = Record({
$id: Now.ID['form-assignment-link'],
table: 'sys_ui_form_section',
data: { sys_ui_form: managerForm, sys_ui_section: assignmentSection, position: 1 },
});

// 4. Add Fields to Sections
export const numberField = Record({
$id: Now.ID['number-field'],
table: 'sys_ui_element',
data: { element: 'number', sys_ui_section: detailsSection, position: 0, type: 'element' },
});

export const assignedToField = Record({
$id: Now.ID['assigned-to-field'],
table: 'sys_ui_element',
data: { element: 'assigned_to', sys_ui_section: assignmentSection, position: 0, type: 'element' },
});

Form element types

TypeDescription
elementStandard field
formatterCustom formatter
listRelated list
.begin_splitOpen 2-column area
.splitColumn divider
.end_splitClose 2-column area
.spaceEmpty space

Column layout with splits

Use splits for short fields (state, priority, category). Never split text areas (description, work_notes) or related lists -- these should be full width after .end_split.

.begin_split  -> opens 2-column area
[left column fields]
.split -> column divider
[right column fields]
.end_split -> closes 2-column area
[full-width content: text areas, related lists]

Positioning: Use increments of 10 (0, 10, 20...) to allow easy insertion later.


View Rules (sysrule_view)

View Rules automatically switch the form layout based on conditions, device type, or script logic. They require existing views -- views must exist in sys_ui_view first.

Three Switching Approaches

  1. Device-Based: Set device_type ('mobile', 'tablet', 'browser')
  2. Condition-Based: Set condition with encoded query (MUST end with ^EQ)
  3. Script-Based: Set advanced: true with custom script

Evaluation Order

  1. Active rules only (active: true)
  2. Device type match
  3. Condition satisfied
  4. Script execution (if advanced: true)
  5. First match wins
  6. User preference (unless overrides_user_preference: true)

CRITICAL: Encoded Query Requirements

  • Must end with ^EQ -- all encoded queries must terminate with ^EQ.
  • Use backend field names -- element names from sys_dictionary, not labels.
  • Use internal values -- values from sys_choice, not display labels.
WRONGCORRECTWhy
Priority=1^EQpriority=1^EQField name lowercase
priority=Critical^EQpriority=1^EQUse internal value
state=Closed^EQstate=7^EQUse state number
priority=1priority=1^EQMust end with ^EQ

CRITICAL: One Advanced Rule Per Device Type

When multiple advanced (script-based) View Rules share the same table AND device_type, only the rule with the lowest order value is evaluated. Others are skipped. Solution: Combine all role/condition checks into a single script.

View Rule Properties

PropertyTypeRequiredDescription
$idNow.ID[string]YesUnique identifier
tablestringYesMust be "sysrule_view"
data.namestringYesDescriptive name
data.tablestringYesTarget table name
data.viewstringNoView name (from sys_ui_view.name, not title). Required unless using script
data.conditionstringNoEncoded query ending with ^EQ
data.device_typestringNo'browser', 'mobile', or 'tablet'
data.activebooleanNoDefault: true
data.overrides_user_preferencebooleanNoOverride manual selection. Default: true
data.advancedbooleanNoEnable custom script. Default: false
data.scriptstringNoJavaScript logic (when advanced: true)
data.ordernumberNoEvaluation order (lower first). Default: 100

Advanced Script Variables

VariableTypeAvailabilityDescription
viewstringAlwaysCurrent view name
is_listbooleanAlwaystrue for lists, false for forms
currentGlideRecordForms onlyCurrent record (undefined for lists)
answerstring/nullAlwaysSet to view name to switch
gsGlideSystemAlwaysGlideSystem API

Always check !is_list && typeof current !== 'undefined' before accessing current.

View Rule Examples

Device-based switching

import { Record } from '@servicenow/sdk/core';

export const mobileRule = Record({
$id: Now.ID['mobile-rule'],
table: 'sysrule_view',
data: {
name: 'Mobile View Rule',
table: 'incident',
view: 'mobile',
device_type: 'mobile',
active: true,
overrides_user_preference: true,
},
});

Condition-based switching

export const criticalRule = Record({
$id: Now.ID['critical-rule'],
table: 'sysrule_view',
data: {
name: 'Critical Priority Rule',
table: 'incident',
view: 'critical',
condition: 'priority=1^ORpriority=2^EQ',
active: true,
overrides_user_preference: true,
},
});

Role-based switching (advanced script)

export const roleRule = Record({
$id: Now.ID['role-rule'],
table: 'sysrule_view',
data: {
name: 'Role-Based View Rule',
table: 'incident',
view: null,
advanced: true,
active: true,
overrides_user_preference: true,
script: `(function overrideView(view, is_list) {
var user = gs.getUser();
if (user.hasRole('admin')) {
answer = 'admin_view';
} else if (user.hasRole('manager')) {
answer = 'manager_view';
} else if (user.hasRole('agent')) {
answer = 'agent_view';
} else {
answer = 'ess';
}
})(view, is_list);`,
},
});

Complex conditional logic (forms only)

export const complexRule = Record({
$id: Now.ID['complex-rule'],
table: 'sysrule_view',
data: {
name: 'Complex Conditional Rule',
table: 'incident',
view: null,
advanced: true,
active: true,
overrides_user_preference: true,
script: `(function overrideView(view, is_list) {
if (!is_list && typeof current !== 'undefined') {
var priority = current.priority.toString();
var state = current.state.toString();
if (priority === '1' && state === '2') {
answer = 'critical_active_view';
} else if (priority === '1' && state === '7') {
answer = 'critical_closed_view';
} else {
answer = null;
}
}
})(view, is_list);`,
},
});

Lists (sys_ui_list)

Use the List API from @servicenow/sdk/core. Requires table, view, and columns.

List Properties

PropertyTypeRequiredDescription
tablestringYesTable name for the list
viewReferenceYesUI view variable or default_view
columnsarrayYesList of ListElement objects (or string shorthand)
parentTableNameNoParent table for related lists
relationshipsys_relationshipNoCustom relationship for related lists
$metaobjectNoInstallation metadata (demo, first install)

List Element Properties

PropertyTypeRequiredDescription
elementstringYesField name (supports dot-walking, e.g., "caller_id.name")
positionnumberNoDisplay position (defaults to array order)
sumbooleanNoShow sum aggregate
averageValuebooleanNoShow average aggregate
minValuebooleanNoShow minimum aggregate
maxValuebooleanNoShow maximum aggregate

List Examples

Basic list with column objects

import { List } from '@servicenow/sdk/core';

const serverList = List({
table: 'cmdb_ci_server',
view: app_task_view,
columns: [
{ element: 'name', position: 0 },
{ element: 'business_unit', position: 1 },
{ element: 'vendor', position: 2 },
{ element: 'cpu_type', position: 3 },
],
});

When a simple reference field exists (e.g., table.field), no relationship sys_id is needed:

import { List, default_view } from '@servicenow/sdk/core';

List({
table: 'project_task',
view: default_view,
parent: 'project',
columns: ['assigned_to', 'short_description', 'due_date', 'state'],
});

When no simple reference field exists, import the relationship and reference it:

import { List, default_view } from '@servicenow/sdk/core';
import { skillMatchedPlayersRelationship } from '../relationships/game_allotment_relationships.now';

export const skillList = List({
table: 'sn_sportshub_players',
view: default_view,
parent: 'sn_sportshub_sports',
relationship: skillMatchedPlayersRelationship,
columns: [
{ element: 'first_name', position: 0 },
{ element: 'gender', position: 1 },
{ element: 'email', position: 2 },
{ element: 'skill_level', position: 3 },
],
});

List Controls (sys_ui_list_control)

List Controls configure UI options on table lists and related lists -- role-based New/Edit button visibility, disable pagination, conditional button hiding.

Key Guidance

  1. Each list control needs a unique $id, table: 'sys_ui_list_control', and valid name (target table).
  2. For related lists, use related_list in table.field or REL:sys_id format.
  3. Do not combine omit_*_button: true with *_roles -- the omit flag overrides role permissions.
  4. Button visibility is OR logic: hidden if omit_*_button == true OR *_condition evaluates to true.
  5. Use omit_count: true for large tables (>10,000 records) for performance.
  6. Condition scripts use Now.include() for external files.

List Control Properties

PropertyTypeRequiredDefaultDescription
nameTableNameYesTarget table name
related_liststringNotable.field or REL:sys_id format
labelstringNoDisplay label for list
omit_new_buttonbooleanNofalseHide New button for everyone
omit_edit_buttonbooleanNotrueHide Edit button for everyone
omit_linksbooleanNofalseHide reference links
omit_drilldown_linkbooleanNofalseDisable first-column drilldown link
omit_filtersbooleanNofalseHide filters/breadcrumbs
omit_if_emptybooleanNofalseHide related list when empty
omit_countbooleanNofalseRemove pagination count
omit_related_list_countbooleanNofalseRemove related list count in Workspace
new_rolesstring[]NoRoles that can see New button
edit_rolesstring[]NoRoles that can see Edit button
filter_rolesstring[]NoRoles that can see filters
link_rolesstring[]NoRoles that can see links
new_conditionScriptNoCondition script to hide New button
edit_conditionScriptNoCondition script to hide Edit button
list_edit_typestringNo'save_by_row', 'disabled', or omit for default
list_edit_ref_qual_tagstringNoTag passed to reference qualifier scripts
hierarchical_listsbooleanNofalseEnable hierarchical list display
disable_nlqbooleanNofalseDisable Natural Language Query
activebooleanNotrueWhether control is active

Condition Script Pattern

var answer;
if (parent.state == 6 || parent.state == 7) {
answer = true; // hide button
} else {
answer = false; // show button
}
answer;
  • parent provides access to parent record fields.
  • answer = true hides the button; answer = false shows it.

List Control Examples

Performance optimization for large table

import { Record } from '@servicenow/sdk/core';

export const auditControl = Record({
$id: Now.ID['audit-list-control'],
table: 'sys_ui_list_control',
data: {
name: 'sys_audit',
omit_count: true,
omit_related_list_count: true,
},
});

Role-based button access

import { Record } from '@servicenow/sdk/core';

export const roleControl = Record({
$id: Now.ID['role-based-access'],
table: 'sys_ui_list_control',
data: {
name: 'incident',
new_roles: ['admin', 'itil'],
edit_roles: ['admin'],
},
});
import { Record } from '@servicenow/sdk/core';

export const conditionalControl = Record({
$id: Now.ID['incident-conditional-button'],
table: 'sys_ui_list_control',
data: {
name: 'incident',
related_list: 'incident.parent_incident',
new_condition: Now.include('../scripts/hideForClosedIncident.js'),
edit_condition: Now.include('../scripts/hideForClosedIncident.js'),
},
});
import { Record } from '@servicenow/sdk/core';

export const hideIfEmpty = Record({
$id: Now.ID['omit-if-empty'],
table: 'sys_ui_list_control',
data: {
name: 'incident',
related_list: 'incident.parent_incident',
omit_if_empty: true,
},
});
import { Record } from '@servicenow/sdk/core';

export const filterRoles = Record({
$id: Now.ID['filter-role-control'],
table: 'sys_ui_list_control',
data: {
name: 'incident',
filter_roles: ['admin', 'report_viewer'],
link_roles: ['admin', 'itil', 'user'],
},
});

Disable list editing

import { Record } from '@servicenow/sdk/core';

export const disableEdit = Record({
$id: Now.ID['disable-list-edit'],
table: 'sys_ui_list_control',
data: {
name: 'x_snc_financial_records',
list_edit_type: 'disabled',
},
});

List control on custom relationship

import '@servicenow/sdk/global';
import { Record } from '@servicenow/sdk/core';
import { activeHighPriorityRelationship } from '../relationships/game_allotment_relationships.now';

export const customRelControl = Record({
$id: Now.ID['sports_table_list_control'],
table: 'sys_ui_list_control',
data: {
name: 'sn_sportshub_sports',
related_list: `REL:${activeHighPriorityRelationship.$id}`,
omit_related_list_count: 'true',
},
});

Relationships (sys_relationship)

Relationships define how tables relate for related lists and cross-table queries.

Relationship Properties

PropertyTypeRequiredDescription
namestringNoDescriptive name
basic_apply_toTableNameNoTable where relationship is defined (basic)
basic_query_fromTableNameNoTable being referenced (basic)
reference_fieldFieldNameNoField containing the reference
query_withScriptNoScript to refine the query
advancedbooleanNoWhether this is an advanced relationship
simple_referencebooleanNoWhether this is a simple reference
apply_toScriptNoScript for advanced: which table applies
query_fromScriptNoScript for advanced: which table to query

Either use basic fields (basic_apply_to, basic_query_from) or advanced fields (apply_to, query_from) -- never both.

Related lists use two tables:

  • sys_ui_related_list -- container for a table's related lists in a view
  • sys_ui_related_list_entry -- individual entries linking to relationships

For referential relationships: use table.reference_field format in the entry. For non-referential relationships: use REL:<relationship_sys_id> format.

Relationship Examples

Basic relationship between custom tables

import { Record } from '@servicenow/sdk/core';

export const deptAllocation = Record({
$id: Now.ID['department_rel_id'],
table: 'sys_relationship',
data: {
advanced: false,
basic_apply_to: 'sn_foo_department',
basic_query_from: 'sn_foo_student',
name: 'Department Allocation Relationship',
query_with: `(function refineQuery(current, parent) {
current.addQuery('department', parent.id);
})(current, parent);`,
simple_reference: false,
},
});
import { Record } from '@servicenow/sdk/core';

const deptRelatedList = Record({
$id: Now.ID['department_related_list_id'],
table: 'sys_ui_related_list',
data: {
calculated_name: 'Department - Default view',
name: 'sn_foo_department',
view: 'Default view',
},
});

Record({
$id: Now.ID['department_related_list_entry_id'],
table: 'sys_ui_related_list_entry',
data: {
list_id: deptRelatedList.$id,
position: '0',
related_list: `REL:${deptAllocation.$id}`,
},
});
const productContainer = Record({
$id: Now.ID['products_related_lists'],
table: 'sys_ui_related_list',
data: { name: 'sn_product_life_products', view: 'Default view' },
});

Record({
$id: Now.ID['feature_requests_entry'],
table: 'sys_ui_related_list_entry',
data: {
list_id: productContainer.$id,
position: 0,
related_list: 'feature_requests.product',
},
});

Record({
$id: Now.ID['testing_reports_entry'],
table: 'sys_ui_related_list_entry',
data: {
list_id: productContainer.$id,
position: 1,
related_list: 'testing_reports.product',
},
});