business-rules
Skill41 starsChanged 6 days agoApache-2.0View on GitHub
Develop secure, efficient business rules with correct timing, conditions, Glide API patterns, error handling, and tests.
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 5 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
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 345
Use script conditions only when the condition field cannot express the logic.
- Very specific: line 862
Name Variables Clearly: Use descriptive names like existingTask not gr2
- Very specific: line 864
Avoid Magic Numbers: Use constants or comments for values like state=6
- Distinctive: line 867
Use .changes() Method: Always exit early if field hasn't changed
- Distinctive: line 869
Limit Queries: Use setLimit(), avoid queries in loops
- Distinctive: line 875
Test with Multiple Roles: Ensure ACLs don't break business rules
- Distinctive: line 876
Avoid Recursion: Use setWorkflow(false) when updating same table
≈ “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
Business Rules Development
Overview
Business rules are server-side scripts that execute when records are displayed, inserted, updated, deleted, or queried. They are the backbone of ServiceNow automation.
- What problem does it solve? Automates record-level logic, enforces data integrity, and triggers workflows based on record changes
- Who should use this skill? ServiceNow developers building custom automation logic
- Expected outcomes: Well-structured, performant business rules that follow ServiceNow best practices
Prerequisites
- Roles:
adminor scoped app developer role - Knowledge: JavaScript fundamentals, GlideRecord API basics
- Access: sys_script table, target table for business rule
- Related skills:
admin/script-execution,admin/update-set-management
When to Use Business Rules
Timing Matrix
| When | Trigger | Use Case | current/previous |
| before | Insert/Update/Delete | Validate data, set field values, abort operations | Both available |
| after | Insert/Update/Delete | Create related records, send notifications, external integrations | Both available |
| async | Insert/Update/Delete | Long-running operations, external API calls | Only current |
| display | Query/Display | Calculate runtime values, populate scratchpad | Only current |
Decision Guide
Use BEFORE when:
- Setting default values based on other fields
- Validating data before save
- Modifying field values before commit
- Aborting invalid operations with
current.setAbortAction(true)
Use AFTER when:
- Creating child/related records
- Sending notifications (after record is committed)
- Updating other tables
- Triggering workflows
Use ASYNC when:
- Making external REST/SOAP calls
- Processing large data sets
- Operations that can fail without blocking the user
- Long-running calculations
Use DISPLAY when:
- Calculating values for form display only
- Populating g_scratchpad for client scripts
- Runtime-only field values (not stored)
Procedure
Phase 1: Create a Business Rule
Step 1.1: Query Table Schema
First, understand the target table structure.
Using MCP:
Tool: SN-Get-Table-Schema
Parameters:
table_name: incidentStep 1.2: Create Basic Business Rule
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Set Priority Based on Impact and Urgency
collection: incident
active: true
when: before
order: 100
filter_condition: impactCHANGES^ORurgencyCHANGES
script: |
(function executeRule(current, previous /*null when async*/) {
// Calculate priority from impact and urgency matrix
var impact = parseInt(current.impact);
var urgency = parseInt(current.urgency);
// Priority matrix: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning
var matrix = {
'1-1': 1, '1-2': 2, '1-3': 3,
'2-1': 2, '2-2': 3, '2-3': 4,
'3-1': 3, '3-2': 4, '3-3': 5
};
var key = impact + '-' + urgency;
var newPriority = matrix[key] || 4;
if (current.priority != newPriority) {
current.priority = newPriority;
gs.info('Priority calculated: ' + newPriority + ' for ' + current.number);
}
})(current, previous);Step 1.3: Create After Business Rule
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Create Related Task on P1 Incident
collection: incident
active: true
when: after
order: 200
filter_condition: priority=1^stateVALCHANGES1
script: |
(function executeRule(current, previous /*null when async*/) {
// Only on insert or when becoming P1
if (current.operation() == 'insert' ||
(previous && previous.priority != 1)) {
var task = new GlideRecord('sc_task');
task.initialize();
task.short_description = 'P1 Response: ' + current.short_description;
task.description = 'Critical incident requires immediate response.\n\nIncident: ' + current.number;
task.assignment_group = current.assignment_group;
task.assigned_to = current.assigned_to;
task.priority = 1;
task.parent = current.sys_id;
var taskId = task.insert();
gs.info('Created P1 response task: ' + taskId + ' for ' + current.number);
}
})(current, previous);Step 1.4: Create Async Business Rule
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Notify External System on Incident Create
collection: incident
active: true
when: async
order: 500
action_insert: true
script: |
(function executeRule(current, previous /*null when async*/) {
try {
var request = new sn_ws.RESTMessageV2('External Notification', 'POST');
request.setStringParameterNoEscape('incident_number', current.number.toString());
request.setStringParameterNoEscape('short_description', current.short_description.toString());
request.setStringParameterNoEscape('priority', current.priority.toString());
var response = request.execute();
var httpStatus = response.getStatusCode();
if (httpStatus == 200 || httpStatus == 201) {
gs.info('External notification sent for: ' + current.number);
} else {
gs.error('External notification failed: ' + httpStatus + ' - ' + response.getBody());
}
} catch (e) {
gs.error('External notification error: ' + e.message);
}
})(current, previous);Phase 2: Using current and previous Objects
Step 2.1: Understanding current vs previous
The current object represents the record being processed. The previous object contains field values before the current transaction.
Key Differences:
| Aspect | current | previous |
| Availability | All business rules | before/after only (null in async) |
| Modifiable | Yes (before rules) | No (read-only) |
| Insert operations | Has values | null |
| Values | New/modified | Original before change |
Step 2.2: Detecting Field Changes
Using .changes() Method (Recommended):
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Log State Changes
collection: incident
active: true
when: after
order: 100
script: |
(function executeRule(current, previous /*null when async*/) {
// EFFICIENT: Exit early if field hasn't changed
if (!current.state.changes()) {
return; // No work to do
}
// Get old and new values
var oldState = previous ? previous.state.getDisplayValue() : '(new)';
var newState = current.state.getDisplayValue();
gs.info('Incident ' + current.number + ' state changed: ' + oldState + ' -> ' + newState);
// Add work note
current.work_notes = 'State changed from ' + oldState + ' to ' + newState;
})(current, previous);Manual Change Detection (When .changes() Not Suitable):
// For complex comparisons or calculated changes
if (previous && current.priority != previous.priority) {
// Priority changed
}
// For reference fields, compare sys_id
if (previous && current.assigned_to.toString() != previous.assigned_to.toString()) {
// Assignment changed
}
// For multiple fields
var fieldsToCheck = ['state', 'priority', 'assigned_to'];
var changedFields = [];
for (var i = 0; i < fieldsToCheck.length; i++) {
var field = fieldsToCheck[i];
if (current[field].changes()) {
changedFields.push(field);
}
}
if (changedFields.length > 0) {
gs.info('Changed fields: ' + changedFields.join(', '));
}Step 2.3: Checking Operation Type
// Determine what triggered the business rule
var operation = current.operation();
switch (operation) {
case 'insert':
// Record is being created
gs.info('New record: ' + current.getTableName());
break;
case 'update':
// Record is being updated
gs.info('Update to: ' + current.getUniqueValue());
break;
case 'delete':
// Record is being deleted
gs.info('Deleting: ' + current.getUniqueValue());
break;
}Phase 3: Condition Field vs Script Conditions
Step 3.1: Condition Field (Preferred for Simple Conditions)
The condition field uses encoded queries and is evaluated BEFORE the script runs. This is more efficient because:
- Evaluated at the database level
- Script never executes if condition fails
- No JavaScript overhead
Best Practices for Condition Field:
# Only run on active P1 incidents
active=true^priority=1
# Only when state changes to Resolved
stateVALCHANGES6
# Only when assigned_to changes
assigned_toCHANGES
# Multiple conditions (AND)
active=true^priority=1^stateVALCHANGES6
# Only on insert (no previous value for state)
stateISEMPTYfalse^ORstateISEMPTYCommon Condition Operators:
| Operator | Meaning | Example |
| CHANGES | Field value changed | stateCHANGES |
| VALCHANGES | Changed TO specific value | stateVALCHANGES6 |
| CHANGESFROM | Changed FROM specific value | stateCHANGESFROM1 |
| = | Equals | priority=1 |
| != | Not equals | state!=7 |
| ISEMPTY | Field is empty | assigned_toISEMPTY |
| ISNOTEMPTY | Field has value | assigned_toISNOTEMPTY |
Step 3.2: Script Conditions (For Complex Logic)
Use script conditions only when the condition field cannot express the logic.
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Complex Condition Example
collection: incident
active: true
when: before
order: 100
condition: |
// Script condition - returns true/false
// Available: current, previous
// Check if escalating (priority going from higher number to lower)
if (current.priority.changes()) {
var oldPri = previous ? parseInt(previous.priority) : 5;
var newPri = parseInt(current.priority);
return newPri < oldPri; // True if escalating
}
return false;
script: |
(function executeRule(current, previous /*null when async*/) {
// This only runs if condition returned true
current.work_notes = 'Incident escalated from P' + previous.priority + ' to P' + current.priority;
gs.info('Escalation detected: ' + current.number);
})(current, previous);When to Use Script Conditions:
- Complex date calculations
- Cross-table validations
- Dynamic conditions based on user roles
- Calculations involving multiple fields
- Conditions requiring GlideRecord queries
Phase 4: Common Glide API Methods
Step 4.1: GlideRecord Essentials
// Query records
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 1);
gr.orderByDesc('sys_created_on');
gr.setLimit(10);
gr.query();
while (gr.next()) {
gs.info('Found: ' + gr.number + ' - ' + gr.short_description);
}
// Get single record by sys_id
var incident = new GlideRecord('incident');
if (incident.get('sys_id_value')) {
gs.info('Found: ' + incident.number);
}
// Get by field value
var user = new GlideRecord('sys_user');
if (user.get('user_name', 'admin')) {
gs.info('Admin sys_id: ' + user.sys_id);
}
// Insert new record
var newTask = new GlideRecord('task');
newTask.initialize();
newTask.short_description = 'New task from business rule';
newTask.assignment_group = current.assignment_group;
var sysId = newTask.insert();
// Update record
var toUpdate = new GlideRecord('incident');
if (toUpdate.get('sys_id_value')) {
toUpdate.work_notes = 'Updated via business rule';
toUpdate.update();
}
// Delete record (use with caution)
var toDelete = new GlideRecord('task');
if (toDelete.get('sys_id_value')) {
toDelete.deleteRecord();
}Step 4.2: GlideSystem (gs) Methods
// Logging
gs.info('Information message');
gs.warn('Warning message');
gs.error('Error message');
gs.debug('Debug message'); // Requires debug enabled
// User context
var userId = gs.getUserID();
var userName = gs.getUserName();
var userDisplayName = gs.getUserDisplayName();
// Check roles
if (gs.hasRole('admin')) {
// Admin-only logic
}
if (gs.hasRole('itil') || gs.hasRole('catalog_admin')) {
// ITIL or catalog admin logic
}
// Date/Time
var now = gs.now(); // Current date/time string
var nowDT = gs.nowDateTime(); // GlideDateTime
var today = gs.beginningOfToday(); // Start of today
var daysAgo = gs.daysAgo(7); // 7 days ago
// Properties
var propValue = gs.getProperty('my.property.name', 'default');
gs.setProperty('my.property.name', 'new_value');
// Generate GUID
var guid = gs.generateGUID();
// Include script include
gs.include('MyScriptInclude');
var util = new MyScriptInclude();
// Nil check (empty string, null, undefined)
if (gs.nil(current.assigned_to)) {
// Field is empty
}
// Event queue (trigger events)
gs.eventQueue('incident.created', current, current.number, current.priority);Step 4.3: GlideDateTime Operations
// Current time
var now = new GlideDateTime();
// Create from string
var dt = new GlideDateTime('2026-02-06 10:30:00');
// Add/subtract time
var future = new GlideDateTime();
future.addDays(5);
future.addHours(2);
future.addMinutes(30);
// Compare dates
var dt1 = new GlideDateTime();
var dt2 = new GlideDateTime(current.sys_created_on);
if (dt1.after(dt2)) {
gs.info('dt1 is after dt2');
}
// Calculate duration
var duration = GlideDateTime.subtract(dt2, dt1); // Returns GlideDuration
var seconds = duration.getNumericValue() / 1000;
var displayValue = duration.getDisplayValue(); // "2 Days 3 Hours"
// Business time calculations
var schedule = new GlideSchedule('sys_id_of_schedule');
var dur = schedule.duration(dt1, dt2);
// Format output
var formatted = now.getDisplayValue(); // User's timezone
var internal = now.getValue(); // Internal format (UTC)
var date = now.getLocalDate().getValue(); // Date onlyStep 4.4: GlideAggregate for Counts and Sums
// Count records
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
var count = ga.getAggregate('COUNT');
gs.info('Active incidents: ' + count);
}
// Count by group
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.orderByAggregate('COUNT', false); // Descending
ga.query();
while (ga.next()) {
var priority = ga.priority.getDisplayValue();
var count = ga.getAggregate('COUNT');
gs.info(priority + ': ' + count + ' incidents');
}
// Sum values
var ga = new GlideAggregate('sc_task');
ga.addQuery('active', true);
ga.addAggregate('SUM', 'time_worked');
ga.query();
if (ga.next()) {
var totalTime = ga.getAggregate('SUM', 'time_worked');
}
// Average
ga.addAggregate('AVG', 'priority');
// Min/Max
ga.addAggregate('MIN', 'sys_created_on');
ga.addAggregate('MAX', 'sys_created_on');Phase 5: Error Handling and Logging
Step 5.1: Comprehensive Error Handling Pattern
Using MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_script
data:
name: Robust Error Handling Example
collection: incident
active: true
when: after
order: 100
script: |
(function executeRule(current, previous /*null when async*/) {
var BR_NAME = 'Robust Error Handling Example';
try {
// Validate inputs
if (gs.nil(current.sys_id)) {
throw new Error('Current record has no sys_id');
}
// Main logic
var relatedCI = new GlideRecord('cmdb_ci');
if (!relatedCI.get(current.cmdb_ci)) {
gs.warn('[' + BR_NAME + '] No CI found for incident: ' + current.number);
return; // Exit gracefully
}
// Perform operations
relatedCI.operational_status = 2; // Under maintenance
relatedCI.update();
gs.info('[' + BR_NAME + '] Updated CI: ' + relatedCI.name);
} catch (e) {
// Log error with context
gs.error('[' + BR_NAME + '] Error: ' + e.message);
gs.error('[' + BR_NAME + '] Incident: ' + current.number);
gs.error('[' + BR_NAME + '] Stack: ' + e.stack);
// Optionally create incident for business rule error
// createErrorIncident(BR_NAME, e, current);
}
})(current, previous);Step 5.2: Abort Actions with User Feedback
// Before business rule - prevent invalid save
(function executeRule(current, previous) {
var errors = [];
// Validation 1: Required field
if (current.priority == 1 && gs.nil(current.assigned_to)) {
errors.push('P1 incidents must have an assigned user');
}
// Validation 2: Business logic
if (current.state == 6 && gs.nil(current.resolution_notes)) {
errors.push('Resolution notes are required when resolving');
}
// Validation 3: Cross-field validation
if (current.impact == 1 && current.urgency == 1 && current.priority != 1) {
errors.push('Impact 1 + Urgency 1 must equal Priority 1');
}
// Abort if errors
if (errors.length > 0) {
var errorMsg = errors.join('\n');
gs.addErrorMessage(errorMsg);
current.setAbortAction(true);
gs.info('[Validation BR] Aborted save: ' + errorMsg);
}
})(current, previous);Phase 6: Performance Optimization
Step 6.1: Use .changes() for Efficiency
WRONG - Always executes full script:
// Inefficient - script runs on every update
(function executeRule(current, previous) {
if (previous && current.state != previous.state) {
// State changed - do work
}
})(current, previous);RIGHT - Use condition field + .changes():
Filter Condition: stateCHANGES
Script:
(function executeRule(current, previous) {
// Script only runs when state changes
// Additional validation with .changes() for safety
if (current.state.changes()) {
// Do work
}
})(current, previous);Step 6.2: Minimize GlideRecord Queries
WRONG - Query inside loop:
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
// BAD: Query for each incident
var user = new GlideRecord('sys_user');
user.get(incidents.assigned_to);
// ...
}RIGHT - Use dot-walking or batch queries:
// Option 1: Dot-walking (single query)
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
var userName = incidents.assigned_to.name; // Dot-walk
var userEmail = incidents.assigned_to.email;
}
// Option 2: Batch query with lookup map
var userIds = [];
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
if (!gs.nil(incidents.assigned_to)) {
userIds.push(incidents.assigned_to.toString());
}
}
var userMap = {};
if (userIds.length > 0) {
var users = new GlideRecord('sys_user');
users.addQuery('sys_id', 'IN', userIds.join(','));
users.query();
while (users.next()) {
userMap[users.sys_id.toString()] = {
name: users.name.toString(),
email: users.email.toString()
};
}
}Step 6.3: Order and Active Flag Best Practices
| Practice | Recommendation |
| Order | 100-199 for validation, 200-399 for field setting, 400+ for external/async |
| Active | Set to false during development, enable when tested |
| Condition | Use filter condition field, not script conditions |
| Inheritance | Set "Inherits" carefully - usually leave unchecked |
Step 6.4: Avoid Recursive Updates
DANGEROUS - Can cause infinite loops:
// After business rule on incident
(function executeRule(current, previous) {
current.work_notes = 'Updated at ' + gs.now();
current.update(); // DANGER: Triggers business rules again!
})(current, previous);SAFE - Set workflow false or use before rules:
// Option 1: Disable workflow on update
var gr = new GlideRecord('incident');
if (gr.get(current.sys_id)) {
gr.setWorkflow(false); // Skip business rules
gr.autoSysFields(false); // Skip sys field updates
gr.work_notes = 'Updated at ' + gs.now();
gr.update();
}
// Option 2: Use before rule instead (preferred)
// Before business rule - modifies current directly
(function executeRule(current, previous) {
current.work_notes = 'Updated at ' + gs.now();
// No .update() needed - current is saved automatically
})(current, previous);Phase 7: Testing Business Rules
Step 7.1: Query Existing Business Rules
Using MCP:
Tool: SN-Query-Table
Parameters:
table_name: sys_script
query: collection=incident^active=true
fields: name,when,order,filter_condition,active
limit: 50Step 7.2: Test with Background Script
Using MCP:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Test business rule by simulating record operation
var testIncident = new GlideRecord('incident');
testIncident.initialize();
testIncident.short_description = 'Test incident for BR testing';
testIncident.caller_id = gs.getUserID();
testIncident.impact = 1;
testIncident.urgency = 1;
// Insert will trigger before/after insert rules
var sysId = testIncident.insert();
gs.info('Created test incident: ' + sysId);
// Verify priority was calculated
testIncident.get(sysId);
gs.info('Priority after BR: ' + testIncident.priority.getDisplayValue());
// Clean up (optional)
// testIncident.deleteRecord();
description: Test priority calculation business ruleStep 7.3: Check System Logs
Using MCP:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: messageLIKEbusiness rule^ORmessageLIKE[BR]^sys_created_on>javascript:gs.minutesAgo(10)
fields: message,level,sys_created_on,source
limit: 50Step 7.4: Explicit Local Development Cycle
Pull the current script and freshness metadata:
Tool: SN-Get-Record
Parameters:
table_name: sys_script
sys_id: [business_rule_sys_id]
fields: sys_id,script,sys_updated_on,sys_mod_count
instance: devSave the script field locally and edit it in the IDE. Immediately before
pushing, repeat SN-Get-Record and compare sys_updated_on, sys_mod_count,
and the original script. If the remote record changed, merge it locally instead
of overwriting it. Otherwise push only the reviewed script field:
Tool: SN-Update-Record
Parameters:
table_name: sys_script
sys_id: [business_rule_sys_id]
data:
script: [reviewed_local_script]
instance: devTool Usage Summary
| Operation | MCP Tool | Purpose |
| Create BR | SN-Create-Record (sys_script) | Create new business rule |
| Update BR | SN-Update-Record | Modify existing business rule |
| Query BRs | SN-Query-Table | Find business rules on table |
| Test BR | SN-Execute-Background-Script | Simulate record operations |
| Debug | SN-Query-Table (syslog) | Check execution logs |
| Local Dev | SN-Get-Record + SN-Update-Record | Explicit pull, freshness check, edit, and push |
| Get Schema | SN-Get-Table-Schema | Understand table structure |
Best Practices
Code Quality
- Use IIFE Pattern: Wrap all scripts in
(function executeRule(current, previous) {...})(current, previous); - Name Variables Clearly: Use descriptive names like
existingTasknotgr2 - Comment Complex Logic: Explain why, not what
- Avoid Magic Numbers: Use constants or comments for values like
state=6
Performance
- Use .changes() Method: Always exit early if field hasn't changed
- Prefer Condition Field: Database-level filtering is faster than script
- Limit Queries: Use setLimit(), avoid queries in loops
- Order Carefully: Lower order = runs first. Put validations early (100-199)
Reliability
- Always Handle Errors: Wrap in try-catch, especially for external calls
- Log Meaningful Messages: Include record identifiers, field values
- Test with Multiple Roles: Ensure ACLs don't break business rules
- Avoid Recursion: Use setWorkflow(false) when updating same table
Maintainability
- One Purpose Per Rule: Split complex rules into focused components
- Use Script Includes: Move reusable logic to script includes
- Document Dependencies: Note what other rules/scripts this interacts with
- Version Control: Pull with
SN-Get-Record, keep the base revision in Git, freshness-check, then push withSN-Update-Record
Troubleshooting
Business Rule Not Firing
Symptom: Record saves but business rule script doesn't execute Causes:
- Active = false
- Filter condition not matching
- Order conflict with abort action
- ACL blocking field access Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_script
query: collection=incident^name=*priority*
fields: name,active,when,filter_condition,orderInfinite Loop / Stack Overflow
Symptom: Error "Maximum call stack size exceeded" or timeout Cause: Business rule calling .update() on same table, triggering itself Solution:
// Use setWorkflow(false) to prevent recursion
var gr = new GlideRecord('incident');
gr.get(current.sys_id);
gr.setWorkflow(false);
gr.update();Previous is Null
Symptom: Error accessing previous.fieldname Cause: previous is null on insert operations or in async rules Solution:
// Always check for null
if (previous && current.state != previous.state) {
// Safe to access previous
}
// Or use operation check
if (current.operation() != 'insert' && previous) {
// previous is available
}Changes Not Detected
Symptom: .changes() returns false when field changed Cause: Field set to same value, or changes made after query Solution:
// Log actual values for debugging
gs.info('Current state: ' + current.state + ', Previous: ' + (previous ? previous.state : 'null'));
gs.info('Changes: ' + current.state.changes());
// Use condition field for reliable change detection
// Filter: stateCHANGESExamples
Example 1: Auto-Assignment Based on Category
(function executeRule(current, previous) {
// Exit early if category hasn't changed
if (!current.category.changes()) {
return;
}
// Assignment mapping
var categoryAssignment = {
'network': 'Network Team',
'hardware': 'Desktop Support',
'software': 'Application Support',
'database': 'DBA Team'
};
var category = current.category.toString();
var groupName = categoryAssignment[category];
if (groupName) {
var group = new GlideRecord('sys_user_group');
if (group.get('name', groupName)) {
current.assignment_group = group.sys_id;
gs.info('Auto-assigned to ' + groupName + ' based on category');
}
}
})(current, previous);Example 2: SLA Escalation Warning
(function executeRule(current, previous) {
// Check SLA breach approaching (80% of response time)
var sla = new GlideRecord('task_sla');
sla.addQuery('task', current.sys_id);
sla.addQuery('stage', 'in_progress');
sla.query();
while (sla.next()) {
var percentComplete = sla.percentage.floatValue();
if (percentComplete >= 80 && percentComplete < 100) {
// Approaching breach - escalate
if (current.priority > 1) {
current.priority = parseInt(current.priority) - 1;
current.work_notes = 'Auto-escalated: SLA at ' + Math.round(percentComplete) + '%';
gs.info('SLA escalation for ' + current.number + ' - ' + percentComplete + '%');
}
}
}
})(current, previous);Example 3: Audit Trail for Sensitive Field Changes
(function executeRule(current, previous) {
var sensitiveFields = ['priority', 'assignment_group', 'assigned_to', 'state'];
var changes = [];
for (var i = 0; i < sensitiveFields.length; i++) {
var field = sensitiveFields[i];
if (current[field].changes()) {
changes.push({
field: field,
from: previous ? previous[field].getDisplayValue() : '(empty)',
to: current[field].getDisplayValue()
});
}
}
if (changes.length > 0) {
var auditNote = 'Field Changes:\n';
for (var j = 0; j < changes.length; j++) {
var c = changes[j];
auditNote += '- ' + c.field + ': "' + c.from + '" -> "' + c.to + '"\n';
}
auditNote += '\nChanged by: ' + gs.getUserDisplayName();
current.work_notes = auditNote;
gs.info('Audit trail recorded for ' + current.number);
}
})(current, previous);Related Skills
admin/script-execution- Background script patterns and executionadmin/update-set-management- Track business rule changesdevelopment/script-includes- Reusable server-side librariescatalog/ui-policies- Client-side field control (complement to BRs)security/acl-management- Access control that affects BR execution
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
- 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
- ui-actionsSkill
- 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.

