What It Is

Prototype is ServiceNow's implementation of JavaScript prototypal inheritance, primarily expressed through the Class.create() method borrowed from the Prototype.js library. This pattern solves the fundamental problem of organizing reusable server-side code by creating constructor functions that can instantiate objects with shared methods and properties. Unlike traditional object-oriented languages, JavaScript uses prototype-based inheritance where objects inherit directly from other objects, and ServiceNow's implementation provides a structured way to define classes with initialize() constructors and method inheritance chains.

Architecturally, this pattern lives within the ServiceNow platform's JavaScript execution environment, specifically in Script Includes stored in the sys_script_include table. The pattern operates at the application layer, sitting between the database layer and user interface components like Business Rules, UI Actions, and REST APIs. Every Script Include that uses Class.create() becomes a reusable class definition that can be instantiated across different execution contexts throughout the platform. This makes it the primary mechanism for building libraries, utilities, and complex business logic that needs to maintain state and behavior.

The prototype pattern relates directly to ServiceNow's server-side JavaScript execution model, where each script runs in a Rhino JavaScript engine with access to platform APIs like GlideRecord and GlideSystem. When you create a prototype-based Script Include, the platform registers your class constructor in the global namespace, making it available for instantiation in any server-side context. The inheritance chain works through JavaScript's native prototype mechanism, where methods defined on the prototype object become available to all instances, and parent class methods can be accessed through the inheritance hierarchy.

You cannot function without prototype-based Script Includes when building complex integrations that require stateful objects, implementing design patterns like Factory or Strategy, or creating reusable libraries that multiple applications need to share. Business scenarios that demand this pattern include multi-step approval workflows where each step maintains context, integration classes that manage external system connections with authentication and error handling, or utility classes that perform complex calculations while maintaining configuration state. Without this pattern, you're limited to procedural scripting with global functions, which becomes unmaintainable in enterprise implementations where code reuse and encapsulation are critical.

Developers primarily manage prototype-based Script Includes, though platform administrators need to understand the pattern for troubleshooting and reviewing custom code. The relationship involves developers writing the class definitions and inheritance hierarchies, while admins control deployment through Update Sets and manage application scoping that affects class visibility. System architects design the overall class structure and inheritance patterns that support the business requirements, particularly when building frameworks that span multiple scoped applications.

Recent ServiceNow releases haven't fundamentally changed the prototype pattern, but scoped application architecture introduced in Helsinki affects how prototype-based classes inherit across application boundaries. In scoped apps, classes defined in one scope can extend classes from parent scopes (like Global), but the reverse isn't true. Vancouver and later releases improved Script Include dependency resolution, making inheritance chains more reliable when classes are defined in different scopes. The ES6 JavaScript features aren't fully supported in server-side scripts, so the Class.create() pattern remains the standard approach for object-oriented programming in ServiceNow.

Where to Find and Configure It

Create and manage prototype-based Script Includes at System Definition > Script Includes where you write the class definition using Class.create() and define methods on the prototype. In Studio, navigate to Server Development > Script Includes to create classes within your scoped application. App Engine Studio provides the same functionality under Logic and automation > Script Includes with a more guided interface for citizen developers.

View existing prototype-based classes in the sys_script_include table, filtering by Script field contains Class.create to identify prototype-based implementations. Test your classes using System Definition > Scripts - Background where you can instantiate objects and test method calls. Check class usage across the platform by searching for the class name in System Diagnostics > Script Execution History to see where and how your classes are being used.

Scoped applications handle prototype inheritance differently than global scope. In scoped apps, classes can inherit from global scope classes, but you must explicitly reference the parent class using the full scope notation. Global scope classes automatically become available to all scoped applications, while scoped classes remain private to their application unless explicitly shared through application cross-scope access rules configured in System Applications > Applications under the Application Cross-Scope Access related list.

How It Works Step by Step

The prototype pattern works by creating a constructor function that ServiceNow registers in the global namespace, making it available for instantiation anywhere in server-side code. When you call Class.create(), ServiceNow creates a function object and sets up its prototype chain. The initialize method becomes the constructor that runs when you create new instances, while other methods defined in the class body become part of the prototype, shared across all instances.

Inheritance works through JavaScript's native prototype chain, where child classes can access parent methods through the prototype hierarchy. When you extend a class, ServiceNow sets up the prototype chain so that method calls first check the instance, then the immediate prototype, then parent prototypes until the method is found. This enables method overriding where child classes can redefine parent methods while still accessing the original implementation through the inheritance chain.

The platform caches Script Include definitions to improve performance, but changes to the class require clearing the cache or restarting the instance for global scope classes. Scoped application classes refresh automatically when the application is updated. Method resolution follows JavaScript's standard prototype lookup, checking the instance first, then walking up the prototype chain until the method is found or the chain ends.

Free Newsletter

Enjoying this? Get one deep-dive per week.

Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.

No spam · Unsubscribe anytime

The Execution Order

  1. ServiceNow loads and evaluates the Script Include, executing the Class.create() call to register the constructor function in the global namespace
  2. The platform sets up the prototype object and copies all method definitions from the class body to the prototype
  3. If extending another class, ServiceNow establishes the prototype chain linking child to parent
  4. When instantiating with new ClassName(), JavaScript creates a new object and sets its prototype to the class prototype
  5. The initialize() method executes as the constructor, receiving any parameters passed to the constructor call
  6. Method calls on the instance trigger prototype chain lookup, checking instance properties first, then prototype methods, then parent class methods
DocumentManagerUtil.js
var DocumentManagerUtil = Class.create();
DocumentManagerUtil.prototype = {
    initialize: function(tableName, sysId) {
        this.table = tableName;
        this.recordId = sysId;
        this.gr = new GlideRecord(tableName);
        this.gr.get(sysId);
    },
    
    attachDocument: function(fileName, content) {
        var attachment = new GlideSysAttachment();
        var attachmentId = attachment.write(this.gr, fileName, 'text/plain', content);
        this._logAttachment(attachmentId, fileName);
        return attachmentId;
    },
    
    _logAttachment: function(attachmentId, fileName) {
        gs.info('Attached document {0} with ID {1} to {2}:{3}', 
               fileName, attachmentId, this.table, this.recordId);
    }
};

Real-World Scenarios

Integration Client with State Management

Your organization needs to integrate with a REST API that requires OAuth authentication, rate limiting, and connection pooling across multiple Business Rules and Scheduled Jobs. The integration must maintain authentication tokens, handle retries, and log all interactions for compliance auditing.

ExternalAPIClient.js
var ExternalAPIClient = Class.create();
ExternalAPIClient.prototype = {
    initialize: function(baseUrl, clientId, clientSecret) {
        this.baseUrl = baseUrl;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.token = null;
        this.tokenExpiry = null;
        this.rateLimitRemaining = 100;
        this.lastRequestTime = 0;
    },
    
    authenticate: function() {
        if (this.token && new GlideDateTime().after(this.tokenExpiry)) {
            return this.token;
        }
        
        var request = new sn_ws.RESTMessageV2();
        request.setEndpoint(this.baseUrl + '/oauth/token');
        request.setHttpMethod('POST');
        request.setRequestBody(JSON.stringify({
            'grant_type': 'client_credentials',
            'client_id': this.clientId,
            'client_secret': this.clientSecret
        }));
        
        var response = request.execute();
        if (response.getStatusCode() == 200) {
            var tokenData = JSON.parse(response.getBody());
            this.token = tokenData.access_token;
            this.tokenExpiry = new GlideDateTime();
            this.tokenExpiry.add(tokenData.expires_in * 1000);
            return this.token;
        }
        throw new Error('Authentication failed: ' + response.getBody());
    },
    
    makeRequest: function(endpoint, method, payload) {
        this._enforceRateLimit();
        var token = this.authenticate();
        
        var request = new sn_ws.RESTMessageV2();
        request.setEndpoint(this.baseUrl + endpoint);
        request.setHttpMethod(method);
        request.setRequestHeader('Authorization', 'Bearer ' + token);
        
        if (payload) {
            request.setRequestBody(JSON.stringify(payload));
        }
        
        var response = request.execute();
        this._updateRateLimit(response);
        this._logRequest(endpoint, method, response.getStatusCode());
        
        return {
            statusCode: response.getStatusCode(),
            body: response.getBody(),
            headers: response.getAllHeaders()
        };
    },
    
    _enforceRateLimit: function() {
        var minInterval = 1000; // 1 second between requests
        var timeSinceLastRequest = new Date().getTime() - this.lastRequestTime;
        if (timeSinceLastRequest < minInterval) {
            gs.sleep(minInterval - timeSinceLastRequest);
        }
        this.lastRequestTime = new Date().getTime();
    },
    
    _updateRateLimit: function(response) {
        var rateLimitHeader = response.getHeader('X-RateLimit-Remaining');
        if (rateLimitHeader) {
            this.rateLimitRemaining = parseInt(rateLimitHeader);
        }
    },
    
    _logRequest: function(endpoint, method, statusCode) {
        gs.info('API Request: {0} {1} returned {2}', method, endpoint, statusCode);
    }
};

Configure this Script Include with Accessible from set to Server scripts and Active checked. Store sensitive credentials in System Properties with type password rather than hardcoding them. Watch for token expiration edge cases where multiple simultaneous requests might trigger redundant authentication calls, and consider implementing a simple mutex using GlideRecord locks on a configuration table.

Workflow Step Factory Pattern

You need to implement a complex approval workflow where different request types require different validation rules, approval hierarchies, and notification patterns. Rather than writing monolithic Business Rules, you want modular step classes that can be combined dynamically based on request attributes.

WorkflowSteps.js
// Base workflow step class
var WorkflowStep = Class.create();
WorkflowStep.prototype = {
    initialize: function(request) {
        this.request = request;
        this.completed = false;
        this.errors = [];
    },
    
    execute: function() {
        try {
            this.validate();
            this.process();
            this.completed = true;
            return { success: true, errors: [] };
        } catch (e) {
            this.errors.push(e.message);
            return { success: false, errors: this.errors };
        }
    },
    
    validate: function() {
        // Override in subclasses
    },
    
    process: function() {
        // Override in subclasses
    }
};

// Budget approval step
var BudgetApprovalStep = Class.create();
BudgetApprovalStep.prototype = Object.extendsObject(WorkflowStep, {
    validate: function() {
        if (!this.request.u_cost || this.request.u_cost <= 0) {
            throw new Error('Valid cost required for budget approval');
        }
        if (!this.request.u_cost_center) {
            throw new Error('Cost center required for budget approval');
        }
    },
    
    process: function() {
        var approver = this._getBudgetApprover(this.request.u_cost, this.request.u_cost_center);
        this._createApprovalTask(approver, 'Budget approval required');
        gs.info('Budget approval step created for {0} amount {1}', 
               this.request.number, this.request.u_cost);
    },
    
    _getBudgetApprover: function(cost, costCenter) {
        if (cost > 10000) {
            return this._getDepartmentHead(costCenter);
        }
        return this._getManager(costCenter);
    }
});

// Security review step
var SecurityReviewStep = Class.create();
SecurityReviewStep.prototype = Object.extendsObject(WorkflowStep, {
    validate: function() {
        if (!this.request.u_access_level) {
            throw new Error('Access level must be specified for security review');
        }
    },
    
    process: function() {
        var securityTeam = this._getSecurityTeam();
        this._createApprovalTask(securityTeam, 'Security review required');
        this._attachSecurityChecklist();
    }
});

Implement step execution in a Business Rule using var stepFactory = { 'budget': BudgetApprovalStep, 'security': SecurityReviewStep } to dynamically instantiate the appropriate step classes. Use a multi-row variable set or choice list to define which steps apply to each request type, then iterate through the required steps in your Business Rule. Watch for step dependencies where one step's output affects another step's validation, and consider implementing a step ordering mechanism using a numeric sequence field on your step configuration records.

⚠️

When extending classes with Object.extendsObject(), the parent class constructor doesn't automatically run. You must explicitly call the parent initialize method in your child constructor if you need parent initialization logic.

Data Transformation Pipeline

Your integration receives data from multiple external systems in different formats (XML, JSON, CSV) and needs to transform them into consistent ServiceNow records. Each data source requires different parsing logic, field mapping rules, and error handling strategies, but the overall transformation pipeline should be consistent.

DataTransformer.js
// Base transformer class
var DataTransformer = Class.create();
DataTransformer.prototype = {
    initialize: function(targetTable, config) {
        this.targetTable = targetTable;
        this.config = config || {};
        this.processedCount = 0;
        this.errorCount = 0;
        this.errors = [];
    },
    
    transform: function(rawData) {
        var parsedData = this.parse(rawData);
        var records = [];
        
        for (var i = 0; i < parsedData.length; i++) {
            try {
                var mappedRecord = this.mapFields(parsedData[i]);
                var validatedRecord = this.validate(mappedRecord);
                records.push(this.createRecord(validatedRecord));
                this.processedCount++;
            } catch (e) {
                this.errorCount++;
                this.errors.push({
                    row: i,
                    data: parsedData[i],
                    error: e.message
                });
            }
        }
        return { records: records, processed: this.processedCount, errors: this.errors };
    },
    
    parse: function(rawData) {
        // Override in subclasses
        throw new Error('parse() method must be implemented');
    },
    
    mapFields: function(sourceData) {
        // Override in subclasses  
        throw new Error('mapFields() method must be implemented');
    },
    
    validate: function(record) {
        // Common validation logic
        if (!record.number && !record.u_external_id) {
            throw new Error('Record must have number or external_id');
        }
        return record;
    }
};

// JSON transformer implementation
var JSONDataTransformer = Class.create();
JSONDataTransformer.prototype = Object.extendsObject(DataTransformer, {
    parse: function(rawData) {
        return JSON.parse(rawData);
    },
    
    mapFields: function(sourceData) {
        return {
            number: sourceData.ticket_id,
            short_description: sourceData.summary,
            description: sourceData.details,
            priority: this._mapPriority(sourceData.severity),
            u_external_id: sourceData.id
        };
    },
    
    _mapPriority: function(severity) {
        var priorityMap = { 'low': '4', 'medium': '3', 'high': '2', 'critical': '1' };
        return priorityMap[severity.toLowerCase()] || '3';
    }
});

Create separate Script Include files for each transformer type (XML, CSV, etc.) that extend the base DataTransformer class. Use Transform Maps or Import Sets for the actual record creation in the createRecord() method to leverage platform capabilities for duplicate detection and field validation. Watch for memory issues when processing large datasets - consider implementing batch processing with configurable batch sizes, and always log transformation statistics for monitoring and troubleshooting.

💡

Use the Abstract Factory pattern by creating a TransformerFactory Script Include that returns the appropriate transformer class based on data type or source system. This makes your integration code more maintainable and testable.

The Classic Mistake

⚠️

Creating prototype methods outside the Class.create() definition breaks inheritance and causes methods to be undefined for child classes.

BadPrototype.js
var BaseProcessor = Class.create();
BaseProcessor.prototype = {
    initialize: function() {
        this.name = 'base';
    },
    processRecord: function(gr) {
        gs.info('Processing: ' + gr.getValue('number'));
    }
};

// This breaks inheritance - added after Class.create()
BaseProcessor.prototype.validateRecord = function(gr) {
    return gr.isValidRecord();
};

var IncidentProcessor = Class.create();
IncidentProcessor.prototype = Object.extendsObject(BaseProcessor, {
    processRecord: function(gr) {
        // validateRecord() will be undefined here
        if (this.validateRecord(gr)) {
            this.processIncident(gr);
        }
    }
});

This fails because ServiceNow's Object.extendsObject() function captures the parent prototype at the moment of inheritance, not dynamically. When you add methods to the prototype after Class.create(), child classes created before that addition won't inherit the new methods. The user sees TypeError: this.validateRecord is not a function errors that seem impossible since the method clearly exists on the parent. This happens because ServiceNow freezes the inheritance chain at the moment Object.extendsObject() executes, making later prototype additions invisible to existing child classes.

GoodPrototype.js
var BaseProcessor = Class.create();
BaseProcessor.prototype = {
    initialize: function() {
        this.name = 'base';
    },
    
    processRecord: function(gr) {
        gs.info('Processing: ' + gr.getValue('number'));
    },
    
    // All methods defined within the original prototype
    validateRecord: function(gr) {
        return gr.isValidRecord();
    },
    
    getRecordType: function() {
        return this.name;
    }
};

var IncidentProcessor = Class.create();
IncidentProcessor.prototype = Object.extendsObject(BaseProcessor, {
    initialize: function() {
        BaseProcessor.prototype.initialize.call(this);
        this.name = 'incident';
    },
    
    processRecord: function(gr) {
        if (this.validateRecord(gr)) {
            this.processIncident(gr);
        }
    }
});
💡

Define ALL prototype methods in the original Class.create() prototype object before any inheritance occurs. Never add methods to prototype after child classes exist.

When to Use This vs Alternatives

Use Class.create() prototype inheritance when you need multiple Script Includes to share common functionality with the ability to override specific methods. This pattern excels when building processor hierarchies, utility class families, or any scenario where you have a clear parent-child relationship between classes that need polymorphic behavior.

Choose Prototypes for Inheritance Hierarchies

Prototypes are the correct choice when you need true inheritance with method overriding, especially for processors that handle different record types but share common validation or transformation logic. Simple object literals and utility functions can't provide the instanceof relationships and polymorphic method calls that prototypes enable. ES6 classes aren't supported in ServiceNow's server-side Rhino environment, making Class.create() the only viable inheritance pattern.

Use Utility Functions for Simple Shared Logic

Choose simple object literal Script Includes with static methods when you need shared utilities without inheritance relationships. If your functions don't need instance state or method overriding, a basic var MyUtils = { formatDate: function(date) {...} } pattern is simpler and performs better than prototype instantiation. This approach also works better with scoped applications since it avoids complex inheritance chains that can break across scope boundaries.

Combine Prototypes with Mixins for Complex Scenarios

Use prototypes as the primary inheritance mechanism alongside mixin objects when you need multiple inheritance behaviors. ServiceNow's Object.extendsObject() can merge multiple objects into your prototype definition, allowing you to compose behavior from various sources while maintaining the core inheritance chain. This pattern works well for adding logging, caching, or audit capabilities to existing processor hierarchies without disrupting the primary class relationships.

Platform Interactions & Side Effects

  • Update Sets capture Script Include prototype definitions as single XML records in sys_remote_update_set, but inheritance relationships aren't explicitly tracked, causing deployment issues when parent classes are missing
  • Script Debugger cannot step into prototype method calls across inheritance chains, making debugging child class method invocations nearly impossible without manual logging
  • Business Rules calling prototype methods create entries in syslog table with generic "Script Include executed" messages that don't identify which prototype method actually ran
  • Scoped applications isolate prototype inheritance, causing Object.extendsObject() failures when parent classes exist in different scopes, even with cross-scope access configured
  • Performance degrades significantly with deep inheritance hierarchies as ServiceNow traverses the prototype chain for each method call, particularly noticeable in Business Rules on high-volume tables
  • ACL scripts using prototype instances fail unpredictably because ACL execution context doesn't guarantee Script Include availability, leading to intermittent access denials
  • Workflow activities instantiating prototype classes hold references in session memory, causing memory leaks in long-running workflows until the workflow context expires
  • Transform Maps calling prototype methods don't log method-level errors to sys_import_log, only generic Script Include failures, making data import troubleshooting extremely difficult
  • Client-side Script Includes cannot use Class.create() prototype patterns, requiring complete rewrites when moving server-side prototype logic to the client
  • Scheduled Jobs executing prototype methods don't inherit the user context properly, causing gs.getUser() and gs.hasRole() calls within prototype methods to return system administrator context instead of intended user

Debugging and Troubleshooting

The most common failure symptom is TypeError: [method] is not a function errors when calling inherited methods, typically appearing in Business Rule execution logs or Transform Map import results. Users see generic "Script error" messages while administrators find cryptic stack traces in System Log > All that reference line numbers in child classes where parent methods should exist. Another common symptom is silent failures where prototype methods simply don't execute, leaving no log entries because ServiceNow's error handling treats undefined method calls as non-fatal in certain contexts like Workflow activities.

Primary debugging locations include System Log > Script Errors for runtime failures and System Definition > Script Includes syntax validation for compilation errors. Enable the glide.script.log_javascript_errors system property to capture detailed prototype instantiation failures that normally get suppressed. The Script Debugger works for testing individual prototype methods through System Definition > Script Debugger, but cannot step through inheritance chains during actual Business Rule or Transform Map execution.

Look for specific error patterns like ReferenceError: [ParentClass] is not defined indicating missing Script Include dependencies, or Object.extendsObject is not a function suggesting scoped application access issues. Debug output showing [object Object] instead of expected prototype method results indicates the inheritance chain broke and you're getting the raw prototype object rather than an instantiated class.

Diagnostic Checklist:

  • Verify parent Script Include exists and is Active in the same scope as child class
  • Test prototype instantiation in Script Debugger: var test = new ChildClass(); gs.info(typeof test.parentMethod);
  • Check Script Include order dependencies in Update Sets - parent must be deployed before children
  • Validate all prototype methods are defined within original Class.create() prototype object, not added later
  • Review Application Scope settings if error mentions cross-scope access violations
  • Check Client callable checkbox - prototype inheritance breaks when enabled inappropriately
  • Examine memory usage in long-running scripts - prototype instantiation in loops causes performance degradation

Quick Reference

  • ServiceNow's Object.extendsObject() performs shallow copying, meaning nested objects in parent prototypes are shared by reference across all child instances
  • Maximum practical inheritance depth is 4 levels before noticeable performance impact in Business Rules on tables with >1000 daily updates
  • Parent constructor initialize() methods don't auto-execute in child classes - must explicitly call ParentClass.prototype.initialize.call(this)
  • Script Include names must match the constructor function name exactly, or Class.create() instantiation fails silently in scoped applications
  • Prototype methods have access to this.constructor property for runtime type checking, but instanceof fails across different ServiceNow execution contexts
  • Transform Maps timeout after 300 seconds when prototype methods contain infinite loops - no intermediate logging occurs until timeout
  • Global scope prototypes can be inherited by scoped applications, but scoped prototypes cannot be extended by global Script Includes
  • Workflow Script activities retain prototype instance references until workflow completion, causing memory accumulation in long-running approval processes
  • Method overriding doesn't support super keyword - must use explicit ParentClass.prototype.methodName.call(this) pattern for calling parent methods
  • Business Rule conditions cannot directly test prototype instance properties - new MyClass().someProperty == 'value' always evaluates false in condition builder