Skip to main content
Version: Latest (4.9.0)

ChoiceSet Guide

Guide for adding choices to table fields using the ChoiceSet API. Use this when your app needs to extend choices on a field it does not own — typically a globally-defined field inherited by an app-scoped table that extends a global table.

When to Use ChoiceSet

ScenarioApproach
Field defined in your app's Table() callInline choices on the column — do not use ChoiceSet
App-scoped table inherits a globally-owned fieldChoiceSet — table must be prefixed with your scope
Global app extending a global table's fieldChoiceSet — omit unwanted base choices from choices map

Scope Rules

Scoped apps (x_app_*) can only use ChoiceSet on tables within their own scope. You cannot target a global table like task directly — create a scoped table that extends it first.

// Not allowed from a scoped app — task is a global table
ChoiceSet({ table: 'task', field: 'priority', choices: { ... } })

// Correct — x_app_task is your scoped table that extends task
ChoiceSet({ table: 'x_app_task', field: 'priority', choices: { ... } })

Global apps can target any global table directly.

Instructions

Looking up existing choices on a parent table

When adding choices to a field inherited from a parent table, query the instance first to see what choices already exist on that table/field — this tells you which values to keep, add, or omit:

now-sdk query sys_choice --query 'name=<table>^element=<field>' -f 'label,value' -o json

For example, to see the base choices on task.priority before extending it:

now-sdk query sys_choice --query 'name=task^element=priority' -f 'label,value' -o json

name is the table the choice is defined on and element is the field name. For an inherited field, name must be the parent table that owns the field, not your extending table — e.g. if x_snc_scan_result extends cmdb_ci and you're adding choices to operational_status (owned by cmdb_ci), query with name=cmdb_ci^element=operational_status, not name=x_snc_scan_result^element=operational_status. sys_choice records are keyed against the table that actually defines the field, so querying your own extending table returns no results — the field doesn't live there. Use this before both the scoped-app and global-app flows below.

Scoped app — adding choices to an inherited field

  1. Create a scoped table that extends the global table (e.g., x_app_task extending task).
  2. Use ChoiceSet targeting your scoped table and the inherited field.
  3. List all desired choices in choices.
import { ChoiceSet } from '@servicenow/sdk/core'

ChoiceSet({
table: 'x_snc_scan_result', // own scoped table extending cmdb_ci
field: 'operational_status',
choices: {
10: { label: 'Pending', sequence: 10 },
11: { label: 'Running', sequence: 11 },
12: { label: 'Completed', sequence: 12 },
13: { label: 'Failed', sequence: 13 },
},
})

Global app — extending a global table's field

  1. Define choices for all values your app should include.
  2. To exclude existing base choices, simply omit them from the choices map. If they were previously defined and are now removed, the build automatically detects the removal via the keys system and writes removed_choices to the XML payload.
import { ChoiceSet } from '@servicenow/sdk/core'

ChoiceSet({
table: 'task',
field: 'priority',
choices: {
1: { label: '1 - Critical', sequence: 1 },
2: { label: '2 - High', sequence: 2 },
3: { label: '3 - Moderate', sequence: 3 },
6: { label: '6 - Deferred', sequence: 6 },
7: { label: '7 - Informational', sequence: 7 },
},
// OOB values 4 and 5 are omitted — the build auto-tracks their removal
})

Removing choices

To remove a previously defined choice, delete it from the choices map. The build automatically tracks deleted choices via the keys system and writes the removed_choices attribute to the v4 XML payload. No explicit action is needed beyond removing the entry.

Using inline choices instead (preferred when you own the field)

If the field is defined in your app's Table() call, add choices directly there:

import { Table, StringColumn } from '@servicenow/sdk/core'

export const MyTable = Table({
name: 'x_snc_my_table',
label: 'My Table',
schema: {
status: StringColumn({
choices: {
open: { label: 'Open', sequence: 0 },
closed: { label: 'Closed', sequence: 1 },
},
}),
},
})

Build-time Validation

The build validates ChoiceSet definitions and provides clear error messages:

ConditionResult
Scoped app targeting non-own table (e.g., task from x_app)Error — only x_app_* tables allowed
Field defined on a project-owned tableError — use inline choices in the Table API
Choice missing required label propertyError — every choice object must have a label

Platform Behavior

Choices are stored in sys_choice records grouped by a sys_choice_set keyed on name (table) + field (field). By default, choice set XML uses version 3 format with a <sys_choice_set> wrapper element, which is supported on all platform versions. The v3 loader uses destructive behavior: all existing choices on the field are deleted, then only the choices in the XML are inserted. The removed_choices attribute is not included.

Set legacyChoices: false in now.config.json to use version 4 format with a <sys_choice_v2> wrapper element instead. This performs an additive merge on supported instances — incoming choices are upserted and removed choices are deleted individually.

The removed_choices attribute on the v4 XML wrapper element tells the platform loader which base choices to remove during install. Choices listed in choices are inserted; choices tracked as removed by the keys system are written to removed_choices and removed from the instance.

Platform Version Support

Platform Versionv3 (sys_choice_set, default)v4 (sys_choice_v2, legacyChoices: false)
Brazil and laterDestructive — deletes all then insertsAdditive merge
Australia P4+DestructiveAdditive merge
Zurich P11+DestructiveAdditive merge
Older (pre-Zurich P11, pre-Australia P4)DestructiveSafe no-op — record skipped, no data loss

On older instances without v4 loader support, the <sys_choice_v2> element is not recognized and falls through to DefaultUpdateLoader, which safely skips it. No choices are deleted, no data is lost — but the choices are also not applied.

Using the Additive (v4) Format

If your app only targets instances with v4 loader support and you want additive/non-destructive updates, set legacyChoices: false in now.config.json or pass --legacyChoices=false on the CLI:

{
"scope": "x_app",
"scopeId": "...",
"legacyChoices": false
}

Inheritance

At runtime the platform walks up the table extension chain. If x_snc_scan_result extends cmdb_ci, choices defined on cmdb_ci.operational_status remain visible unless shadowed by a choice set on x_snc_scan_result.operational_status with the same value key.

Avoidance

  1. Do not use ChoiceSet for fields your app owns — use inline choices in the Table API instead. The build will error if you target a field defined in the same project.
  2. Scoped apps must extend the global table first — you cannot add choices directly to task, incident, etc. Create x_app_task extending task, then target that.

See

  • choiceset-api — Full property reference and examples
  • table-guide — Creating tables and adding fields with inline choices
  • table-api — Full Table API reference