Form Formatters
Guide for adding formatters to ServiceNow forms using the Form API. Formatters display special UI components (activity streams, process flows, knowledge panels, checklist, parent breadcrumb) that are not regular table fields.
When to Use
Use formatters to enhance existing forms with these capabilities:
- Adding an activity stream (comments, work notes, journal) to an existing form
- Displaying attached knowledge articles on existing task records
- Showing a process flow (lifecycle stages) at the top of an existing form
- Adding a checklist for tracking sub-tasks on an existing form
- Displaying CI relationship maps on existing CMDB record forms
- Showing parent breadcrumb navigation on existing hierarchical task records
- Surfacing contextual search results (knowledge articles, catalog items) on an existing form based on field values
- Displaying variable/question values from record producers on existing service catalog forms
Critical Instructions
When adding formatter elements to forms, follow these guidelines to ensure proper functionality. Failure to follow these requirements may result in build or runtime errors.
- Always set
type: 'formatter'— this is required for the element to be treated as a formatter formatterRefis required — use a predefined key string (e.g.,'Activities_Filtered'), aFormatter.*constant, or aRecord<'sys_ui_formatter'>referenceformatterNameis optional — automatically derived for predefinedFormatter.*keys andRecord<'sys_ui_formatter'>references- Import
Formatterfrom@servicenow/sdk/corewhen using formatter constants - Never create custom formatter macros — not supported in Fluent. Always use built-in formatters
- Never create new
sys_ui_formatterrecords for globally available formatters (Activity, Attached Knowledge) — they already exist - Formatter properties are formatter-only — never use
formatterReforformatterNameon other element types - Always include
angular_modulewhen required — Checklist and CI Relations formatters require this property
Instructions
- Identify the formatter: Determine which formatter type matches your use case from the Built-in Formatters table below.
- Check availability: Activity and Attached Knowledge exist globally — skip to step 4. For all others, query
sys_ui_formatterusing the 3-level cascade (target table → global → parent table). - Create if missing: If the
sys_ui_formatterrecord does not exist, create one usingRecord()before theForm()call. - Add the element: Add
{ type: 'formatter', formatterRef: ... }to the appropriate section in yourForm()call. - Position correctly: Follow the placement rules — visual formatters go at the top, historical formatters go in dedicated sections at the bottom.
- Handle prerequisites: Some formatters require additional setup (stage records, CXS config,
parentfield). See per-formatter details below.
API Reference
See the form-api topic for the full FormElement type reference, including formatterRef and formatterName properties.
Table Schema
The sys_ui_formatter table schema for creating formatter records using Record():
| Field | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Display name of the formatter (shown in form designer and admin lists). |
type | String | Yes | Must be 'formatter' — identifies this as a formatter record. |
formatter | String | Yes | Macro file name (e.g., 'process_flow.xml', 'activity.xml', 'inline_checklist_macro.xml'). Must match an existing platform formatter macro — never create custom macros. |
table | String | Yes | Target table name. Use 'global' for all tables, specific table name (e.g., 'incident'), or parent table (e.g., 'task', 'cmdb_ci') for inheritance. |
active | Boolean | Yes | Whether the formatter is active and available for use. Set to true to enable. |
angular_module | String | Conditional | AngularJS module name. Required for Checklist ('inlineChecklist') and CI Relations ('sn.ciRelationFormatter'). Optional for Contextual Search ('sn.cxs.table_search'). Omit for all other formatters. |
Key Concepts
Form API Structure
Understanding the Form API hierarchy is essential for correct formatter placement:
Form Structure:
- A form has multiple sections (array of section objects)
- Each section has a content array containing layout blocks
- Layout blocks can be
one-column(withelementsarray) ortwo-column(withleftElementsandrightElementsarrays) - Element position is determined by array order within the layout block
Example structure:
Form({
table: 'x_myapp_task',
view: default_view,
sections: [ // Array of sections
{
caption: 'Overview',
content: [ // Array of layout blocks
{
layout: 'one-column',
elements: [ // Array of elements - order matters!
// First element (index 0)
{ type: 'formatter', formatterRef: processFlowFormatter },
// Second element (index 1)
{ field: 'name', type: 'table_field' },
],
},
],
},
],
})
When we say "first element in section", this means index 0 in the elements array of the first layout block.
Formatter Properties
| Property | Required | Type | Description |
|---|---|---|---|
type | Yes | 'formatter' | Must be 'formatter' |
formatterRef | Yes | Formatter.* constant, Record<'sys_ui_formatter'>, or key string | Identifies which formatter to display |
formatterName | No | string | Macro name (e.g., 'activity.xml'). Auto-derived for Formatter.* constants and Record references |
Formatter Constants
Only two formatters have predefined constants (import from @servicenow/sdk/core):
import { Formatter } from '@servicenow/sdk/core'
| Constant | Macro | Description |
|---|---|---|
Formatter.Activities_Filtered | activity.xml | Activity stream with filtering (comments, work notes, journal) |
Formatter.Attached_Knowledge | attached_knowledge | Related knowledge articles panel |
All other formatters require a Record<'sys_ui_formatter'> reference as formatterRef.
Built-in Formatters
| Formatter | Macro | Angular Module | Position | Prerequisites |
|---|---|---|---|---|
| Activity | activity.xml | N/A | Dedicated section at bottom | None — globally available |
| Attached Knowledge | attached_knowledge | N/A | Dedicated section at bottom | Knowledge Management plugin; table extends task |
| Process Flow | process_flow.xml | N/A | First element in first section | sys_process_flow stage records for the table |
| Checklist | inline_checklist_macro.xml | inlineChecklist | Dedicated section near bottom | Checklist plugin; task-derived table |
| CI Relations | ui_ng_relation_formatter.xml | sn.ciRelationFormatter | First element in section | CMDB plugin; table extends cmdb_ci; relationship records in cmdb_rel_ci |
| Parent Breadcrumb | parent_crumbs.xml | N/A | First element in section | Table must have a field named exactly parent (reference type) |
| Contextual Search | cxs_table_search.xml | sn.cxs.table_search | After the search context field | CXS enabled; cxs_table_config and cxs_table_field_config records |
| Variables Editor | com_glideapp_questionset_default_question_editor | N/A | No fixed position | Table extends task; record created from record producer |
Angular Module Requirement:
- Checklist: MUST include
angular_module: 'inlineChecklist'in thesys_ui_formatterRecord() data - CI Relations: MUST include
angular_module: 'sn.ciRelationFormatter'in thesys_ui_formatterRecord() data - Contextual Search: The table lists
sn.cxs.table_searchbut this may be optional — verify on your instance - All others: No angular_module required
Placement Rules
| Category | Formatters | Where to Place | Additional Guidance |
|---|---|---|---|
| Visual/Overview | Process Flow, Parent Breadcrumb, CI Relations | First element in the main section | These are visual headers that belong with record details — place in a section that also contains fields |
| Contextual | Contextual Search, Variables Editor | Same section as related fields, after them | Place immediately after the field they relate to (e.g., CXS after short_description) |
| Historical/Reference | Activity, Checklist, Attached Knowledge | Dedicated section at bottom of form | Formatter-only sections (no other fields) — these become separate tabs in the UI |
General rules:
- Use one-column layout for formatters — formatters typically span the full width; avoid placing them in
leftElementsorrightElementsof two-column layouts - One formatter per dedicated section — Activity, Attached Knowledge, and Checklist each get their own section
- Visual formatters can share sections — Process Flow, Parent Breadcrumb can be in the same section as form fields (as the first element)
sys_ui_formatter Records
Before adding any formatter (except Activity and Attached Knowledge), verify the sys_ui_formatter record exists using a 3-level cascade query:
- Target table:
table=<table_name>^formatter=<macro_name> - Global scope:
table=global^formatter=<macro_name> - Parent table:
table=<parent_table>^formatter=<macro_name>(if table extends another)
If found, use the existing record. If not found, create one:
import { Record } from '@servicenow/sdk/core'
const myFormatter = Record({
$id: Now.ID['my-formatter'],
table: 'sys_ui_formatter',
data: {
name: 'Formatter Display Name',
type: 'formatter',
formatter: '<macro_name>',
table: '<target_table>',
active: true,
// Include angular_module if required (Checklist, CI Relations)
// angular_module: 'module.name',
},
})
Some formatters require an angular_module field:
- Checklist:
'inlineChecklist' - CI Relations:
'sn.ciRelationFormatter' - Contextual Search:
'sn.cxs.table_search'(optional — verify on your instance)
Examples
Activity Stream (Global Formatter)
No sys_ui_formatter record needed — use the Formatter constant directly.
import { Form, Formatter, default_view } from '@servicenow/sdk/core'
Form({
table: 'x_myapp_task',
view: default_view,
sections: [
{
caption: 'Details',
content: [
{
layout: 'one-column',
elements: [
{ field: 'name', type: 'table_field' },
{ field: 'description', type: 'table_field' },
],
},
],
},
{
caption: 'Activity',
content: [
{
layout: 'one-column',
elements: [
{ type: 'formatter', formatterRef: Formatter.Activities_Filtered },
],
},
],
},
],
})
Prerequisites: Available by default for every table. No additional setup required.
Process Flow with Stage Records
Requires a sys_ui_formatter record and sys_process_flow stage records as prerequisites.
import { Form, Record, default_view } from '@servicenow/sdk/core'
const processFlowFormatter = Record({
$id: Now.ID['process-flow-formatter'],
table: 'sys_ui_formatter',
data: {
name: 'Process Flow',
type: 'formatter',
formatter: 'process_flow.xml',
table: 'x_myapp_task',
active: true,
},
})
Record({
$id: Now.ID['stage-new'],
table: 'sys_process_flow',
data: {
active: true,
condition: 'state=new^EQ',
label: 'New',
name: 'Task Flow - New',
order: '100',
table: 'x_myapp_task',
},
})
Record({
$id: Now.ID['stage-active'],
table: 'sys_process_flow',
data: {
active: true,
condition: 'state=active^EQ',
label: 'Active',
name: 'Task Flow - Active',
order: '200',
table: 'x_myapp_task',
},
})
Record({
$id: Now.ID['stage-closed'],
table: 'sys_process_flow',
data: {
active: true,
condition: 'state=closed^EQ',
label: 'Closed',
name: 'Task Flow - Closed',
order: '300',
table: 'x_myapp_task',
},
})
Form({
table: 'x_myapp_task',
view: default_view,
sections: [
{
caption: 'Overview',
content: [
{
layout: 'one-column',
elements: [
{ type: 'formatter', formatterRef: processFlowFormatter },
],
},
{
layout: 'two-column',
leftElements: [
{ field: 'name', type: 'table_field' },
{ field: 'state', type: 'table_field' },
],
rightElements: [
{ field: 'priority', type: 'table_field' },
{ field: 'assigned_to', type: 'table_field' },
],
},
],
},
],
})
Prerequisites: Stage Model must be configured in sys_process_flow for the target table.
Checklist Formatter
Requires the Checklist plugin and a task-derived table.
import { Form, Record, default_view } from '@servicenow/sdk/core'
const checklistFormatter = Record({
$id: Now.ID['checklist-formatter'],
table: 'sys_ui_formatter',
data: {
name: 'Checklist Formatter',
type: 'formatter',
formatter: 'inline_checklist_macro.xml',
angular_module: 'inlineChecklist', // REQUIRED for Checklist
table: 'x_myapp_task',
active: true,
},
})
Form({
table: 'x_myapp_task',
view: default_view,
sections: [
{
caption: 'Task Details',
content: [
{
layout: 'one-column',
elements: [
{ field: 'name', type: 'table_field' },
{ field: 'state', type: 'table_field' },
],
},
],
},
{
caption: 'Checklist',
content: [
{
layout: 'one-column',
elements: [
{ type: 'formatter', formatterRef: checklistFormatter },
],
},
],
},
],
})
Prerequisites:
- Checklist plugin enabled
- Target table supports checklists (typically task-derived)
- Checklist definitions/items exist or can be added
- User has rights to view/edit checklist items
Attached Knowledge Formatter
Display knowledge articles linked to a task record. Uses the global Formatter.Attached_Knowledge constant — no sys_ui_formatter record needed.
import { Form, Formatter, default_view } from '@servicenow/sdk/core'
Form({
table: 'incident',
view: default_view,
sections: [
{
caption: 'Incident Details',
content: [
{
layout: 'one-column',
elements: [
{ field: 'short_description', type: 'table_field' },
{ field: 'description', type: 'table_field' },
],
},
],
},
{
caption: 'Knowledge',
content: [
{
layout: 'one-column',
elements: [
{
type: 'formatter',
formatterRef: Formatter.Attached_Knowledge,
},
],
},
],
},
],
})
Prerequisites:
- Knowledge Management plugin installed
- At least one knowledge article is attached to the record
- Target table is
taskor extends fromtask
CI Relations Formatter
Display CI relationship maps on CMDB records. Requires creating a sys_ui_formatter record first.
import { Record, Form, default_view } from '@servicenow/sdk/core'
const ciRelationsFormatter = Record({
$id: Now.ID['ci-relations-formatter'],
table: 'sys_ui_formatter',
data: {
formatter: 'ui_ng_relation_formatter.xml',
angular_module: 'sn.ciRelationFormatter', // REQUIRED for CI Relations
name: 'CI Relations',
table: 'cmdb_ci',
type: 'formatter',
active: true,
},
})
Form({
table: 'cmdb_ci_server',
view: default_view,
sections: [
{
caption: 'Server Details',
content: [
{
layout: 'one-column',
elements: [
{
type: 'formatter',
formatterRef: ciRelationsFormatter,
},
{ field: 'name', type: 'table_field' },
{ field: 'ip_address', type: 'table_field' },
],
},
],
},
],
})
Prerequisites:
- CMDB plugin installed
- Target table extends
cmdb_ci - Relationship records exist in
cmdb_rel_ciwith appropriate relationship types
Contextual Search Formatter
Adds a search results panel to an EXISTING form that displays contextual knowledge articles or catalog items based on field values as the user types. This formatter enhances platform forms (incident, task, custom tables) — it does NOT create a new search page or interface. Requires CXS configuration records.
How CXS Works (Architecture)
Form record (e.g. incident)
└─ field value (e.g. short_description = "VPN not connecting")
└─ used as search term
└─ searches Knowledge Base (kb_knowledge), Catalog, or Communities
└─ matching articles displayed in the formatter panel on the form
Key architectural concepts:
cxs_table_config.table— the form table (e.g.,incident). This is the table whose field values drive the search.cxs_table_field_config.field— the field on the form table whose value is sent as the search term.cxs_context_config— defines the search sources (KB, Catalog, Communities). These are platform system records — never create them. Query to find available ones.- CXS searches
kb_knowledge(Knowledge Base), catalog items, and communities. It does NOT search custom tables.
Critical CXS Limitations
- CXS cannot search custom tables — it only searches Knowledge Base (
kb_knowledge), Catalog items, and Communities. Never create a custom knowledge table and attempt to wire it to CXS. - Knowledge articles must exist as
kb_knowledgerecords inside an active Knowledge Base. If the user asks for a "contextual panel showing knowledge articles", those articles must be in the platform KB, not a custom table. - Query existing search contexts first —
cxs_context_configrecords are platform system records. Query the instance for active records in tablecxs_context_configwith fieldssys_id,namebefore configuring (seequerytopic).
CXS Setup Process
Step 1: Get available search contexts
Query the instance for table cxs_context_config for all records with fields sys_id, name, description (see query topic for how to perform queries in your environment).
Common context: Knowledge Base Search. Ask the user to select a search context if multiple are available.
Step 2: Create cxs_table_config record
The table field here is the form table (e.g., incident, sn_si_incident) — not the KB table. The match_condition is an encoded query on this same form table to control when the formatter is active.
CRITICAL: match_condition field validation
The match_condition field must only use fields that exist on the form table. Before using a field in match_condition:
- Query the instance for table
sys_dictionarywherename=<form_table>andelement=<field_name>, retrieving fieldelementto verify the field exists (seequerytopic) - Use
sys_created_on!=NULL^EQas a safe fallback if unsure (always true for saved records)
Step 3: Verify string fields on form table
For cxs_table_field_config, you need to identify string fields available on the form table.
First, query the instance for table sys_glide_object where name=string to get the String type sys_id (see query topic).
Then query the instance table sys_dictionary where name=<form_table> and internal_type=<string_type_id>, retrieving field element to find available string fields (see query topic).
Prefer short_description or description. If neither exists, ask the user to select an appropriate string field.
Full Example
import { Record, Form, default_view } from '@servicenow/sdk/core'
// Create CXS table config
const cxsTableConfig = Record({
$id: Now.ID['incident-cxs-config'],
table: 'cxs_table_config',
data: {
table: 'incident', // The form table - drives the search
name: 'Incident[incident]', // Format: "Label[table_name]"
active: true,
cxs_context_config: '<sys_id_from_query>', // From step 1
ui_type: 'platform',
results_header_text: 'Related Search Results',
limit: 10,
results_per_page: 10,
match_condition: 'short_descriptionISNOTEMPTY^category=software', // Encoded query on form table
related_search_active: true,
enable_source_selector: true,
},
})
// Create CXS field config
const cxsFieldConfig = Record({
$id: Now.ID['incident-cxs-field-config'],
table: 'cxs_table_field_config',
data: {
active: true,
cxs_table_config: cxsTableConfig,
field: 'short_description', // Field on form table used as search term
name: 'Short Description[short_description]', // Format: "Field Label[field_name]"
sys_name: 'Short Description[short_description]',
default_config: true,
weight: 100,
},
})
// Create formatter
const cxsFormatter = Record({
$id: Now.ID['incident-cxs-formatter'],
table: 'sys_ui_formatter',
data: {
formatter: 'cxs_table_search.xml',
name: 'Contextual Search',
table: 'incident',
type: 'formatter',
active: true,
// angular_module: 'sn.cxs.table_search', // May be optional - verify on your instance
},
})
Form({
table: 'incident',
view: default_view,
sections: [
{
caption: 'Details',
content: [
{
layout: 'one-column',
elements: [
{ field: 'short_description', type: 'table_field' },
{
type: 'formatter',
formatterRef: cxsFormatter,
},
{ field: 'category', type: 'table_field' },
],
},
],
},
],
})
Prerequisites:
- Contextual Search enabled and configured on the instance
- Knowledge articles exist as
kb_knowledgerecords in an active Knowledge Base cxs_table_configmust be configured for the form tablematch_conditionuses fields that exist on the table
Parent Breadcrumb Formatter
Show parent breadcrumb navigation on hierarchical task records. Requires a field named exactly parent.
Critical Field Requirement
The table MUST have a reference field named exactly parent.
| Field Name | Acceptable |
|---|---|
parent | ✅ Yes |
parent_task | ❌ No |
parent_ticket | ❌ No |
parent_record | ❌ No |
The field must:
- Be named exactly
parent(case-sensitive) - Be a reference field type
- Reference the same table the record is on, or a table it extends from (e.g., the base
tasktable)
If this field does not exist, create it before adding the formatter.
import { Record, Form, default_view } from '@servicenow/sdk/core'
const parentBreadcrumbFormatter = Record({
$id: Now.ID['parent-breadcrumb-formatter'],
table: 'sys_ui_formatter',
data: {
formatter: 'parent_crumbs.xml',
name: 'Parent Breadcrumb',
table: 'incident',
type: 'formatter',
active: true,
},
})
Form({
table: 'incident',
view: default_view,
sections: [
{
caption: 'Incident',
content: [
{
layout: 'one-column',
elements: [
{
type: 'formatter',
formatterRef: parentBreadcrumbFormatter,
},
{ field: 'number', type: 'table_field' },
{ field: 'parent', type: 'table_field' },
],
},
],
},
],
})
Prerequisites: Table must have a field named exactly parent (reference type, not parent_task or parent_record).
Variables Editor Formatter
Display variable/question values from record producers on task-derived tables.
import { Record, Form, default_view } from '@servicenow/sdk/core'
const variablesFormatter = Record({
$id: Now.ID['variables-formatter'],
table: 'sys_ui_formatter',
data: {
formatter: 'com_glideapp_questionset_default_question_editor',
name: 'Variables',
table: 'sc_req_item',
type: 'formatter',
active: true,
},
})
Form({
table: 'sc_req_item',
view: default_view,
sections: [
{
caption: 'Request Details',
content: [
{
layout: 'one-column',
elements: [
{ field: 'number', type: 'table_field' },
{ field: 'short_description', type: 'table_field' },
],
},
],
},
{
caption: 'Variables',
content: [
{
layout: 'one-column',
elements: [
{
type: 'formatter',
formatterRef: variablesFormatter,
},
],
},
],
},
],
})
Prerequisites:
- Target table extends
task - Record created from a record producer
- Questions/variables are defined for the table
Position: No fixed position requirement — place where variable questions logically fit in the form flow.
Best Practices
- Use
Formatterconstants: Always preferFormatter.Activities_Filteredover raw GUID strings for readability and maintainability - Place activity streams in dedicated sections: Activity formatters are best at the bottom of the form in their own section — this matches ServiceNow's standard layout convention and creates a separate tab in the UI
- Don't duplicate formatters: Each formatter should appear only once per form — adding the same formatter twice causes rendering issues
- Check formatter availability: Before using a formatter, verify the
sys_ui_formatterrecord exists on the target instance using the 3-level cascade query (target table → global → parent table) - Use one-column layouts: Formatters span the full width — always use
layout: 'one-column'for formatter elements - Validate prerequisites before adding: Process Flow needs stage records, CXS needs config records, Parent Breadcrumb needs a
parentfield — verify these exist before adding the formatter element
Avoidance
- Never create
sys_ui_formatterrecords for Activity or Attached Knowledge — they exist globally on all instances. - Never create custom formatter macros — not supported in Fluent. Only use built-in formatters.
- Never place formatters in two-column layout blocks — formatters need full width. Always use
one-column. - Never duplicate formatters on the same form — adding the same formatter twice causes rendering issues.
- Never use the Parent Breadcrumb formatter without a field named exactly
parent— must be a reference type (notparent_task,parent_record). - Never create
cxs_table_configwithout verifying thematch_conditionuses fields that exist on the form table — query the instance for tablesys_dictionarywherename=<form_table>andelement=<field_name>, retrieving fieldelementto verify the field exists (seequerytopic). - Never hardcode formatter GUIDs — use
Formatter.*constants for Activity and Attached Knowledge, or aRecord<'sys_ui_formatter'>reference for all others. - Never skip the sys_ui_formatter query — always check if the record exists before creating a new one using the 3-level cascade (target table → global → parent table).
- Never create a custom knowledge table for CXS — CXS only searches
kb_knowledge, catalog items, and communities. Custom tables are not supported as search sources. - Never omit
angular_modulefor Checklist or CI Relations — these formatters require the angular_module property in the sys_ui_formatter record or they will fail at runtime.
Next Steps
- For API properties and full type reference, see the
form-apitopic - For table creation and field management, see the
tabletopic - For service catalog and record producer configuration, see the
service-catalogtopic