What It Is
The insert() method is the definitive way to commit a new record to the ServiceNow database. It's a GlideRecord method that takes the field values you've set on your GlideRecord object and persists them as a new row in the target table, returning the sys_id of the newly created record. This method exists because ServiceNow's data layer sits between your application logic and the underlying MySQL database, translating JavaScript operations into proper SQL inserts while maintaining referential integrity and triggering the platform's extensive automation framework.
Architecturally, insert() executes exclusively on the server side—you'll find it in Business Rules, Script Includes, Scheduled Scripts, and any other server-side scripting context. The browser never directly calls insert(); when users submit forms through the web interface, ServiceNow's form submission process handles the database insertion. This server-side constraint exists for security and data integrity—allowing client-side code to directly manipulate database records would create massive vulnerabilities and bypass the platform's built-in validation and automation layers.
Under the hood, ServiceNow processes insert() calls through its ORM layer, which validates field types, enforces table constraints, and triggers the entire Business Rule lifecycle. The platform first runs any 'before' Business Rules with operation 'insert', then commits the data to MySQL, generates the sys_id, and finally executes 'after' Business Rules and async rules. This entire process happens within a database transaction, so if any step fails, the entire insert rolls back—a critical behavior that prevents partial record creation but can surprise developers who don't account for rollback scenarios in their error handling.
Without insert(), you cannot programmatically create records in ServiceNow from server-side scripts. While REST APIs and form submissions provide alternative pathways for record creation, these mechanisms ultimately rely on the same underlying insert operation. You cannot trigger custom Business Rules with operation 'insert' without calling insert() or its equivalent through the platform's other interfaces. Integration scenarios, automated record creation from workflows, and any server-side logic that needs to create audit trails or related records all depend on this method.
Developers use insert() in Business Rules to create related records, in Script Includes to provide record creation services, and in Scheduled Scripts for bulk data operations. Administrators typically encounter it when copying Business Rules from other instances or when extending existing automation. Architects rely on insert() for building complex data integration patterns, designing audit frameworks, and implementing sophisticated automation that spans multiple tables. The method becomes critical in enterprise implementations where manual record creation doesn't scale and consistent data formatting requires programmatic control.
The insert() method works alongside update() and deleteRecord() to form ServiceNow's complete CRUD operations suite. Unlike insertWithReferences(), which handles reference field resolution more intelligently, insert() requires you to provide valid sys_id values for reference fields rather than display values. It also differs from autoSysFields(false) patterns where you want to bypass automatic field population—insert() always populates system fields unless explicitly prevented.
How It Works Under the Hood
When you call insert() on a GlideRecord object, ServiceNow doesn't immediately write to the database. Instead, it first validates that all field values match their column definitions—checking data types, field lengths, and choice list constraints. The platform then initiates a database transaction and begins executing Business Rules marked as 'before' operations with the 'insert' condition, passing your GlideRecord object through each rule in priority order. During this phase, Business Rules can modify field values, perform additional validation, or even abort the insert entirely by calling current.setAbortAction(true).
After all 'before' rules complete successfully, ServiceNow commits the actual database insertion through its ORM layer, which translates the GlideRecord field values into a SQL INSERT statement. The database assigns the new record a sys_id (unless you explicitly set one), populates audit fields like sys_created_on and sys_created_by, and returns control to the application layer. At this point, the insert() method captures the newly assigned sys_id and updates your GlideRecord object with the complete record data, including any calculated fields or default values applied by the database.
The final phase executes 'after' Business Rules synchronously, followed by 'async' rules that run outside the main transaction. After Business Rules have access to the complete record, including the sys_id and all populated fields, making them ideal for creating related records or triggering workflows. If any step in this entire process fails—from validation through the final Business Rule—ServiceNow rolls back the entire transaction, leaving no trace of the attempted insert. This transactional behavior means that insert() either succeeds completely or fails completely, with no partial record creation possible.
The Request Lifecycle
- Field validation occurs first—ServiceNow checks data types, field lengths, mandatory field requirements, and choice list constraints against your GlideRecord object's current values.
- Database transaction begins, locking resources and establishing rollback capability for the entire insert operation.
- Before Business Rules execute in priority order, with full read-write access to the GlideRecord object and the ability to abort the operation.
- SQL INSERT statement executes against the MySQL database, with ServiceNow's ORM layer handling the translation from GlideRecord fields to database columns.
- System fields populate automatically (
sys_id,sys_created_on,sys_created_by) and the database returns the new record's complete data to ServiceNow. - After Business Rules execute with access to the complete, committed record, including the final
sys_idand all populated fields. - Transaction commits, making all changes permanent, then async Business Rules execute outside the transaction scope for operations like notifications and workflow triggers.
- The
insert()method returns thesys_idof the successfully created record to your calling script.
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
// Client-side: trigger server-side record creation via AJAX
// Cannot call insert() directly from browser - security restriction
function createRelatedTask() {
// Collect data needed for server-side processing
var incidentId = g_form.getUniqueValue();
var priority = g_form.getValue('priority');
// Call server-side Script Include via GlideAjax
var ga = new GlideAjax('IncidentTaskHandler');
ga.addParam('sysparm_name', 'createTask');
ga.addParam('sysparm_incident_id', incidentId);
ga.addParam('sysparm_priority', priority);
ga.getXMLAnswer(function(answer) {
// Handle response from server-side insert operation
var result = answer;
if (result) {
g_form.addInfoMessage('Task created: ' + result);
}
});
}// Server-side: actual insert() execution with proper error handling
var IncidentTaskHandler = Class.create();
IncidentTaskHandler.prototype = Object.extendsObject(AbstractAjaxProcessor, {
createTask: function() {
// Always validate parameters from client-side calls
var incidentId = this.getParameter('sysparm_incident_id');
var priority = this.getParameter('sysparm_priority');
if (!incidentId) return '';
// Create new GlideRecord for target table
var task = new GlideRecord('sc_task');
task.initialize(); // Sets up new record with defaults
// Set required fields before calling insert()
task.short_description = 'Follow-up task for incident';
task.assigned_to = gs.getUserID(); // Current user
task.priority = priority || '3';
// insert() returns sys_id if successful, null if failed
var taskId = task.insert();
return taskId; // Return to client-side callback
},
type: 'IncidentTaskHandler'
});Real-World Scenarios
Auto-Creating Approval Records When Request Items Exceed Budget Threshold
When catalog request items exceed a predefined budget threshold, the system needs to automatically create approval records and assign them to the appropriate financial approvers. This Business Rule fires on catalog request item insertion and creates the necessary approval workflow records without requiring manual intervention.
// Runs after insert on sc_req_item table
// Creates approval records for high-value items
(function executeRule(current, previous) {
// Check if item price exceeds approval threshold
var itemPrice = parseFloat(current.price) || 0;
var approvalThreshold = 1000; // Company policy: $1000+ needs approval
if (itemPrice >= approvalThreshold) {
// Create approval record in sysapproval_approver table
var approval = new GlideRecord('sysapproval_approver');
approval.initialize();
// Link approval to the request item
approval.sysapproval = current.request; // Parent request sys_id
approval.source_table = 'sc_req_item';
approval.document_id = current.sys_id;
// Set approval details
approval.approver = current.requested_for.manager; // Get manager
approval.state = 'requested'; // Initial approval state
approval.comments = 'Auto-created for budget approval: $' + itemPrice;
var approvalId = approval.insert();
gs.log('Created approval record: ' + approvalId + ' for item: ' + current.number);
}
})(current, previous);Watch for null reference fields—if the requester has no manager, the approval creation will fail silently. Always validate reference field values before using them in insert() operations. The sysapproval_approver table has strict requirements for the document_id and source_table fields that must match exactly.
Bulk Creating CI Records from Discovery Import
A scheduled script processes discovery data from an external CMDB system, creating Configuration Item records in ServiceNow for newly discovered servers. The script handles duplicate detection and ensures each CI has proper categorization and ownership assignment based on the imported data.
// Processes staging table data into cmdb_ci_server records
// Runs nightly to sync with external discovery tools
var stagingGR = new GlideRecord('u_discovery_staging');
stagingGR.addQuery('u_processed', false);
stagingGR.addQuery('u_record_type', 'server');
stagingGR.query();
while (stagingGR.next()) {
// Check if CI already exists to prevent duplicates
var existingCI = new GlideRecord('cmdb_ci_server');
existingCI.addQuery('serial_number', stagingGR.u_serial_number);
existingCI.query();
if (!existingCI.hasNext()) {
// Create new server CI record
var serverCI = new GlideRecord('cmdb_ci_server');
serverCI.initialize();
// Map staging data to CI fields
serverCI.name = stagingGR.u_hostname;
serverCI.serial_number = stagingGR.u_serial_number;
serverCI.ip_address = stagingGR.u_ip_address;
serverCI.operational_status = '1'; // Operational
serverCI.install_status = '1'; // Installed
// Set ownership based on network segment
serverCI.assigned_to = getOwnerByNetwork(stagingGR.u_ip_address);
var newCIId = serverCI.insert();
if (newCIId) {
// Mark staging record as processed
stagingGR.u_processed = true;
stagingGR.u_ci_sys_id = newCIId;
stagingGR.update();
gs.log('Created CI: ' + newCIId + ' for server: ' + stagingGR.u_hostname);
}
}
}Bulk operations like this can hit governance limits if processing too many records in a single execution. Consider batching with setLimit() on your query to process records in manageable chunks. The CMDB has referential integrity rules that can cause insert() failures if required reference fields like assigned_to contain invalid sys_user references.
Creating Audit Trail Records for Security-Sensitive Changes
When users modify security-sensitive fields on user records, the system needs to create detailed audit records that capture not just what changed, but the business context and approval status. This Business Rule creates comprehensive audit trails that satisfy compliance requirements for user access modifications.
// Runs after update on sys_user table
// Creates detailed audit records for security-sensitive changes
(function executeRule(current, previous) {
// Define fields that require audit trail creation
var auditFields = ['active', 'locked_out', 'failed_attempts', 'roles'];
var changedFields = [];
// Check which sensitive fields were modified
auditFields.forEach(function(fieldName) {
if (current.getValue(fieldName) != previous.getValue(fieldName)) {
changedFields.push(fieldName);
}
});
// Create audit record if sensitive fields changed
if (changedFields.length > 0) {
var auditRecord = new GlideRecord('u_security_audit');
auditRecord.initialize();
// Link to the modified user record
auditRecord.u_target_user = current.sys_id;
auditRecord.u_target_table = 'sys_user';
auditRecord.u_changed_by = gs.getUserID();
// Build detailed change description
var changeDetails = 'Modified fields: ' + changedFields.join(', ');
auditRecord.u_change_description = changeDetails;
auditRecord.u_business_justification = gs.getSession().getProperty('business_justification') || 'Not provided';
var auditId = auditRecord.insert();
gs.log('Security audit created: ' + auditId + ' for user: ' + current.user_name);
}
})(current, previous);Session properties used for business justification can be manipulated by savvy users. Consider requiring approval workflow integration rather than relying on client-provided justification data.
The Classic Mistake
Calling insert() without first initializing all mandatory fields leads to mysterious database constraint violations.
// Business Rule trying to create a related record
var gr = new GlideRecord('incident');
gr.short_description = 'Auto-generated incident';
gr.description = 'System detected an issue';
gr.caller_id = current.requested_for;
// Looks complete, right? Wrong.
var sysId = gr.insert();
if (sysId) {
gs.info('Created incident: ' + sysId);
} else {
gs.error('Failed to create incident');
// This will run, but you won't know why
}This fails because state and impact are mandatory on the incident table but weren't set. ServiceNow's database layer rejects the insert with a constraint violation, but insert() silently returns null instead of throwing an exception. You'll see "Database constraint violation" in System Log > All, but your script continues executing as if nothing happened. The real kicker is that some mandatory fields have default values in the dictionary, so this works in dev but breaks in production where admins have modified field configurations.
// Always initialize with newRecord() and set ALL mandatory fields
var gr = new GlideRecord('incident');
gr.newRecord(); // This sets default values from dictionary
gr.short_description = 'Auto-generated incident';
gr.description = 'System detected an issue';
gr.caller_id = current.requested_for;
gr.state = 1; // New
gr.impact = 3; // Low
gr.urgency = 3; // Low
var sysId = gr.insert();
if (sysId) {
gs.info('Successfully created incident: ' + sysId);
} else {
gs.error('Insert failed - check mandatory fields');
}Always call newRecord() before setting values, and check the table's Dictionary to identify mandatory fields before writing insert code.
Performance Rules
- Never call
insert()inside a loop over more than 10 records. Each insert triggers a full Business Rule cascade and database round-trip. Over 50 inserts in a single script execution will trigger script timeout warnings and potentially crash client browsers. - Avoid
setWorkflow(true)beforeinsert()unless absolutely necessary. Workflow execution adds 2-5 seconds per insert and can cause transaction deadlocks when inserting more than 5 records rapidly. Admins will receive Performance Analytics alerts about slow transactions. - Always use
autoSysFields(false)when inserting historical data or system records. The default behavior queries Active Directory for user info on everysys_created_byfield, adding 500ms+ per insert and potentially timing out LDAP connections. - Use
setMultipleUpdate(true)when inserting more than 20 records in a batch operation. Without this, each insert fires separate audit trail entries and notification emails. With 100+ inserts, you'll flood thesys_audittable and crash email servers. - Never call
insert()from a before Business Rule on the same table. This creates recursive insert loops that will consume all available database connections within 30 seconds and require platform restart to resolve. UsesetAbortAction(true)instead to prevent the original insert. - Always check the return value of
insert()before using it in subsequent operations. Failed inserts returnnull, and passingnullto reference field assignments can corrupt related records. This manifests as broken links in lists and form views that require database cleanup scripts to fix. - Disable Business Rules with
setUseEngines(false)only for data migration scripts inserting more than 1000 records. Each Business Rule evaluation adds 50-200ms overhead, but disabling engines also disables field auto-population, approval workflows, and SLA calculations that users expect. - Wrap bulk insert operations in
gs.beginTransaction()andgs.commitTransaction()when inserting related records across multiple tables. Without explicit transactions, partial failures leave orphaned records that violate business logic and require manual cleanup through background scripts.
Side Effects & Platform Behavior
- Triggers all active Business Rules with 'insert' operation in this order: before rules, database insert, after rules, async rules. Each rule can modify field values or abort the entire operation.
- Creates entries in
sys_audittable for every field with audit enabled,sys_update_setentries if the table is in scope, andsys_history_linerecords for activity stream display. - Evaluates ACL rules with 'create' operation and can silently fail if the current user lacks insert permissions. The record appears created to your script but isn't actually written to the database.
- Automatically populates
sys_created_on,sys_created_by,sys_updated_on, andsys_updated_bysystem fields unlessautoSysFields(false)is called first. - Fires all matching Notification rules, which can send emails, SMS, or push notifications to users. These notifications run asynchronously but count against your instance's email quotas.
- Initiates Workflow contexts if
setWorkflow(true)was called or if triggered from a form submission. Workflow activities appear in the record's activity stream and create entries inwf_contexttable. - Updates related list counts and reference field displays across all forms and lists currently open by other users. This can cause momentary UI flickers in their browsers as the counts refresh.
- Breaks if called from client-side scripts (Client Scripts, UI Policies) because
GlideRecordinsert operations require server-side execution context. UseGlideAjaxto call a Script Include instead. - Fails silently when mandatory fields are missing, returning
nullinstead of throwing exceptions. Error details only appear in System Log > All with "Database constraint violation" messages. - Increments Performance Analytics counters for table operations and transaction duration. Excessive insert operations trigger automated alerts to system administrators about potential performance issues.
Debugging When It Breaks
When insert() fails, you'll typically see one of three symptoms: the method returns null instead of a sys_id, users report that records aren't appearing where expected, or background scripts hang indefinitely without completing. The most insidious failures happen when ACL restrictions silently prevent the insert—your code thinks it succeeded, but no database write actually occurred.
Always start debugging at System Log > All, filtering by the timestamp when your script ran. Look for entries containing "Database constraint violation", "ACL Deny", or "Mandatory field missing". These messages pinpoint exactly which field or permission caused the failure. The Script Debugger won't catch insert() failures because they're database-level rejections, not JavaScript exceptions.
Performance issues manifest as browser timeouts, "Script execution time exceeded" warnings, or users reporting that the platform feels slow. Check System Log > Performance for entries showing transaction times over 30 seconds. If you see multiple insert() operations in a loop, each with Business Rule cascades, you've found your bottleneck. Quick diagnostic checklist:
- Check if
insert()returnsnull- if yes, examine System Log for constraint violations - Verify all mandatory fields are set by checking Table > Dictionary for required=true entries
- Test with
gs.getUser().hasRole('admin')to rule out ACL restrictions - Look for active before Business Rules that might call
setAbortAction(true) - Check if you're in a recursive loop by counting
gs.info()debug messages
Quick Reference
- Always call
newRecord()before setting field values to ensure dictionary defaults are applied - Check return value: successful
insert()returns 32-character sys_id string, failures returnnull - Use
setWorkflow(false)for bulk operations to avoid workflow timeouts and approval complications - Business Rules with 'insert' operation fire in order: before, database write, after, async rules
- Never use
insert()inside before Business Rules on the same table to avoid infinite recursion - Use
autoSysFields(false)when importing historical data to preserve original timestamps - Failed inserts due to ACL restrictions don't throw errors but return
nulland log "ACL Deny" messages - Use
setMultipleUpdate(true)for batch operations to prevent audit table overflow and notification spam - Debug failures in System Log > All by searching for "constraint violation" or "mandatory field" errors
- Reference fields must contain valid sys_ids from target tables or
insert()will fail with foreign key constraint violations