ui-actions
Skill41 starsChanged 6 days agoApache-2.0View on GitHub
Complete guide to UI Action development including form buttons, list buttons, context menu actions, client-side and server-side scripts, conditions, security, and common patterns
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 13 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈13
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 39
When to use: When you need to add custom buttons or menu items to forms and lists that execute client-side or server-side logic.
- Distinctive: line 976
Log sensitive actions: Create audit trail
- Very specific: line 1326
Write script and onclick to separate local files.
- Distinctive: line 1371
Descriptive Names: Use clear, action-oriented names (e.g., "Escalate to Management")
- Distinctive: line 1372
Consistent Order: Use standardized order numbers (100, 200, 300)
- Distinctive: line 1375
Add Comments: Document the purpose and any dependencies
- Distinctive: line 1376
Role-Based Access: Always restrict sensitive actions
- Distinctive: line 1377
Audit Trail: Add work notes for all significant actions
- Distinctive: line 1379
Test Thoroughly: Test all conditions, roles, and edge cases
- Distinctive: line 1402
Check active=true and verify condition logic.
- Distinctive: line 1413
Check browser console (F12) for errors
- Distinctive: line 1415
Test onclick script in browser console
- Very specific: line 1444
Verify action.setRedirectURL(current) at end of server script
- Distinctive: line 1446
Check client onclick returns true or submits form
- Distinctive: line 1457
Test condition in background script:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
UI Actions
Overview
This skill covers creating and configuring UI Actions in ServiceNow:
- Form buttons (header/footer) and form context menu items
- List buttons, list context menu, and list choice actions
- Client-side scripts, server-side scripts, and combined execution
- Visibility conditions and role-based security
- Common patterns: Approve, Reject, Clone, Escalate, etc.
- Testing and debugging UI Actions
When to use: When you need to add custom buttons or menu items to forms and lists that execute client-side or server-side logic.
Who should use this: Developers and administrators who need to extend ServiceNow forms and lists with custom functionality.
Prerequisites
- Roles:
admin,personalize(for basic UI actions), or specific application roles - Access: sys_ui_action table, related tables for testing
- Knowledge: JavaScript, GlideRecord API, client-side g_form API
- Related Skills:
admin/script-executionfor server-side patterns
Understanding UI Actions
UI Action Types
┌─────────────────────────────────────────────────────────────────────────────┐
│ UI Action Locations │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────── FORM ────────────┐ ┌─────────── LIST ────────────┐ │
│ │ │ │ │ │
│ │ [Form Button] [Button] │ │ [List Button] [Button] │ │
│ │ ───────────────────────── │ │ ───────────────────────── │ │
│ │ ┌─────────────────────┐ │ │ ☐ Number Description │ │
│ │ │ │ │ │ ☐ INC001 Server down │ │
│ │ │ Form Content │ │ │ ☐ INC002 Email issue │ │
│ │ │ │ │ │ │ │
│ │ │ Right-click → │ │ │ Right-click → │ │
│ │ │ [Context Menu] │ │ │ [Context Menu] │ │
│ │ │ │ │ │ │ │
│ │ └─────────────────────┘ │ │ Actions Dropdown: │ │
│ │ │ │ ☐ List Choice Option │ │
│ │ [Footer Button] [Button] │ │ │ │
│ └─────────────────────────────┘ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘UI Action Execution Flow
User Clicks UI Action
│
▼
┌───────────────────┐
│ Check Conditions │
│ (if defined) │
└────────┬──────────┘
│
Condition
True?
┌───┴───┐
Yes No
│ │
▼ ▼
┌───────┐ [Action
│Client │ Hidden]
│Script?│
└───┬───┘
│
Yes ──► Run Client Script (onclick)
│ │
│ gsftSubmit()?
│ ┌───┴───┐
│ Yes No
│ │ │
│ ▼ ▼
│ ┌───────┐ [Client
│ │Submit │ Only]
│ │ Form │
│ └───┬───┘
│ │
▼ ▼
┌───────────────────┐
│ Run Server Script │◄── No Client Script (direct server)
└───────────────────┘
│
▼
┌─────────┐
│Redirect?│
└────┬────┘
┌───┴───┐
Yes No
│ │
▼ ▼
[Custom [Form
URL] Reload]Procedure
Phase 1: Understanding UI Action Fields
Step 1.1: Get Table Schema
Query the sys_ui_action schema:
Tool: SN-Get-Table-Schema
Parameters:
table_name: sys_ui_actionStep 1.2: Key Fields Reference
| Field | Type | Description |
| name | String | Button/menu item label |
| table | String | Target table (e.g., incident) |
| action_name | String | Internal name (no spaces, used in URLs) |
| order | Integer | Display order (lower = first) |
| active | Boolean | Enable/disable the action |
| client | Boolean | Run client-side script (onclick) |
| script | Script | Server-side GlideRecord script |
| onclick | Script | Client-side JavaScript (g_form) |
| condition | Script | Server-side visibility condition |
| comments | String | Description/documentation |
Step 1.3: Location Fields
| Field | Type | Description |
| form_button | Boolean | Show as form header button |
| form_button_v2 | Boolean | Show as form footer button (v2 forms) |
| form_link | Boolean | Show in form context menu |
| list_banner_button | Boolean | Show as list header button |
| list_button | Boolean | Show in list actions (legacy) |
| list_choice | Boolean | Show in list Actions dropdown |
| list_context_menu | Boolean | Show in list right-click menu |
| list_link | Boolean | Show in list Related Links |
Phase 2: Create Form Button UI Actions
Step 2.1: Basic Form Button
Create a simple form button that runs server-side:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Escalate to Management"
table: incident
action_name: escalate_to_management
order: 100
active: true
form_button: true
client: false
script: |
// Server-side script
current.escalation = 1;
current.priority = 1;
current.work_notes = "Escalated to management by " + gs.getUserDisplayName();
current.update();
action.setRedirectURL(current);
comments: "Escalates incident to management with P1 priority"Result: Returns sys_id of created UI action.
Step 2.2: Form Button with Client Script
Create a button with client-side confirmation:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Close Incident"
table: incident
action_name: close_incident
order: 200
active: true
form_button: true
client: true
onclick: |
// Client-side script
var closeCode = g_form.getValue('close_code');
var closeNotes = g_form.getValue('close_notes');
// Validate required fields
if (!closeCode) {
g_form.showFieldMsg('close_code', 'Close code is required', 'error');
return false; // Prevent submission
}
if (!closeNotes) {
g_form.showFieldMsg('close_notes', 'Close notes are required', 'error');
return false;
}
// Confirm with user
if (!confirm('Are you sure you want to close this incident?')) {
return false;
}
// Submit the form
gsftSubmit(null, g_form.getFormElement(), 'close_incident');
script: |
// Server-side script (runs after form submit)
current.state = 7; // Closed
current.closed_at = new GlideDateTime();
current.closed_by = gs.getUserID();
current.update();
action.setRedirectURL(current);
comments: "Closes incident with validation and confirmation"Step 2.3: Form Header vs Footer Button
Form Header Button (form_button):
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Quick Assign"
table: incident
action_name: quick_assign
order: 50
active: true
form_button: true
form_button_v2: false
# ... rest of configurationForm Footer Button (form_button_v2):
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Save and Return"
table: incident
action_name: save_and_return
order: 300
active: true
form_button: false
form_button_v2: true
# ... rest of configurationPhase 3: Create List UI Actions
Step 3.1: List Banner Button
Button that appears at the top of lists:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Export Selected"
table: incident
action_name: export_selected
order: 100
active: true
list_banner_button: true
client: true
onclick: |
// Get selected records
var list = GlideList2.get(g_form.getTableName());
var checked = list.getChecked();
if (!checked) {
alert('Please select at least one record');
return false;
}
// Redirect to export with selected sys_ids
var url = 'export_selected.do?sysparm_sys_ids=' + checked;
window.location = url;
comments: "Exports selected incidents to CSV"Step 3.2: List Context Menu
Right-click menu item for list rows:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Assign to Me"
table: incident
action_name: assign_to_me_list
order: 100
active: true
list_context_menu: true
client: false
script: |
// Server-side script
// current is the right-clicked record
current.assigned_to = gs.getUserID();
current.assignment_group = gs.getUser().getMyGroups()[0];
current.work_notes = "Self-assigned via list context menu";
current.update();
comments: "Assigns the right-clicked incident to current user"Step 3.3: List Choice (Actions Dropdown)
Bulk action in the Actions dropdown:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Bulk Close"
table: incident
action_name: bulk_close
order: 200
active: true
list_choice: true
client: true
onclick: |
// Get checked records
var list = GlideList2.get(g_form.getTableName());
var checked = list.getChecked();
if (!checked) {
alert('Please select at least one incident to close');
return false;
}
var count = checked.split(',').length;
if (!confirm('Close ' + count + ' selected incident(s)?')) {
return false;
}
// Submit to server
gsftSubmit(null, g_form.getFormElement(), 'bulk_close');
script: |
// Server-side - process all checked records
var sysIds = RP.getParameterValue('sysparm_checked_items');
if (sysIds) {
var ids = sysIds.split(',');
for (var i = 0; i < ids.length; i++) {
var gr = new GlideRecord('incident');
if (gr.get(ids[i])) {
gr.state = 7; // Closed
gr.close_code = 'Solved (Permanently)';
gr.close_notes = 'Bulk closed by ' + gs.getUserDisplayName();
gr.update();
}
}
}
comments: "Closes all selected incidents in bulk"Phase 4: Context Menu (Form Right-Click)
Step 4.1: Form Context Menu Item
Add item to form right-click menu:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "View Audit History"
table: incident
action_name: view_audit_history
order: 500
active: true
form_link: true
client: true
onclick: |
// Open audit history in new window
var sysId = g_form.getUniqueValue();
var tableName = g_form.getTableName();
var url = 'sys_audit_list.do?sysparm_query=tablename=' + tableName +
'^documentkey=' + sysId + '&sysparm_view=';
window.open(url, '_blank');
comments: "Opens audit history for current record in new tab"Step 4.2: Form Link with Server Script
Context menu that runs server-side:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Create Child Incident"
table: incident
action_name: create_child_incident
order: 600
active: true
form_link: true
client: false
script: |
// Create child incident
var child = new GlideRecord('incident');
child.initialize();
child.parent_incident = current.sys_id;
child.short_description = 'Child of: ' + current.short_description;
child.description = 'Child incident created from ' + current.number;
child.caller_id = current.caller_id;
child.category = current.category;
child.subcategory = current.subcategory;
child.priority = current.priority;
var childId = child.insert();
// Redirect to new child incident
action.setRedirectURL(child);
comments: "Creates a child incident linked to current record"Phase 5: Conditions for Visibility
Step 5.1: Simple Field Conditions
Show button only for specific states:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Reopen Incident"
table: incident
action_name: reopen_incident
order: 100
active: true
form_button: true
condition: "current.state == 7"
script: |
// Reopen logic
current.state = 2; // In Progress
current.work_notes = "Reopened by " + gs.getUserDisplayName();
current.update();
action.setRedirectURL(current);
comments: "Visible only when incident is Closed (state=7)"Common Condition Patterns:
| Condition | Meaning |
current.state == 1 | State equals New |
current.active == true | Record is active |
current.assigned_to == gs.getUserID() | Assigned to current user |
current.assignment_group.manager == gs.getUserID() | Current user is group manager |
current.isNewRecord() | New record (not saved yet) |
!current.isNewRecord() | Existing record only |
current.priority <= 2 | Priority is 1 or 2 |
current.caller_id.vip == true | Caller is VIP |
Step 5.2: Role-Based Conditions
Show button only for specific roles:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Override SLA"
table: incident
action_name: override_sla
order: 100
active: true
form_button: true
condition: "gs.hasRole('incident_manager') || gs.hasRole('admin')"
script: |
// SLA override logic
current.sla_due = '';
current.work_notes = "SLA overridden by manager";
current.update();
comments: "Visible only to incident managers and admins"Step 5.3: Complex Conditions
Multiple conditions combined:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Emergency Escalation"
table: incident
action_name: emergency_escalation
order: 50
active: true
form_button: true
condition: |
// Only show for P1/P2 active incidents assigned to current user's group
current.state != 6 && current.state != 7 &&
current.priority <= 2 &&
current.assignment_group.getDisplayValue() != '' &&
gs.getUser().isMemberOf(current.assignment_group)
script: |
// Emergency escalation logic
current.escalation = 3;
current.work_notes = "[EMERGENCY ESCALATION] " + gs.getUserDisplayName();
current.update();
comments: "Emergency escalation for P1/P2 incidents in user's group"Step 5.4: UI Action Visibility Best Practices
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Approve Request"
table: sc_req_item
action_name: approve_request
order: 100
active: true
form_button: true
condition: |
// Best practice: Check multiple conditions
var canApprove = false;
// 1. Check record state
if (current.approval == 'requested') {
// 2. Check if user is an approver
var approver = new GlideRecord('sysapproval_approver');
approver.addQuery('sysapproval', current.sys_id);
approver.addQuery('approver', gs.getUserID());
approver.addQuery('state', 'requested');
approver.query();
if (approver.next()) {
canApprove = true;
}
// 3. Or check if user has override role
if (gs.hasRole('approval_admin')) {
canApprove = true;
}
}
canApprove;
script: |
// Approval logic
comments: "Visible only when user can approve this request"Phase 6: Client-Side vs Server-Side Scripts
Step 6.1: Client-Side Only (No Submit)
Action that only runs client-side:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Copy to Clipboard"
table: incident
action_name: copy_to_clipboard
order: 100
active: true
form_button: true
client: true
onclick: |
// Pure client-side - no server round trip
var number = g_form.getValue('number');
var shortDesc = g_form.getValue('short_description');
var text = number + ': ' + shortDesc;
// Copy to clipboard (modern browsers)
navigator.clipboard.writeText(text).then(function() {
g_form.addInfoMessage('Copied to clipboard: ' + text);
}).catch(function(err) {
alert('Failed to copy: ' + err);
});
// Return false to prevent form submission
return false;
script: ""
comments: "Copies incident number and description to clipboard"Step 6.2: Server-Side Only
Action that only runs server-side:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Recalculate Priority"
table: incident
action_name: recalculate_priority
order: 100
active: true
form_button: true
client: false
script: |
// Server-side only
var calculator = new PriorityCalculator();
var newPriority = calculator.calculate(current.impact, current.urgency);
current.priority = newPriority;
current.work_notes = "Priority recalculated to " + newPriority;
current.update();
action.setRedirectURL(current);
comments: "Recalculates priority based on impact and urgency matrix"Step 6.3: Combined Client and Server
Client validates/confirms, then server executes:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Transfer to Team"
table: incident
action_name: transfer_to_team
order: 100
active: true
form_button: true
client: true
onclick: |
// Client-side validation and data collection
var group = g_form.getValue('assignment_group');
if (!group) {
g_form.showFieldMsg('assignment_group', 'Please select a group first', 'error');
return false;
}
var reason = prompt('Enter transfer reason (required):');
if (!reason || reason.trim() === '') {
alert('Transfer reason is required');
return false;
}
// Store reason in a hidden field or use sysparm
g_form.setValue('work_notes', 'Transfer Reason: ' + reason);
// Submit to server
gsftSubmit(null, g_form.getFormElement(), 'transfer_to_team');
script: |
// Server-side execution
var targetGroup = current.assignment_group.getDisplayValue();
// Clear individual assignee
current.assigned_to = '';
// Add transfer audit trail
current.work_notes = 'Transferred to ' + targetGroup + '\n' +
'Transferred by: ' + gs.getUserDisplayName();
current.update();
gs.addInfoMessage('Incident transferred to ' + targetGroup);
action.setRedirectURL(current);
comments: "Transfers incident with reason capture"Phase 7: Using g_form in UI Actions
Step 7.1: Common g_form Methods
Reading Field Values:
// In onclick script
var value = g_form.getValue('field_name');
var display = g_form.getDisplayBox('field_name').value;
var reference = g_form.getReference('caller_id', function(ref) {
// Async callback for reference fields
alert('Caller name: ' + ref.name);
});Setting Field Values:
// In onclick script
g_form.setValue('priority', '1');
g_form.setValue('work_notes', 'Updated via UI Action');
g_form.setDisplay('field_name', true); // Show field
g_form.setMandatory('field_name', true); // Make mandatory
g_form.setReadOnly('field_name', true); // Make read-onlyUser Feedback:
// Messages
g_form.addInfoMessage('Operation completed successfully');
g_form.addWarningMessage('Please review the changes');
g_form.addErrorMessage('An error occurred');
g_form.showFieldMsg('field_name', 'Message text', 'info|warning|error');
// Clear messages
g_form.clearMessages();
g_form.hideFieldMsg('field_name');Step 7.2: Complete Client Script Example
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Validate and Submit"
table: incident
action_name: validate_submit
order: 100
active: true
form_button: true
client: true
onclick: |
function validateAndSubmit() {
// Clear previous messages
g_form.clearMessages();
var valid = true;
var errors = [];
// Validate short description
var shortDesc = g_form.getValue('short_description');
if (!shortDesc || shortDesc.length < 10) {
g_form.showFieldMsg('short_description', 'Must be at least 10 characters', 'error');
valid = false;
errors.push('Short description');
}
// Validate caller
var caller = g_form.getValue('caller_id');
if (!caller) {
g_form.showFieldMsg('caller_id', 'Caller is required', 'error');
valid = false;
errors.push('Caller');
}
// Validate category
var category = g_form.getValue('category');
if (!category) {
g_form.showFieldMsg('category', 'Category is required', 'error');
valid = false;
errors.push('Category');
}
// Check P1 requirements
var priority = g_form.getValue('priority');
if (priority == '1') {
var businessImpact = g_form.getValue('business_impact');
if (!businessImpact) {
g_form.showFieldMsg('business_impact', 'Required for P1 incidents', 'error');
valid = false;
errors.push('Business Impact');
}
}
if (!valid) {
g_form.addErrorMessage('Please fix validation errors: ' + errors.join(', '));
return false;
}
// Confirmation
if (confirm('Submit this incident?')) {
gsftSubmit(null, g_form.getFormElement(), 'validate_submit');
return true;
}
return false;
}
validateAndSubmit();
script: |
current.update();
gs.addInfoMessage('Incident saved successfully');
action.setRedirectURL(current);
comments: "Validates required fields before submission"Phase 8: Redirecting After Action
Step 8.1: Redirect Methods
Redirect to Same Record:
// Server-side script
action.setRedirectURL(current);Redirect to Different Record:
// Server-side script
var newRecord = new GlideRecord('incident');
newRecord.get('some_sys_id');
action.setRedirectURL(newRecord);Redirect to List:
// Server-side script
action.setRedirectURL('incident_list.do?sysparm_query=active=true');Redirect to Specific URL:
// Server-side script
action.setRedirectURL('/nav_to.do?uri=incident.do?sys_id=' + current.sys_id);Prevent Redirect (Stay on Form):
// Server-side script
current.update();
action.setRedirectURL(current); // Refresh current recordRedirect to Previous Page:
// Client-side onclick
// After server processing, redirect back
history.back();
return false;Step 8.2: Common Redirect Patterns
Clone and Redirect to New:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Clone Incident"
table: incident
action_name: clone_incident
order: 100
active: true
form_button: true
condition: "!current.isNewRecord()"
client: false
script: |
// Clone the incident
var clone = new GlideRecord('incident');
clone.initialize();
// Copy fields
clone.short_description = 'CLONE: ' + current.short_description;
clone.description = current.description;
clone.caller_id = current.caller_id;
clone.category = current.category;
clone.subcategory = current.subcategory;
clone.priority = current.priority;
clone.impact = current.impact;
clone.urgency = current.urgency;
// Reference original
clone.work_notes = 'Cloned from ' + current.number;
var cloneId = clone.insert();
// Redirect to new cloned record
action.setRedirectURL(clone);
comments: "Creates a copy of the current incident"Save and Return to List:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Save and Return to List"
table: incident
action_name: save_return_list
order: 100
active: true
form_button: true
client: false
script: |
current.update();
action.setRedirectURL('incident_list.do');
comments: "Saves current record and returns to incident list"Phase 9: Action Security
Step 9.1: Role-Based Access
Restrict to Specific Roles (Using Condition):
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Delete Incident"
table: incident
action_name: delete_incident
order: 900
active: true
form_button: true
condition: "gs.hasRole('incident_admin')"
client: true
onclick: |
if (!confirm('Are you sure you want to DELETE this incident? This cannot be undone.')) {
return false;
}
if (!confirm('FINAL WARNING: This will permanently delete ' + g_form.getValue('number'))) {
return false;
}
gsftSubmit(null, g_form.getFormElement(), 'delete_incident');
script: |
var number = current.number;
current.deleteRecord();
gs.addInfoMessage('Incident ' + number + ' has been deleted');
action.setRedirectURL('incident_list.do');
comments: "Permanently deletes incident - restricted to incident_admin"Step 9.2: Create ACL for UI Action
Create explicit ACL protection:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "incident.delete_incident"
type: ui_action
operation: execute
active: true
admin_overrides: false
advanced: false
script: "gs.hasRole('incident_admin')"
description: "ACL for delete incident UI action"Step 9.3: Security Best Practices
- Always validate on server-side: Client-side checks can be bypassed
- Use conditions AND ACLs: Defense in depth
- Log sensitive actions: Create audit trail
- Limit destructive actions: Require confirmation and restrict roles
Complete Secure Action Example:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Override Approval"
table: sc_req_item
action_name: override_approval
order: 100
active: true
form_button: true
condition: |
// Check role and record state
gs.hasRole('approval_admin') &&
current.approval == 'requested' &&
!current.isNewRecord()
client: true
onclick: |
// Client-side confirmation
var reason = prompt('Enter override reason (required for audit):');
if (!reason || reason.trim().length < 10) {
alert('A detailed reason (min 10 chars) is required for approval override');
return false;
}
if (!confirm('Override approval for ' + g_form.getValue('number') + '?')) {
return false;
}
// Store reason
g_form.setValue('work_notes', 'APPROVAL OVERRIDE REASON: ' + reason);
gsftSubmit(null, g_form.getFormElement(), 'override_approval');
script: |
// Server-side validation (defense in depth)
if (!gs.hasRole('approval_admin')) {
gs.addErrorMessage('Unauthorized: approval_admin role required');
action.setRedirectURL(current);
return;
}
// Process override
current.approval = 'approved';
current.work_notes = 'APPROVAL OVERRIDE\n' +
'Overridden by: ' + gs.getUserDisplayName() + '\n' +
'Date: ' + new GlideDateTime().getDisplayValue();
current.update();
// Audit log
gs.log('Approval override on ' + current.number + ' by ' + gs.getUserName(), 'ApprovalOverride');
gs.addInfoMessage('Approval overridden successfully');
action.setRedirectURL(current);
comments: "Override approval with audit trail - approval_admin only"Phase 10: Common Use Cases
Step 10.1: Approve/Reject Pattern
Approve Button:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Approve"
table: sc_req_item
action_name: approve_item
order: 100
active: true
form_button: true
condition: |
current.approval == 'requested' &&
new ApprovalHelper().isUserApprover(gs.getUserID(), current.sys_id)
client: true
onclick: |
if (!confirm('Approve this request?')) {
return false;
}
gsftSubmit(null, g_form.getFormElement(), 'approve_item');
script: |
var approver = new GlideRecord('sysapproval_approver');
approver.addQuery('sysapproval', current.sys_id);
approver.addQuery('approver', gs.getUserID());
approver.addQuery('state', 'requested');
approver.query();
if (approver.next()) {
approver.state = 'approved';
approver.update();
current.work_notes = 'Approved by ' + gs.getUserDisplayName();
current.update();
gs.addInfoMessage('Request approved');
}
action.setRedirectURL(current);
comments: "Approve request item"Reject Button:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Reject"
table: sc_req_item
action_name: reject_item
order: 110
active: true
form_button: true
condition: |
current.approval == 'requested' &&
new ApprovalHelper().isUserApprover(gs.getUserID(), current.sys_id)
client: true
onclick: |
var reason = prompt('Enter rejection reason (required):');
if (!reason || reason.trim() === '') {
alert('Rejection reason is required');
return false;
}
g_form.setValue('work_notes', 'REJECTION REASON: ' + reason);
if (!confirm('Reject this request?')) {
return false;
}
gsftSubmit(null, g_form.getFormElement(), 'reject_item');
script: |
var approver = new GlideRecord('sysapproval_approver');
approver.addQuery('sysapproval', current.sys_id);
approver.addQuery('approver', gs.getUserID());
approver.addQuery('state', 'requested');
approver.query();
if (approver.next()) {
approver.state = 'rejected';
approver.update();
current.work_notes = 'Rejected by ' + gs.getUserDisplayName();
current.update();
gs.addInfoMessage('Request rejected');
}
action.setRedirectURL(current);
comments: "Reject request item with reason"Step 10.2: Escalation Pattern
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Escalate"
table: incident
action_name: escalate_incident
order: 100
active: true
form_button: true
condition: "current.escalation < 3 && current.state != 6 && current.state != 7"
client: true
onclick: |
var currentLevel = parseInt(g_form.getValue('escalation')) || 0;
var levels = ['Normal', 'Moderate', 'High', 'Overdue'];
var nextLevel = levels[currentLevel + 1] || 'Maximum';
if (!confirm('Escalate to ' + nextLevel + ' level?')) {
return false;
}
gsftSubmit(null, g_form.getFormElement(), 'escalate_incident');
script: |
var oldLevel = current.escalation;
current.escalation = parseInt(current.escalation) + 1;
current.work_notes = 'Escalated from level ' + oldLevel + ' to ' + current.escalation +
' by ' + gs.getUserDisplayName();
current.update();
gs.addInfoMessage('Incident escalated to level ' + current.escalation);
action.setRedirectURL(current);
comments: "Escalates incident to next level"Step 10.3: Clone Pattern
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_action
data:
name: "Clone Record"
table: incident
action_name: clone_record
order: 200
active: true
form_link: true
condition: "!current.isNewRecord()"
client: false
script: |
// Fields to copy
var copyFields = [
'short_description', 'description', 'caller_id',
'category', 'subcategory', 'priority', 'impact',
'urgency', 'assignment_group', 'contact_type'
];
// Fields to exclude (system fields, unique identifiers)
var excludeFields = [
'sys_id', 'number', 'opened_at', 'opened_by',
'closed_at', 'closed_by', 'resolved_at', 'resolved_by',
'sys_created_on', 'sys_created_by', 'sys_updated_on', 'sys_updated_by'
];
var clone = new GlideRecord('incident');
clone.initialize();
// Copy specified fields
for (var i = 0; i < copyFields.length; i++) {
var field = copyFields[i];
if (current.getValue(field)) {
clone.setValue(field, current.getValue(field));
}
}
// Modify for clone
clone.short_description = '[CLONE] ' + clone.short_description;
clone.work_notes = 'Cloned from ' + current.number + ' by ' + gs.getUserDisplayName();
var sysId = clone.insert();
gs.addInfoMessage('Cloned incident ' + current.number + ' successfully');
action.setRedirectURL(clone);
comments: "Creates a copy of the incident"Phase 11: Testing UI Actions
Step 11.1: Query Existing UI Actions
Find all UI actions for a table:
Tool: SN-Query-Table
Parameters:
table_name: sys_ui_action
query: table=incident^active=true
fields: sys_id,name,action_name,order,form_button,list_banner_button,condition
limit: 50Find UI actions by name:
Tool: SN-Query-Table
Parameters:
table_name: sys_ui_action
query: nameLIKEapprove^active=true
fields: sys_id,name,table,condition,client
limit: 20Step 11.2: Testing Checklist
Pre-Testing:
- UI Action is active
- Table is correct
- Location is set (form_button, list_banner_button, etc.)
- Condition returns true for test scenario
- Scripts have no syntax errors
Functional Testing:
- Visibility Test:
- Does button appear when condition is true?
- Does button hide when condition is false?
- Is button visible to correct roles only?
- Client Script Test:
- Does onclick validation work?
- Are error messages displayed?
- Does confirmation dialog appear?
- Does form submit correctly?
- Server Script Test:
- Is record updated correctly?
- Is redirect working?
- Are work notes added?
- Is audit trail created?
- Security Test:
- Can unauthorized users access the action?
- Can users bypass client validation?
- Are server-side role checks in place?
Step 11.3: Debug Server Script
Add Debug Logging:
Tool: SN-Update-Record
Parameters:
table_name: sys_ui_action
sys_id: [action_sys_id]
data:
script: |
// Debug logging
gs.info('[DEBUG] UI Action: escalate_incident started');
gs.info('[DEBUG] Current user: ' + gs.getUserName());
gs.info('[DEBUG] Current record: ' + current.number);
try {
// Your logic here
current.escalation = parseInt(current.escalation) + 1;
current.update();
gs.info('[DEBUG] Escalation updated to: ' + current.escalation);
} catch (e) {
gs.error('[DEBUG] Error: ' + e.message);
}
action.setRedirectURL(current);Check Logs:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: messageLIKE[DEBUG] UI Action^sys_created_on>javascript:gs.minutesAgo(10)
fields: message,level,sys_created_on
limit: 50Phase 12: Explicit Local Script Development
Step 12.1: Pull UI Action Fields Locally
Retrieve both script fields and freshness metadata:
Tool: SN-Get-Record
Parameters:
table_name: sys_ui_action
sys_id: [ui_action_sys_id]
fields: sys_id,script,onclick,sys_updated_on,sys_mod_count
instance: devWrite script and onclick to separate local files. Before pushing, retrieve
the same fields again and stop if sys_updated_on, sys_mod_count, or either
remote field changed. After merging any conflict locally, update only the
reviewed fields:
Tool: SN-Update-Record
Parameters:
table_name: sys_ui_action
sys_id: [ui_action_sys_id]
data:
script: [reviewed_server_script]
onclick: [reviewed_client_script]
instance: devStep 12.2: Directory Structure
servicenow/
└── ui_actions/
├── incident/
│ ├── escalate_incident_server.js
│ ├── escalate_incident_client.js
│ ├── close_incident_server.js
│ └── close_incident_client.js
├── sc_req_item/
│ ├── approve_item_server.js
│ └── reject_item_server.js
└── README.mdTool Usage Summary
| Operation | MCP Tool | Notes |
| Create UI Action | SN-Create-Record | sys_ui_action table |
| Update UI Action | SN-Update-Record | Modify script, condition |
| Query UI Actions | SN-Query-Table | Find by table, name |
| Get Schema | SN-Get-Table-Schema | Understand available fields |
| Local script cycle | SN-Get-Record + SN-Update-Record | Pull, freshness check, edit, push |
| Execute Test Script | SN-Execute-Background-Script | Debug server logic |
Best Practices
- Descriptive Names: Use clear, action-oriented names (e.g., "Escalate to Management")
- Consistent Order: Use standardized order numbers (100, 200, 300)
- Always Validate: Both client-side (user experience) and server-side (security)
- Use Conditions: Hide irrelevant buttons instead of showing disabled ones
- Add Comments: Document the purpose and any dependencies
- Role-Based Access: Always restrict sensitive actions
- Audit Trail: Add work notes for all significant actions
- Error Handling: Wrap server scripts in try-catch blocks
- Test Thoroughly: Test all conditions, roles, and edge cases
- Version Control: Sync scripts locally for version control
Troubleshooting
Button Not Appearing
Symptom: UI Action button not visible on form/list Causes:
- UI Action is inactive
- Condition evaluates to false
- Wrong location flag set
- User lacks required role
Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_ui_action
query: action_name=my_action
fields: active,condition,form_button,tableCheck active=true and verify condition logic.
Client Script Not Executing
Symptom: Nothing happens when clicking button Causes:
- JavaScript syntax error
- client field not set to true
- onclick is empty
Solution:
- Check browser console (F12) for errors
- Verify
client: true - Test onclick script in browser console
Server Script Failing
Symptom: Form submits but no action occurs Causes:
- Server script syntax error
- Permission error
- Missing current.update()
Solution:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: levelINerror,warn^sys_created_on>javascript:gs.minutesAgo(5)
fields: message,source,sys_created_on
limit: 20Redirect Not Working
Symptom: Stays on form or goes to wrong page Causes:
- Missing action.setRedirectURL()
- Invalid redirect target
- Client returning false before submit
Solution:
- Verify
action.setRedirectURL(current)at end of server script - For custom URLs, ensure valid format
- Check client onclick returns true or submits form
Condition Errors
Symptom: Button appears/disappears unexpectedly Causes:
- Condition syntax error (fails silently)
- Reference field not loaded
- Type mismatch (string vs number)
Solution: Test condition in background script:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var current = new GlideRecord('incident');
current.get('[sys_id]');
// Test your condition
var result = current.state == 7; // Your condition
gs.info('Condition result: ' + result);
description: Test UI action conditionRelated Skills
admin/script-execution- Background script patternscatalog/ui-policies- Catalog form behavioradmin/workflow-creation- Workflow integrationsecurity/acl-management- Access control listsadmin/script-sync- Local script development
References
In this repository184 other files it gives its agents.
- happy-platform-skillsSkill
- application-scopeSkill
- batch-operationsSkill
- configuration-validationSkill
- deployment-workflowSkill
- generic-crud-operationsSkill
- instance-managementSkill
- schema-discoverySkill
- script-executionSkill
- script-syncSkill
- smart-assessmentSkill
- task-analysisSkill
- task-summarizationSkill
- update-set-managementSkill
- user-provisioningSkill
- workflow-creationSkill
- workspace-insightsSkill
- approval-summarizationSkill
- approval-workflowsSkill
- catalog-builder-aiSkill
- catalog-item-generationSkill
- item-creationSkill
- multi-turn-orderingSkill
- request-fulfillmentSkill
- ui-policiesSkill
- variable-managementSkill
- ci-discoverySkill
- cmdb-search-analysisSkill
- data-qualitySkill
- impact-analysisSkill
- relationship-mappingSkill
- service-graph-diagnosisSkill
- activity-responseSkill
- csm-case-summarizationSkill
- chat-recommendationSkill
- email-recommendationSkill
- csm-kb-generationSkill
- resolution-notesSkill
- csm-sentiment-analysisSkill
- csm-sidebar-summarizationSkill
- csm-suggested-stepsSkill
- trending-topicsSkill
- automated-testingSkill
- business-rulesSkill
- client-scriptsSkill
- code-assistSkill
- code-reviewSkill
- data-importSkill
- debugging-techniquesSkill
- fluent-sdkSkill
- glide-api-referenceSkill
- mcp-server-installationSkill
- mcp-serverSkill
- notificationsSkill
- performance-optimizationSkill
- scheduled-jobsSkill
- scripted-rest-apisSkill
- script-includesSkill
- servicenow-docs-mcpSkill
- test-generationSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

