ui-actions-policies
$
npx mdskill add serac-labs/serac/ui-actions-policiesBuild ServiceNow UI Actions and UI Policies with server/client scripts and GlideAjax
- Solve UI customization needs with buttons, menus, and conditional field controls
- Uses ServiceNow APIs like GlideAjax, UI Actions, and UI Policies
- Analyzes forms with snow_analyze_form before making changes
- Delivers structured scripts and policies via code generation tools
SKILL.md
.github/skills/ui-actions-policiesView on GitHub ↗
---
name: ui-actions-policies
description: Build ServiceNow UI Actions (form/list buttons, context menus) and UI Policies (conditional mandatory/visible/read-only) with server/client scripts and GlideAjax. Use snow_analyze_form before modifying forms.
license: Apache-2.0
compatibility: Designed for Snow-Code and ServiceNow development
metadata:
author: serac
version: "1.0.0"
category: servicenow
tools:
- snow_create_ui_action
- snow_create_ui_policy
- snow_find_artifact
- snow_edit_artifact
- snow_analyze_form
---
# UI Actions & UI Policies for ServiceNow
UI Actions add buttons, links, and context menus. UI Policies control form field behavior dynamically.
Before modifying an existing form, reach for `snow_analyze_form` — it returns every active UI policy, client script, data policy, record + field ACL, and UI action on the target table in one call. Use it to answer "why is this form behaving like this?" before guessing.
## UI Actions
### UI Action Types
| Type | Location | Example |
| --------------------- | ------------------ | ------------------ |
| **Form Button** | Form header | "Resolve Incident" |
| **Form Context Menu** | Right-click menu | "Copy Record" |
| **Form Link** | Related links | "View CI" |
| **List Button** | List header | "Export Selected" |
| **List Context Menu** | Right-click on row | "Assign to Me" |
| **List Choice** | Actions dropdown | "Update State" |
| **List Link** | List header links | "New Record" |
### Form Button UI Action (ES5)
```javascript
// Table: sys_ui_action
// Name: Resolve Incident
// Table: incident
// Form button: true
// Active: true
// Condition: current.active == true && current.state != 6
// Script (Server-side - ES5 ONLY):
;(function executeAction() {
// Validate before resolving
if (!current.resolution_code) {
gs.addErrorMessage("Please select a resolution code")
action.setRedirectURL(current)
return
}
if (!current.close_notes) {
gs.addErrorMessage("Please provide resolution notes")
action.setRedirectURL(current)
return
}
// Set resolved state
current.state = 6 // Resolved
current.resolved_at = new GlideDateTime()
current.resolved_by = gs.getUserID()
current.update()
gs.addInfoMessage("Incident " + current.number + " has been resolved")
action.setRedirectURL(current)
})()
```
### Client-Side UI Action (ES5)
```javascript
// Table: sys_ui_action
// Name: Quick Assign
// Client: true
// Onclick: quickAssign()
// Client script (ES5 ONLY):
function quickAssign() {
// Get current user
var userId = g_user.userID
var userName = g_user.getFullName()
// Confirm action
var confirmed = confirm("Assign this incident to yourself (" + userName + ")?")
if (!confirmed) {
return false
}
// Set the field value
g_form.setValue("assigned_to", userId)
g_form.setValue("assignment_group", g_user.getGroupID())
// Save the form
gsftSubmit(null, g_form.getFormElement(), "save")
return false
}
```
### List UI Action (ES5)
```javascript
// Table: sys_ui_action
// Name: Close Selected Incidents
// Table: incident
// List button: true
// List choice: true
// Condition: gs.hasRole('itil')
// Script (Server-side - ES5 ONLY):
;(function executeAction() {
// Get selected records
var selectedRecords = RP.getParameterValue("sysparm_checked_items")
if (!selectedRecords) {
gs.addErrorMessage("No records selected")
return
}
var sysIds = selectedRecords.split(",")
var closedCount = 0
for (var i = 0; i < sysIds.length; i++) {
var gr = new GlideRecord("incident")
if (gr.get(sysIds[i])) {
if (gr.state != 7) {
// Not already closed
gr.state = 7 // Closed
gr.closed_at = new GlideDateTime()
gr.closed_by = gs.getUserID()
gr.update()
closedCount++
}
}
}
gs.addInfoMessage("Closed " + closedCount + " incident(s)")
})()
```
### UI Action with GlideAjax (ES5)
```javascript
// Client-side UI Action calling server
// Client: true
// Onclick: checkAndEscalate()
function checkAndEscalate() {
var incidentId = g_form.getUniqueValue()
// Check if escalation is allowed
var ga = new GlideAjax("IncidentAjax")
ga.addParam("sysparm_name", "canEscalate")
ga.addParam("sysparm_incident_id", incidentId)
ga.getXMLAnswer(function (answer) {
var result = JSON.parse(answer)
if (result.canEscalate) {
// Proceed with escalation
g_form.setValue("priority", 1)
g_form.setValue("escalation", 1)
gsftSubmit(null, g_form.getFormElement(), "escalate_incident")
} else {
alert("Cannot escalate: " + result.reason)
}
})
return false
}
```
## UI Policies
### UI Policy Structure
| Field | Purpose |
| --------------------- | ------------------------- |
| **Short description** | Policy name |
| **Table** | Target table |
| **Conditions** | When to apply |
| **Reverse if false** | Undo when condition false |
| **On load** | Run when form loads |
| **UI Policy Actions** | Field behaviors |
### Basic UI Policy (No Script)
```yaml
# UI Policy: Make Resolution Required on Resolve
Table: incident
Short description: Require resolution fields when resolving
Conditions: state = 6 (Resolved)
On load: true
Reverse if false: true
# UI Policy Actions:
- Field: resolution_code
Mandatory: true
Visible: true
- Field: close_notes
Mandatory: true
Visible: true
- Field: resolved_by
Read only: true
```
### UI Policy with Script (ES5)
```javascript
// UI Policy Script - Execute if true
// Runs when condition becomes true (ES5 ONLY!)
function onCondition() {
// Show/hide fields based on category
var category = g_form.getValue("category")
if (category === "hardware") {
g_form.setDisplay("cmdb_ci", true)
g_form.setMandatory("cmdb_ci", true)
g_form.setDisplay("software", false)
} else if (category === "software") {
g_form.setDisplay("software", true)
g_form.setMandatory("software", true)
g_form.setDisplay("cmdb_ci", false)
}
}
```
### Complex UI Policy Script (ES5)
```javascript
// UI Policy: VIP Caller Handling
// Condition: None (script handles logic)
// On load: true
// Run scripts: true
// Script - Execute if true (ES5 ONLY!):
function onCondition() {
var callerId = g_form.getValue("caller_id")
if (!callerId) {
return
}
// Check if VIP via GlideAjax
var ga = new GlideAjax("UserAjax")
ga.addParam("sysparm_name", "isVIP")
ga.addParam("sysparm_user_id", callerId)
ga.getXMLAnswer(function (answer) {
var isVIP = answer === "true"
if (isVIP) {
// Highlight form
g_form.flash("caller_id", "#FFD700", 0)
g_form.showFieldMsg("caller_id", "VIP Customer", "info")
// Set default priority
if (!g_form.getValue("priority")) {
g_form.setValue("priority", 2)
}
// Make assignment group mandatory
g_form.setMandatory("assignment_group", true)
}
})
}
```
### Dynamic Field Visibility (ES5)
```javascript
// UI Policy: Show fields based on incident type
// Table: incident
// On load: true
function onCondition() {
var incidentType = g_form.getValue("u_incident_type")
// Reset all conditional fields
var conditionalFields = ["u_network_details", "u_hardware_model", "u_software_name"]
for (var i = 0; i < conditionalFields.length; i++) {
g_form.setDisplay(conditionalFields[i], false)
g_form.setMandatory(conditionalFields[i], false)
}
// Show relevant fields
switch (incidentType) {
case "network":
g_form.setDisplay("u_network_details", true)
g_form.setMandatory("u_network_details", true)
break
case "hardware":
g_form.setDisplay("u_hardware_model", true)
g_form.setMandatory("u_hardware_model", true)
break
case "software":
g_form.setDisplay("u_software_name", true)
g_form.setMandatory("u_software_name", true)
break
}
}
```
## Creating via Scripts (ES5)
### Create UI Action Programmatically
```javascript
// Create UI Action via background script (ES5 ONLY!)
var uiAction = new GlideRecord("sys_ui_action")
uiAction.initialize()
uiAction.setValue("name", "Escalate to Manager")
uiAction.setValue("table", "incident")
uiAction.setValue("active", true)
uiAction.setValue("form_button", true)
uiAction.setValue("form_style", "btn-warning")
uiAction.setValue("hint", "Escalate this incident to the caller's manager")
uiAction.setValue("condition", "current.active == true && current.priority > 2")
uiAction.setValue(
"script",
"(function executeAction() {\n" +
" current.priority = 2;\n" +
" current.escalation = 1;\n" +
' current.work_notes = "Escalated by " + gs.getUserDisplayName();\n' +
" current.update();\n" +
' gs.addInfoMessage("Incident escalated");\n' +
" action.setRedirectURL(current);\n" +
"})();",
)
uiAction.insert()
```
### Create UI Policy Programmatically
```javascript
// Create UI Policy (ES5 ONLY!)
var policy = new GlideRecord("sys_ui_policy")
policy.initialize()
policy.setValue("short_description", "Require Close Notes on Close")
policy.setValue("table", "incident")
policy.setValue("active", true)
policy.setValue("on_load", true)
policy.setValue("reverse_if_false", true)
policy.setValue("conditions", "state=7")
var policySysId = policy.insert()
// Add UI Policy Action
var action = new GlideRecord("sys_ui_policy_action")
action.initialize()
action.setValue("ui_policy", policySysId)
action.setValue("field", "close_notes")
action.setValue("mandatory", true)
action.setValue("visible", true)
action.setValue("disabled", false)
action.insert()
```
## MCP Tool Integration
### Available Tools
| Tool | Purpose |
| ----------------------- | ------------------------- |
| `snow_create_ui_action` | Create UI Action |
| `snow_create_ui_policy` | Create UI Policy |
| `snow_find_artifact` | Find existing UI elements |
| `snow_edit_artifact` | Modify UI elements |
### Example Workflow
```javascript
// 1. Create UI Action
await snow_create_ui_action({
name: "Approve Change",
table: "change_request",
form_button: true,
condition: 'current.state == "assess"',
script: "/* approval script */",
})
// 2. Create UI Policy
await snow_create_ui_policy({
short_description: "Require justification for high priority",
table: "change_request",
conditions: "priority=1",
actions: [{ field: "justification", mandatory: true }],
})
```
## Best Practices
1. **Descriptive Names** - Clear purpose in name
2. **Conditions First** - Use conditions before scripts
3. **Minimal Scripts** - Keep scripts short
4. **Reverse If False** - Clean up field states
5. **Test Thoroughly** - Multiple scenarios
6. **Role Security** - Add role conditions
7. **ES5 Only** - No modern JavaScript syntax
8. **Form vs List** - Choose appropriate action type