What It Is
deleteRecord() is a GlideRecord method that permanently removes a specific record from the ServiceNow database while respecting the platform's security model and business logic framework. It solves the fundamental problem of programmatic data deletion in enterprise environments where simply removing rows from tables isn't enough—you need audit trails, permission checks, cascading updates, and business rule execution. This method sits at the intersection of data persistence and business process automation, ensuring that when records disappear, they do so through controlled, auditable channels that maintain referential integrity and trigger appropriate downstream actions.
Architecturally, deleteRecord() executes exclusively on the ServiceNow application server—never in the browser. This server-side execution is non-negotiable because deletion operations require database transaction management, ACL evaluation against the server-side security context, and business rule processing that can't be trusted to client-side JavaScript. When called from client scripts, the method doesn't exist and will throw errors. When called from server-side contexts like Business Rules, Script Includes, or Scheduled Scripts, it leverages the full Java-based GlideRecord implementation that interfaces directly with the MySQL database layer through ServiceNow's ORM abstraction.
Under the hood, ServiceNow processes deleteRecord() calls through a sophisticated workflow that begins with ACL evaluation against delete permissions for both the table and specific record. The platform then executes any 'before delete' Business Rules, allowing for validation, related record cleanup, or abortion of the delete operation. Once business logic approves the deletion, ServiceNow removes the record from the primary table, updates any related records that reference the deleted record (depending on reference field configurations), and creates audit entries in the sys_audit_delete table. Finally, 'after delete' Business Rules execute, enabling cleanup of related data, notifications, or integration callouts that need to know about the deletion.
Without deleteRecord(), you cannot programmatically delete records in a way that respects ServiceNow's security and business logic frameworks. Direct database manipulation bypasses ACLs, business rules, and audit logging—creating data integrity issues and compliance violations. The platform doesn't provide alternative deletion methods at the GlideRecord level; deleteRecord() is the singular API for programmatic record deletion. Manual deletion through the UI essentially calls the same underlying mechanisms, but lacks the automation capabilities required for bulk operations, scheduled cleanup tasks, or integration-driven data lifecycle management. Any serious ServiceNow implementation eventually requires programmatic deletion capabilities for data retention policies, automated workflows, or integration cleanup processes.
Developers use deleteRecord() most frequently in Business Rules for cascading deletions, Script Includes for data cleanup utilities, and Scheduled Scripts for retention policy enforcement. System administrators typically avoid direct deleteRecord() usage but rely on it indirectly through data archiving tools and bulk deletion utilities. Enterprise architects design deletion patterns around deleteRecord() when building data lifecycle management strategies, particularly for high-volume transactional data like audit records, import logs, or temporary workflow data that needs automated cleanup to prevent database bloat.
The method relates closely to deleteMultiple(), which deletes all records matching a GlideRecord query rather than a single specific record, though deleteMultiple() bypasses business rules for performance reasons. It also connects to the broader update() and insert() operations as part of the complete CRUD operation suite, sharing the same transaction management and business rule execution patterns. Understanding deleteRecord() behavior is essential for designing effective Business Rules, since deletion operations often trigger cascading updates or cleanup operations that need careful orchestration to avoid performance issues or infinite recursion scenarios.
How It Works Under the Hood
When you call deleteRecord() on a GlideRecord instance, ServiceNow's Java-based application server initiates a complex sequence of security checks, business logic execution, and database operations. The platform first validates that the current user context has delete permissions on the target table through ACL evaluation, then checks any record-level security constraints like data visibility rules or field-level ACLs that might prevent deletion. This permission checking happens entirely server-side using the session's security context, which is why client-side code cannot directly execute deletion operations—the browser lacks the authenticated server session required for ACL evaluation.
Once security validation passes, ServiceNow loads the complete record data into memory and begins business rule processing, starting with any Business Rules configured for 'before delete' operations on the target table. These Business Rules execute with full access to the current record data through the current object, allowing for validation logic, related record cleanup, or abortion of the deletion process via current.setAbortAction(true). After business rule execution completes successfully, the platform commits the actual database deletion, updates any reference fields in related records, creates audit log entries, and finally executes 'after delete' Business Rules that can perform cleanup operations but cannot access the deleted record's data since it no longer exists in the database.
The entire deletion operation executes within a database transaction that can roll back if any step fails, ensuring data consistency even when complex business logic or cascading operations encounter errors. ServiceNow's transaction management automatically handles rollback scenarios, though developers need to understand that 'after delete' Business Rules execute after the transaction commits, meaning their failures won't reverse the deletion but may leave related data in inconsistent states that require separate error handling.
The Delete Request Lifecycle
- ACL Evaluation: ServiceNow checks delete permissions against the target table and specific record using the current session's security context, including role-based access controls and conditional ACL scripts.
- Record Loading: The platform loads the complete record data into the GlideRecord object, making all field values available to subsequent business logic processing.
- Before Delete Business Rules: Any Business Rules with 'delete' operation and 'before' timing execute with access to the
currentobject containing the record data, allowing validation or abortion of the delete operation. - Database Deletion: ServiceNow removes the record from the primary table within a database transaction, ensuring atomicity with related operations like reference field updates.
- Reference Field Updates: Related records with reference fields pointing to the deleted record get updated based on their reference field configuration (nullified, cascaded, or protected).
- Audit Logging: The platform creates entries in the
sys_audit_deletetable with the deleted record's data and metadata about the deletion operation. - After Delete Business Rules: Any Business Rules with 'delete' operation and 'after' timing execute, but without access to the deleted record data since the database transaction has already committed.
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.
The Core Pattern
deleteRecord() only works in server-side scripts. Client-side code must use GlideAjax or other server communication methods to trigger deletion operations.
function onSubmit() {
// Client-side validation before triggering server deletion
if (g_form.getValue('state') == '7' && g_form.getValue('close_code') == 'duplicate') {
// Use GlideAjax to call server-side deletion logic
var ga = new GlideAjax('IncidentCleanupUtils');
ga.addParam('sysparm_name', 'deleteRelatedRecords');
ga.addParam('sysparm_incident_sys_id', g_form.getUniqueValue());
// Asynchronous call to avoid blocking UI thread
ga.getXMLAnswer(function(answer) {
if (answer == 'success') {
g_form.addInfoMessage('Related records cleaned up successfully');
} else {
g_form.addErrorMessage('Failed to clean up related records: ' + answer);
}
});
}
}var IncidentCleanupUtils = Class.create();
IncidentCleanupUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
deleteRelatedRecords: function() {
var incidentSysId = this.getParameter('sysparm_incident_sys_id');
try {
// Delete related task records that are no longer needed
var taskGR = new GlideRecord('sc_task');
taskGR.addQuery('parent', incidentSysId);
taskGR.query();
while (taskGR.next()) {
// deleteRecord() respects ACLs and triggers business rules
if (taskGR.canDelete()) {
taskGR.deleteRecord(); // Single record deletion with full audit trail
}
}
return 'success';
} catch (e) {
gs.error('Failed to delete related records: ' + e.getMessage());
return 'error: ' + e.getMessage();
}
},
type: 'IncidentCleanupUtils'
});Real-World Scenarios
Automated Cleanup of Expired Approval Records
Service catalog requests often generate approval records that become obsolete when requests are cancelled or rejected. A scheduled script needs to identify and delete these orphaned approval records to prevent database bloat while maintaining proper audit trails.
(function() {
// Find approval records for cancelled requests older than 30 days
var approvalGR = new GlideRecord('sysapproval_approver');
approvalGR.addQuery('sysapproval.state', 'cancelled');
approvalGR.addQuery('sys_created_on', '<', gs.daysAgoStart(30));
approvalGR.addQuery('state', 'requested'); // Still pending approvals
approvalGR.query();
var deleteCount = 0;
var errorCount = 0;
while (approvalGR.next()) {
try {
// deleteRecord() ensures proper audit logging and business rule execution
approvalGR.deleteRecord();
deleteCount++;
// Prevent timeout on large datasets
if (deleteCount % 100 == 0) {
gs.info('Deleted ' + deleteCount + ' obsolete approval records');
}
} catch (e) {
errorCount++;
gs.error('Failed to delete approval record ' + approvalGR.sys_id + ': ' + e.getMessage());
}
}
gs.info('Approval cleanup complete: ' + deleteCount + ' deleted, ' + errorCount + ' errors');
})();Watch for ACL restrictions that might prevent deletion of approval records owned by other users. The script runs in system context, but approval workflows often have complex security rules. Consider batching operations to avoid transaction timeouts on large datasets, and always include error handling since approval records may have foreign key constraints that prevent deletion.
Cascade Deletion of Child Records in Business Rules
When a project record gets deleted, all associated task assignments and project documentation should be automatically removed. A Business Rule handles this cascading deletion to maintain data integrity and prevent orphaned child records.
(function executeRule(current, previous /*null when async*/) {
// Runs on 'before delete' to clean up child records first
var projectSysId = current.sys_id.toString();
try {
// Delete project task assignments
var assignmentGR = new GlideRecord('project_task_assignment');
assignmentGR.addQuery('project', projectSysId);
assignmentGR.query();
while (assignmentGR.next()) {
// Each deleteRecord() call respects security and triggers business rules
assignmentGR.deleteRecord();
}
// Delete project documentation
var docGR = new GlideRecord('project_documentation');
docGR.addQuery('project', projectSysId);
docGR.query();
while (docGR.next()) {
docGR.deleteRecord();
}
gs.info('Cleaned up child records for deleted project: ' + projectSysId);
} catch (e) {
gs.error('Failed to clean up project child records: ' + e.getMessage());
// Don't abort the parent deletion - log error and continue
}
})(current, previous);Use 'before delete' timing to ensure child records are removed before the parent deletion commits. Avoid infinite recursion by ensuring child record deletions don't trigger business rules that might delete the parent record again. Consider performance implications when deleting large numbers of child records—you might need asynchronous processing for high-volume scenarios.
Conditional Record Deletion Based on Integration Status
Customer records imported from external systems should be deleted from ServiceNow when they're marked as inactive in the source system. A Script Include provides safe deletion logic that checks integration status and handles dependent records appropriately.
deleteInactiveCustomers: function(daysInactive) {
// Find customers marked inactive by integration for specified days
var customerGR = new GlideRecord('customer_account');
customerGR.addQuery('active', false);
customerGR.addQuery('integration_status', 'inactive');
customerGR.addQuery('sys_updated_on', '<', gs.daysAgoStart(daysInactive));
customerGR.query();
var results = {
deleted: 0,
skipped: 0,
errors: []
};
while (customerGR.next()) {
// Check for dependent records that prevent deletion
var hasActiveTickets = this._hasActiveTickets(customerGR.sys_id.toString());
var hasOpenContracts = this._hasOpenContracts(customerGR.sys_id.toString());
if (hasActiveTickets || hasOpenContracts) {
results.skipped++;
continue;
}
try {
// Safe deletion with full business rule processing
customerGR.deleteRecord();
results.deleted++;
} catch (e) {
results.errors.push({
sys_id: customerGR.sys_id.toString(),
error: e.getMessage()
});
}
}
return results;
}Always validate business constraints before calling deleteRecord() to avoid foreign key violations or business rule conflicts. Integration-driven deletions should include status tracking to prevent re-importing deleted records. Consider implementing soft delete patterns for critical business data rather than permanent deletion, especially when dealing with customer or financial records that may need recovery.
The Classic Mistake
Calling deleteRecord() inside a Business Rule that triggers on delete operations creates an infinite recursion loop.
// Business Rule: before delete on incident table
(function executeRule(current, previous /*null when async*/) {
// Trying to cascade delete related records
var taskGr = new GlideRecord('sc_task');
taskGr.addQuery('request_item', current.sys_id);
taskGr.query();
while (taskGr.next()) {
// Log the deletion for audit trail
gs.info('Deleting related task: ' + taskGr.number);
taskGr.deleteRecord(); // This triggers delete Business Rules on sc_task
}
// Also clean up related approvals
var approvalGr = new GlideRecord('sysapproval_approver');
approvalGr.addQuery('sysapproval', current.sys_id);
approvalGr.deleteMultiple(); // Even this can cause issues in complex scenarios
})(current, previous);This fails because deleteRecord() triggers all delete Business Rules on the target table, which may themselves call deleteRecord() on other records. You'll see "Maximum call stack size exceeded" errors in the browser console and transaction timeouts in the system logs. ServiceNow's transaction manager eventually kills the request after consuming massive server resources. In complex environments, this can cascade across multiple tables and bring down instance performance for all users.
// Business Rule: async after delete on incident table
(function executeRule(current, previous /*null when async*/) {
// Use async after delete to avoid recursion
// The original record is already gone, we're just cleaning up
var taskGr = new GlideRecord('sc_task');
taskGr.addQuery('request_item', current.sys_id);
taskGr.query();
while (taskGr.next()) {
gs.info('Deleting related task: ' + taskGr.number);
taskGr.deleteRecord(); // Safe here - original delete is complete
}
// For simple cleanup without Business Rule triggers, use deleteMultiple
var approvalGr = new GlideRecord('sysapproval_approver');
approvalGr.addQuery('sysapproval', current.sys_id);
approvalGr.deleteMultiple(); // Bypasses Business Rules, safer for cleanup
})(current, previous);Never call deleteRecord() in before or after delete Business Rules on the same transaction thread. Use async after delete for cascade operations, or move the logic to a scheduled job.
Performance Rules
- Use
deleteMultiple()instead of loopingdeleteRecord()when deleting over 10 records. Individual deletes create separate database transactions and trigger Business Rules for each record, causing timeouts beyond 50 records and potential instance performance degradation. - Limit
deleteRecord()calls to under 100 per script execution. Each call executes ACL checks, audit logging, and Business Rule processing. Exceeding this limit triggers the "Script exceeded maximum runtime" error and kills the transaction, leaving data in an inconsistent state. - Avoid calling
deleteRecord()on tables with complex Business Rules (more than 5 active rules). Each delete processes all before/after/async rules, creating exponential performance degradation. System administrators will see CPU spikes and transaction log warnings in the node status page. - Check
canDelete()beforedeleteRecord()in loops. Failed delete attempts still consume database resources and generate error log entries. Without this check, users see confusing "Operation failed" messages and administrators get flooded with ACL violation logs. - Use
setWorkflow(false)before bulk deletes over 25 records. Workflow processing adds 200-500ms per record and can trigger workflow context timeouts. Production environments will see workflow queue backlogs and delayed email notifications. - Batch large deletions into chunks of 500 records using
setLimit()and scheduled jobs. Single transactions deleting over 1000 records cause memory exhaustion and database lock contention. Users experience browser freezes and the instance may require restart to clear locked transactions. - Never call
deleteRecord()insideonSubmit()client scripts. Each delete requires a synchronous server round-trip, freezing the browser for 2-5 seconds per record. Forms become unresponsive and users assume the system has crashed. - Disable audit logging with
setUseEngines(false)for cleanup operations deleting over 100 records. Audit entries consume significant database space and processing time. Without this optimization, cleanup jobs run 3-5x slower and generate unnecessarysys_audit_deleterecords.
Side Effects & Platform Behavior
- Triggers all before delete, after delete, and async Business Rules on the target table in order of execution sequence. Display Business Rules are ignored during delete operations.
- Creates audit records in
sys_audit_deletetable if auditing is enabled for the target table, storing complete field values and user context for compliance tracking. - Executes ACL checks against delete operation permissions. If user lacks delete rights, method returns
falseand logs security violation insyslogtable. - Processes active workflow contexts and transitions. Workflows in "Waiting" or "Executing" states are cancelled and their activities are marked as "Cancelled" in the
wf_contexttable. - Updates related records through reference field cascading rules defined in data dictionary. This can trigger additional
deleteRecord()calls or set reference fields to empty on dependent records. - Sends notification emails if notification rules are configured for delete events on the table. Email generation happens asynchronously after the delete completes.
- Fails silently in client-side scripts if called from Service Portal widgets. The record remains in database but no error message displays to the user.
- Removes record from all report data sources immediately, potentially breaking dashboards and scheduled reports that depend on historical data retention.
- Invalidates cached query results and forces re-execution of related list queries on forms viewing records that reference the deleted record.
- Breaks parent-child relationships in hierarchical tables like
cmn_locationorcmdb_ci, requiring manual cleanup of orphaned child records or causing data integrity warnings.
Debugging When It Breaks
The most common failure is the method returning false without any visible error message. Users see the record still exists after attempting to delete it, or forms show "Access denied" messages without explanation. In client scripts, the browser console shows no JavaScript errors, making the failure appear mysterious.
Check System Log → All for ACL violations and script execution errors. Look for entries with source "Security" containing the deleted table name and user information. Business Rule exceptions appear as "Script Error" entries with the specific rule name and line number. Transaction timeouts show as "Database" source entries with "Statement cancelled due to timeout" messages.
Enable session debugging by setting glide.security.enforce_acl.debug=true in system properties to see detailed ACL evaluation logs. For client-side issues, check the Network tab in browser developer tools for failed DELETE requests with 403 status codes. Script execution problems generate entries in the Script Debugger with complete stack traces.
- Verify the GlideRecord points to a valid record:
gr.isValidRecord()should returntrue - Test delete permissions:
gr.canDelete()returnsfalsewhen ACLs block the operation - Check for active workflows: query
wf_contexttable withtable=your_table^id=record_sys_id - Review Business Rules on the table: disable temporarily to isolate which rule causes recursion or errors
- Verify table exists and user has table access:
gs.tableExists('table_name')in background script
Quick Reference
- Always call
query()andnext()beforedeleteRecord()- the GlideRecord must point to a specific record - Method returns
boolean:truefor successful deletion,falsefor ACL denial or script errors - Use
deleteMultiple()for bulk operations - it bypasses Business Rules but respects ACLs and is 10x faster - Client-side
deleteRecord()requiresGlideAjaxcalls to Script Includes - direct deletion not supported - Deleting parent records with
cascade deletereference rules automatically removes child records via separatedeleteRecord()calls - Call
setWorkflow(false)before deletion to skip workflow processing - essential for cleanup scripts - Records in
Import Setsandsys_tables require admin role - regular users cannot delete system records - Deleted records with active
Scheduled JobsorEmail Templatescause those processes to fail with reference errors - Use
gs.nil(gr.sys_id)afterdeleteRecord()to verify deletion - thesys_idbecomes empty on successful deletion - Avoid deleting records in
Display Business Rules- they execute during form loads and cause unexpected data loss