Skip to main content
Version: 4.6.0

Tables, Columns, and Relationships Guide

Guide for creating ServiceNow Tables (sys_db_object), Columns (sys_dictionary), and Relationships (sys_relationship) to define data models. Use when the user mentions tables, columns, fields, schema, extending tables, relationships, related lists, or data modeling.

When to Use

  • When creating new tables to store application data
  • When adding columns (fields) to new or existing tables
  • When extending an existing table (e.g., extending task)
  • When setting up relationships between tables or configuring related lists
  • When defining choices, defaults, or dynamic values for fields

Key Concepts

  • Tables define the data model. Columns define the fields. Relationships connect tables and display related records as lists on forms.
  • Implicit vs explicit relationships: If a reference field exists between tables, the relationship is implicit (no extra record needed). If not, create an explicit sys_relationship record.
  • To access the table using the Table API, allowWebServiceAccess must be enabled.

Instructions

Creating a New Table

  1. Table naming: The name must start with the application scope prefix (e.g., x_acme_my_table). The exported variable name MUST match the name property exactly.
  2. Choose column types carefully: Only use supported column types. Only import the types you use to avoid build errors.
  3. Extending tables: Use the extends property to inherit all fields from a base table. Only extend tables marked as extensible.
  4. Cross-scope access: Set accessibleFrom, callerAccess, and actions based on whether other scoped apps need access. Default to package_private unless public access is needed.
  5. Enable web service access: If the table will be accessed via the Table API (/api/now/table) or any REST integration, set allowWebServiceAccess: true. This defaults to false -- without it, REST calls return 403 "User Not Authorized" even when ACLs are correctly configured.

Adding Columns to an Existing Table

  1. When adding columns to a table in a different scope, provide the table name without the scope prefix followed by as any. Prefix column names with your application scope.
  2. Import only the column types you actually use -- unused imports cause build errors.

Setting Up Relationships

  1. Reference field exists between tables: Use implicit relationship (no sys_relationship needed).
  2. No reference field or custom query logic needed: Create explicit sys_relationship record.
  3. Adding existing platform relationship (e.g., Attachments): Use known REL: ID directly.

Avoidance

  1. Never mismatch the exported variable name and the name property -- they must be identical.
  2. Never use unsupported column types -- only use types listed in the Column Types section.
  3. Never mix basic and advanced relationship fields in the same record.
  4. Never omit the wrapper in query_with scripts -- must use (function refineQuery(current, parent) { ... })(current, parent); format.
  5. Never hardcode sys_id strings in record references -- use ${record.$id}.
  6. Never omit allowWebServiceAccess when the table will be accessed via REST -- it defaults to false, and the Table API will return 403 even with correct ACLs.

Table API Reference

For the full property reference and examples, see the table-api topic.


Column Type Reference

Column Type Selection Guide

DataColumn TypeNotes
Short textStringColumnmaxLength < 254 renders single-line
Long textMultiLineTextColumn or StringColumnmaxLength >= 255 renders multi-line
Dropdown/choicesChoiceColumn or StringColumn with choices + dropdownStringColumn allows free-text too
True/falseBooleanColumn
Whole numbersIntegerColumnSupports min and max
Decimal numbersDecimalColumn
Floating pointFloatColumnUse scale for decimal places
Foreign keyReferenceColumnSet referenceTable
Multi-value referenceListColumnSet referenceTable
Multi-select pickerSlushBucketColumnSet script for available items
Date onlyDateColumnFormat: yyyy-mm-dd
Date and timeDateTimeColumnFormat: yyyy-mm-dd HH:mm:ss
DurationDurationColumnDefault: { days, hours, minutes, seconds }
Time of dayTimeColumnDefault: { hours, minutes, seconds }
HTML/rich textHtmlColumnUse html() helper for defaults
URLUrlColumnMust be http:// or https://
EmailEmailColumnMust be user@domain format
JSONJsonColumnUse json() helper for defaults
Field listFieldListColumnNeeds dependent or attributes.table
Server scriptScriptColumn
Radio buttonsRadioColumnRequires choices

All Supported Column Types

ListColumn, RadioColumn, StringColumn, MultiLineTextColumn, ApprovalRulesColumn, SlushBucketColumn, ChoiceColumn, ScriptColumn, BooleanColumn, ConditionsColumn, DecimalColumn, FloatColumn, IntegerColumn, VersionColumn, DomainIdColumn, FieldNameColumn, FieldListColumn, ReferenceColumn, RecordsColumn, TableNameColumn, UserRolesColumn, BasicImageColumn, DocumentIdColumn, DomainPathColumn, TranslatedTextColumn, SystemClassNameColumn, TranslatedFieldColumn, GenericColumn, DateColumn, DateTimeColumn, CalendarDateTimeColumn, BasicDateTimeColumn, DueDateColumn, IntegerDateColumn, ScheduleDateTimeColumn, OtherDateColumn, DurationColumn, TimeColumn, DayOfWeekColumn, DaysOfWeekColumn, JsonColumn, NameValuePairsColumn, HtmlColumn, UrlColumn, EmailColumn, TemplateValueColumn, GuidColumn.

Only import the types you use.

Common Column Properties

NameTypeDescription
labelString or ArrayDisplay label for the column.
maxLengthNumberMaximum value length. Default: 40.
activeBooleanDisplay in lists and forms. Default: true.
mandatoryBooleanRequire a value to save. Default: false.
readOnlyBooleanPrevent editing. Default: false.
defaultAnyDefault value when creating a record.
choicesObjectChoices for ChoiceColumn and compatible types.
dropdownString'none', 'dropdown_without_none', 'dropdown_with_none', 'suggestion'.
attributesObjectDictionary attributes.
dynamic_value_definitionsObjectDynamic defaults: calculated_value, dynamic_default, dependent_field, choices_from_other_table.
function_definitionStringGlide function definition (e.g., 'glidefunction:concat(...)').

ReferenceColumn Specifics

ReferenceColumn({
label: 'Assigned To',
referenceTable: 'sys_user',
cascadeRule: 'none', // 'none' | 'cascade' | 'delete' | 'restrict' | 'clear'
})

Cross-Scope Column Pattern

// Adding columns to a table in a different scope
export const incident = Table({
name: 'incident' as any,
schema: {
x_scope_myColumn: StringColumn({ label: 'My Custom Field' }),
},
});

Determine the Relationship Path

  1. Reference field exists between tables: Implicit relationship (no sys_relationship needed).
  2. No reference field or custom query: Explicit relationship (create sys_relationship record).
  3. Existing platform relationship: Use known REL: ID.

Common Platform Relationship IDs

  • Attachments: REL:b9edf0ca0a0a0b010035de2d6b579a03
  • Applications with Role: REL:66c422fac0a80a880012fadcb8c2480e
  • Approval History: REL:247c6f15670303003b4687cb5685ef32

Implicit Relationship Example

// Table A has a ReferenceColumn pointing to Table B
// Only need sys_ui_related_list + sys_ui_related_list_entry

const listRecord = Record({
$id: Now.ID['department_related_list'],
table: 'sys_ui_related_list',
data: { name: 'department', view: 'Default view' },
});

Record({
$id: Now.ID['department_related_list_entry'],
table: 'sys_ui_related_list_entry',
data: {
list_id: listRecord.$id,
position: '0',
related_list: 'custom_task.department', // table.reference_field format
},
});

Explicit Relationship Example

// No reference field -- need sys_relationship + sys_ui_related_list + sys_ui_related_list_entry

export const departmentRel = 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,
},
});

const listRecord = Record({
$id: Now.ID['department_related_list_id'],
table: 'sys_ui_related_list',
data: {
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: listRecord.$id,
position: '0',
related_list: `REL:${departmentRel.$id}`,
},
});

Relationship Properties (sys_relationship)

FieldTypeRequiredDescription
namestringNoDescriptive name
basic_apply_toTableNameNoParent table (basic mode)
basic_query_fromTableNameNoChild table (basic mode)
reference_fieldFieldNameNoReference field for the relationship
query_withScriptNoScript to refine the query
advancedBooleanNoWhether advanced mode (default: false)
simple_referenceBooleanNoWhether simple reference relationship

One sys_ui_related_list per table, multiple sys_ui_related_list_entry records for different relationships:

const productContainer = Record({
$id: 'products_related_lists',
table: 'sys_ui_related_list',
data: { name: 'sn_product_life_products', view: 'Default view' },
});

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

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

query_with Script Format

(function refineQuery(current, parent) {
current.addQuery('field', parent.field);
})(current, parent);

The current variable represents the child table query. The parent variable represents the record on the form where the related list appears.