Understanding Client and Server APIs: A Complete Guide






Mastering ServiceNow: A Human-Like Guide to Client and Server APIs


Mastering ServiceNow: A Human-Like Guide to Client and Server APIs

Ever found yourself staring at a ServiceNow form, thinking, “I wish it could do X,” or needing to grab some data from a table without getting lost in database jargon? If so, you’re in the right place! ServiceNow isn’t just a platform; it’s a canvas for customization, and the brushes you use are its APIs. This article will demystify the core of ServiceNow scripting: the Client and Server APIs, particularly focusing on the indispensable GlideRecord and GlideForm.

Forget dry documentation. We’re going to explore these powerful tools with a human touch, providing practical insights, real-world scenarios, and even some tips to ace your next ServiceNow interview. Let’s dive in!

Glide API Overview: Your ServiceNow Scripting Superpower

In the ServiceNow world, “API” stands for Application Programming Interface. Think of it as a predefined set of instructions and tools that allow your custom scripts to interact with the platform’s core functionalities. Instead of writing complex SQL queries to fetch data or directly manipulating UI elements, Glide APIs provide a cleaner, more controlled, and often more secure way to get things done.

ServiceNow developers frequently leverage Glide APIs to:

  • Change Default Behavior: Need a field to be mandatory under certain conditions? API.
  • Customize Functionality: Want to automate a task after a record is updated? API.
  • Interact with the Database: Querying, creating, updating, or deleting records? You guessed it – APIs, specifically GlideRecord.
  • Modify the User Interface: Dynamically showing/hiding fields or sending messages to the user? GlideForm is your friend.

These Glide Classes offer immense flexibility, abstracting away the underlying complexities and allowing you to focus on the business logic. Each API comes packed with a multitude of methods, each designed for a specific operation within the ServiceNow ecosystem.

Client-Side vs. Server-Side: A Tale of Two Scripting Realms

Understanding where your script runs is fundamental. ServiceNow APIs are broadly categorized into Client-Side and Server-Side, each with distinct purposes and execution contexts.

Client-Side (Browser)Server-Side (ServiceNow Instance)
GlideForm (g_form)GlideRecord
GlideUser (g_user)GlideSystem (gs)
GlideAjaxGlideDate
GlideDialogWindowGlideDateTime
GlideListGlideAggregation
GlideMenuGlideElement

Client-Side APIs run directly in the user’s browser. They’re all about enhancing the user experience, dynamically reacting to user input, validating data before submission, and manipulating the form’s appearance. Think instant feedback, hiding fields, or showing alerts. These are typically used in Client Scripts or UI Policies (though UI Policies are often preferred for simpler UI changes).

Server-Side APIs execute on the ServiceNow instance itself. This is where the heavy lifting happens: interacting with the database, performing complex calculations, integrating with external systems, and enforcing business logic. They run in contexts like Business Rules, Script Includes, Fix Scripts, Workflows, and of course, the Script – Background module for testing.

🔧 Troubleshooting Tip: The Client/Server Mismatch

A common beginner mistake (and even for experienced developers on a bad day!) is trying to use a server-side API (like GlideRecord) in a client-side script, or vice-versa. Your browser has no idea what new GlideRecord() means, and your server can’t directly manipulate the user’s browser form with g_form.setValue(). Always remember the context!

💭 Interview Relevance:

The distinction between client-side and server-side scripting is a foundational interview question. Be ready to explain when you would use each and provide examples of APIs for both.

Deep Dive: The Server-Side Powerhouse – GlideRecord

What is GlideRecord and Why You Can’t Live Without It

If you’re doing anything significant with data in ServiceNow, you’re using GlideRecord. It’s hands down the most common and vital API in the platform. At its core, GlideRecord is a special Java class (exposed to you as a native JavaScript class) designed to perform standard database operations directly from your server-side scripts. Think of it as your secure, high-level commander for database interactions.

Here’s why it’s so crucial:

  • No SQL Queries (Directly): You won’t be writing raw SQL. GlideRecord abstracts that away, allowing you to perform CRUD operations using JavaScript methods.
  • Server-Side Execution: It always runs on the ServiceNow server, never in the user’s browser. This ensures data integrity and security.
  • Handles Rows and Columns: It’s designed to interact with tables, allowing you to query for records (rows) and access their field values (columns).

In essence, if you need to fetch, create, modify, or delete records in any ServiceNow table, GlideRecord is your go-to API.

⚠ Important Note on GlideRecord Usage:

Always, always, ALWAYS test your GlideRecord scripts on a non-production instance first! An incorrectly constructed query, especially with `addEncodedQuery`, or a misplaced `update()` or `deleteRecord()` call, can lead to unintended data loss or corruption. Be cautious, be thorough, and validate your queries!

💭 Interview Relevance:

What is GlideRecord? When do you use it? Can you give an example of querying records? These are absolute must-knows for any ServiceNow developer interview.

GlideRecord Architecture: Bridging JavaScript and the Database

The beauty of GlideRecord lies in its abstraction. Instead of you writing complex SQL queries like SELECT * FROM incident WHERE active = TRUE AND priority = 1;, you write JavaScript code. Behind the scenes, GlideRecord translates your JavaScript methods (like addQuery(), query()) into optimized SQL queries that the underlying database understands. This not only simplifies development but also adds a layer of security and performance optimization.

new GlideRecord('incident') literally instantiates an object representing the ‘incident’ table, allowing you to then call methods on it to interact with that table’s data.

Key GlideRecord Methods: Your Data Manipulation Toolkit

Let’s explore some of the most common and critical GlideRecord methods. Remember, to test these, you’ll typically use the “Script – Background” module in ServiceNow (learn more here).

Basic Output for Server-Side Scripts:

Before we dive into GlideRecord, how do you see results from your server-side scripts?

  • gs.print('Your message');: Prints output directly to the text area in Script – Background.
  • gs.info('Your message');: Writes to the system log (syslog). Useful for debugging and seeing output in various server-side contexts like Business Rules.
gs.print('Welcome to ServiceNow Academy');
gs.info('Debugging message: Script started');

Querying Records: Finding What You Need

This is where GlideRecord truly shines. Finding specific records or sets of records is a daily task.

  • new GlideRecord('table_name'): Creates a GlideRecord object for the specified table.
  • query(): Executes the query defined by any preceding addQuery() or addEncodedQuery() methods.
  • addQuery('field_name', 'operator', 'value'): Adds a filter condition. You can chain multiple addQuery() calls.
    var inc = new GlideRecord('incident');
    inc.addQuery('active', true); // Active incidents
    inc.addQuery('priority', '<=', 2); // Priority 1 or 2
    inc.query();
  • addEncodedQuery('active=true^category=software^priority=1'): A powerful way to add multiple conditions as a single string. You can get this string directly from list filters!
    // Step 1: Filter incident list for active=true, category=software, priority=1
    // Step 2: Right-click breadcrumb > Copy query
    // Step 3: Paste into your script:
    var inc = new GlideRecord('incident');
    inc.addEncodedQuery('active=true^category=software^priority=1');
    inc.query();
  • addActiveQuery() / addInactiveQuery(): Convenience methods for addQuery('active', true) and addQuery('active', false) respectively.
    var inc = new GlideRecord('incident');
    inc.addActiveQuery(); // Equivalent to inc.addQuery('active', true);
    inc.addQuery('priority', 1);
    inc.query();
  • while (gr.next()) { ... }: Iterates through the records returned by query(). next() moves to the next record and returns true if there is one, false otherwise. This is crucial for processing multiple results.
  • get('sys_id') or get('field_name', 'value'): Retrieves a single record directly.
    var inc = new GlideRecord('incident');
    // Get by sys_id
    inc.get('447788445778844'); // Assuming valid sys_id
    gs.print('Incident number: ' + inc.number);
    
    // Get by another field (e.g., incident number)
    inc.get('number', 'INC0009005');
    gs.print('Incident sys_id: ' + inc.sys_id);
  • orderBy('field_name') / orderByDesc('field_name'): Sorts the query results.
    var inc = new GlideRecord('incident');
    inc.addActiveQuery();
    inc.orderByDesc('sys_created_on'); // Newest first
    inc.setLimit(5); // Get only the top 5
    inc.query();
    while (inc.next()) {
        gs.print(inc.number + ' - ' + inc.short_description);
    }
  • setLimit(integer): Limits the number of records returned by the query.
  • chooseWindow(first_row, last_row): Selects a subset of records by their row number (first row is included, second is excluded).
  • getRowCount(): Returns the total number of records found by the query.
    var users = new GlideRecord('sys_user');
    users.addQuery('active', true);
    users.query();
    gs.print('Active users found: ' + users.getRowCount());
  • getEncodedQuery(): Returns the encoded query string that was used to build the current GlideRecord query. Useful for debugging or dynamically building queries.
    var inc = new GlideRecord('incident');
    inc.addQuery('active', true);
    inc.addQuery('priority', 1);
    inc.query();
    gs.print('Generated Encoded Query: ' + inc.getEncodedQuery());
  • addNullQuery('field_name') / addNotNullQuery('field_name'): Filters for records where a specific field is empty (null) or not empty.
  • addJoinQuery('joined_table', 'local_field', 'join_field'): For more advanced scenarios, allowing you to join tables in your query.
    // Find problems that have at least one associated incident
    var prob = new GlideRecord('problem');
    prob.addJoinQuery('incident', 'sys_id', 'problem_id'); // Link problem.sys_id to incident.problem_id
    prob.query();
    while(prob.next()){
        gs.print(prob.number);
    }

Creating New Records: The insert() Family

  • initialize(): Prepares a new, empty record object, filling it with default values for the table. It's like opening a blank form.
  • newRecord(): Similar to initialize() but also assigns a unique ID to the record immediately.
  • setValue('field_name', 'value') or gr.field_name = 'value': Sets the value of a specific field.
    var inc = new GlideRecord('incident');
    inc.initialize(); // Start a new record
    inc.category = 'network'; // Set field value directly
    inc.setValue('short_description', 'Critical VPN Issue - Test Record'); // Set value via method
    inc.priority = 1;
    inc.insert(); // Save the new record to the database
    gs.print('New Incident Created: ' + inc.number);
  • insert(): Saves the new record to the database.
  • isNewRecord(): Checks if the current GlideRecord object represents a record that hasn't been saved to the database yet.

Updating Records: The update() Family

  • update(): Saves changes to a single existing record. You must first query and retrieve the record.
    var inc = new GlideRecord('incident');
    inc.get('number', 'INC0000057'); // Get a specific incident
    if (inc.isValidRecord()) { // Always check if the record exists!
        inc.state = 2; // Set state to "Active" or similar
        inc.update(); // Save changes
        gs.print('Incident ' + inc.number + ' updated.');
    } else {
        gs.print('Incident INC0000057 not found.');
    }
  • updateMultiple(): Saves changes to multiple records that match the current query. This is much more efficient than looping through and calling update() for each record if you're making the same change to many.
    var inc = new GlideRecord('incident');
    inc.addQuery('category', 'hardware'); // Find all hardware incidents
    inc.setValue('category', 'software'); // Change their category to software
    inc.updateMultiple(); // Update all matching records
    gs.print('All hardware incidents changed to software category.');
  • autoSysFields(false) / setWorkflow(false): These methods are essential for control.
    • autoSysFields(false): Prevents system fields like sys_updated_on, sys_updated_by, sys_mod_count from being updated. Useful when you're doing a data migration or correction and don't want to mess with historical timestamps. (Note: Not always supported in scoped applications).
    • setWorkflow(false): Prevents Business Rules, Workflow, and other automation from running when the record is updated. Use with extreme caution, as it can bypass critical business logic.
      var inc = new GlideRecord('incident');
      inc.addQuery('state', 1); // Get incidents in New state
      inc.query();
      while (inc.next()) {
          inc.autoSysFields(false); // Don't update system fields
          inc.setWorkflow(false);   // Don't run business rules
          inc.state = 2;            // Set state to Active
          inc.update();
      }
      gs.print('Updated incidents without triggering workflows or system fields.');

Deleting Records: The delete() Family

  • deleteRecord(): Deletes a single record. You must have retrieved the record first.
    var inc = new GlideRecord('incident');
    inc.get('number', 'INC0010013'); // Find the record to delete
    if (inc.isValidRecord()) {
        inc.deleteRecord();
        gs.print('Incident ' + inc.number + ' deleted.');
    } else {
        gs.print('Incident INC0010013 not found for deletion.');
    }
  • deleteMultiple(): Deletes all records that match the current query. Use this with extreme care!
    var inc = new GlideRecord('incident');
    inc.addQuery('priority', 4); // Find all priority 4 incidents
    // ALWAYS add a sanity check here before deleting, e.g., gs.print(inc.getRowCount());
    inc.query();
    inc.deleteMultiple(); // Delete them all!
    gs.print('All priority 4 incidents deleted.');

Utility and Information Methods: Peeking into Records

  • getValue('field_name'): Returns the actual, stored value of a field. For reference fields, this will be the sys_id. For choice fields, it's the backend integer/string value.
    var inc = new GlideRecord('incident');
    inc.get('number', 'INC0000001');
    gs.print('Short Description (Value): ' + inc.getValue('short_description'));
  • getDisplayValue('field_name'): Returns the user-friendly, displayed value of a field. For reference fields, it's the display name. For choice fields, it's the label.
    var inc = new GlideRecord('incident');
    inc.get('number', 'INC0000001');
    gs.print('Priority (Display Value): ' + inc.priority.getDisplayValue());
  • getUniqueValue(): Returns the sys_id of the current record.
    var inc = new GlideRecord('incident');
    inc.query();
    if (inc.next()) {
        gs.print('Unique ID (Sys_id): ' + inc.getUniqueValue());
    }
  • getElement('field_name'): Returns a GlideElement object for the specified field, allowing access to more field properties.
    var inc = new GlideRecord('incident');
    inc.query();
    if (inc.next()) {
        var categoryElement = inc.getElement('category');
        gs.print('Category Label: ' + categoryElement.getLabel());
    }
  • getTableName(): Returns the name of the table associated with the GlideRecord object.
  • isValid(): Checks if the GlideRecord object was successfully initialized with a valid table name.
  • isValidField('field_name'): Checks if a specified field exists on the table.
  • isValidRecord(): Checks if the current GlideRecord object represents an actual record found by a query or get() operation.
  • getRecordClassName(): Retrieves the class name (table name) for the current record.
  • getLink(boolean_encode): Generates a direct URL link to the current record.
    var gr = new GlideRecord("incident");
    gr.query();
    if(gr.next()) {
        gs.print(gs.getProperty('glide.servlet.uri') + gr.getLink(false)); // Full URL
    }
  • canCreate() / canRead() / canWrite() / canDelete(): Checks if the currently logged-in user (or system user if running in a background script) has the necessary ACLs (Access Control Lists) to perform these operations on the current record/table.
    var inc = new GlideRecord('incident');
    gs.print('Can create incidents? ' + inc.canCreate());
    inc.get('number', 'INC0000001');
    if (inc.isValidRecord()) {
        gs.print('Can write to INC0000001? ' + inc.canWrite());
    }
  • setAbortAction(true): Typically used in Business Rules (e.g., a before-insert/update rule) to prevent the current database action from completing. This is often paired with `gs.addErrorMessage()` to inform the user why their action was stopped.
    // Example in a "before" business rule on incident table
    // If short description is 'Urgent', stop the insert/update
    if (current.short_description == 'Urgent') {
        gs.addErrorMessage('Cannot create/update incidents with "Urgent" in short description.');
        current.setAbortAction(true);
    }

💡 Pro Tip: Using Script - Background

The "Script - Background" module is your sandbox for GlideRecord. It allows you to run server-side JavaScript directly without affecting other parts of your instance (unless your script explicitly modifies data, of course!). It's perfect for testing queries, practicing CRUD operations, and understanding API behavior.

Deep Dive: The Client-Side Companion - GlideForm

GlideForm (g_form) Overview and Usage: Interacting with the User Interface

While GlideRecord talks to the database, GlideForm (accessed via the global object g_form) talks directly to the form currently displayed in the user's browser. It's your direct control panel for the UI, enabling you to make real-time changes based on user interactions.

g_form methods are exclusively client-side, meaning they run in the browser and are primarily used within:

Think about dynamic forms: fields appearing or disappearing, becoming mandatory, or displaying validation messages without a page refresh. That's g_form in action.

💭 Interview Relevance:

Another common question: "What is g_form, and how is it different from GlideRecord?" You should be able to explain its purpose (UI manipulation), where it runs (browser), and provide examples of its use.

Essential GlideForm Methods: Crafting Dynamic User Experiences

To test these methods, navigate to any record form (e.g., an Incident), open your browser's developer console (usually F12 or Ctrl+Shift+J), and type your g_form commands.

Getting and Setting Field Values:

  • g_form.getValue('field_name'): Retrieves the current value of a field on the form.
    alert(g_form.getValue('category')); // Pop up an alert with the category value
  • g_form.setValue('field_name', 'value'): Sets the value of a field on the form. This also triggers any onChange Client Scripts for that field.
    g_form.setValue('category', 'hardware'); // Set category to hardware

Controlling Field Properties:

These methods allow you to dynamically change how fields behave and appear to the user.

  • g_form.setMandatory('field_name', true/false): Makes a field mandatory or optional.
    g_form.setMandatory('category', true); // Make Category mandatory
  • g_form.setDisabled('field_name', true/false) or g_form.setReadOnly('field_name', true/false): Makes a field read-only (disabled) or editable.
    g_form.setDisabled('category', true); // Make Category read-only
    g_form.setReadOnly('short_description', true); // Make Short Description read-only
  • g_form.setDisplay('field_name', true/false): Hides or shows a field. When hidden, the field takes up no space.
    g_form.setDisplay('business_service', false); // Hide the Business Service field
  • g_form.setVisible('field_name', true/false): Hides or shows a field, but unlike setDisplay(), it retains the space the field would normally occupy. Useful for maintaining layout when toggling visibility.
    g_form.setVisible('subcategory', false); // Hide subcategory, but keep its space

🛈 Best Practice Note:

For simple UI changes like making fields mandatory, read-only, or visible/hidden, it's generally a best practice to use UI Policies instead of Client Scripts. UI Policies are low-code, declarative, and easier to maintain. Reserve Client Scripts and g_form for more complex logic that UI Policies can't handle.

Messages and Feedback:

  • g_form.addInfoMessage('Your message'): Displays an informational message at the top of the form.
  • g_form.addErrorMessage('Your error message'): Displays an error message at the top of the form.
  • g_form.clearMessages(): Clears all messages displayed by addInfoMessage or addErrorMessage.
  • g_form.showFieldMsg('field_name', 'Your message', 'type'): Displays a message directly below a specific field. Types can be 'info', 'warning', 'error'.
    g_form.showFieldMsg('short_description', 'Please be more descriptive!', 'warning');
  • g_form.hideFieldMsg('field_name'): Hides the message displayed by showFieldMsg().
  • g_form.showErrorBox('field_name', 'Error reason'): Displays an error icon and message next to a field.
  • g_form.flash('field_name', '#FF0000', 0): Makes a field flash with a specified color.

Working with Choice Fields:

  • g_form.addOption('field_name', 'value', 'label', index): Adds a new option to a choice or select box.
  • g_form.removeOption('field_name', 'value'): Removes an option from a choice or select box.
  • g_form.clearOptions('field_name'): Removes all options from a choice or select box.

Other Useful Methods:

  • g_form.getTableName(): Returns the name of the table for the current form.
  • g_form.getUniqueValue(): Returns the sys_id of the record currently displayed on the form.
  • g_form.isNewRecord(): Returns true if the form is displaying a new record (not yet saved to the database).
  • g_form.save(): Saves the current record without submitting or redirecting.
  • g_form.submit(): Submits the current form, bypassing client-side validation. Use with caution.
  • g_form.getControl('field_name') / g_form.getElement('field_name'): Returns the DOM element for a field, allowing more advanced browser-side manipulation if necessary (use sparingly, as it can be less portable).

🔧 Troubleshooting Tip: Client Script Performance

Too many complex g_form operations, especially in onChange scripts, can impact form load times and user experience. Aim for efficiency and consider if a UI Policy or a simpler approach might suffice. Also, remember that g_form can only interact with fields that are actually present on the form's UI.

Client-Side vs. Server-Side API Mapping: A Quick Recap

To solidify your understanding, let's revisit the core distinction:

  • Client-Side APIs (e.g., g_form): Your toolbox for interacting with the user interface, validating input, and providing instant feedback. Runs in the browser.
  • Server-Side APIs (e.g., GlideRecord, gs): Your powerhouse for database operations, business logic, and integrations. Runs on the ServiceNow instance.

They complement each other, with client-side scripts often making lightweight checks and server-side scripts enforcing stricter rules and handling persistent data changes.

Troubleshooting Common Glide API Issues

Even seasoned developers encounter issues. Here's a quick rundown of common problems and how to approach them:

  1. Client vs. Server Mismatch: As discussed, this is #1. If g_form isn't working in a Business Rule, or GlideRecord isn't working in a Client Script, you've mixed your realms.
  2. Syntax Errors: Simple typos, missing semicolons, or incorrect method names. The browser console (for client-side) and system logs (for server-side, using gs.info() or gs.debug()) are your best friends.
  3. ACLs Preventing Operations: Your script might be perfectly valid, but the user running it (or the system if impersonating) doesn't have the necessary permissions. Test with an admin user or check your ACLs carefully.
  4. Query Issues (GlideRecord):
    • No results: Is your query too restrictive? Check field names for typos.
    • Too many results: Is your query not restrictive enough? Forgot an addQuery()?
    • Performance: Complex addEncodedQuery strings or GlideRecord loops with many update() calls can be slow. Consider updateMultiple() or optimizing your query.
    • Forgetting .next(): If you're expecting multiple records but only getting one (or none), you might have forgotten the while (gr.next()) loop.
  5. Undefined Fields (GlideForm): Trying to manipulate a field that isn't on the current form will cause errors. Always verify field names.
  6. Unexpected Behavior (setWorkflow(false)): If your changes aren't triggering expected Business Rules or Workflows, double-check if you've inadvertently used setWorkflow(false).
  7. Debugging Output: Remember gs.print() and gs.info() for server-side debugging, and console.log() for client-side debugging.

Interview Readiness: Mastering Glide APIs

Being comfortable with Glide APIs is a non-negotiable skill for any ServiceNow developer. Here are key concepts and questions you might face:

  • Explain the difference between client-side and server-side scripting.
    • Example: "Client-side runs in the browser, ideal for UI interaction (g_form, g_user). Server-side runs on the instance, ideal for database operations and business logic (GlideRecord, gs)."
  • When would you use GlideRecord versus g_form?
    • Example: "GlideRecord for creating/reading/updating/deleting records in the database. g_form for dynamically changing the user's form view, like hiding fields or adding messages."
  • Walk me through a typical GlideRecord query to find active, high-priority incidents.
    • Be ready to write:
      var grInc = new GlideRecord('incident');
      grInc.addQuery('active', true);
      grInc.addQuery('priority', 1);
      grInc.query();
      while (grInc.next()) {
          gs.print(grInc.number + ' - ' + grInc.short_description);
      }
  • How do you create a new record using GlideRecord?
    • Keywords: initialize(), setting field values, insert().
  • What are some common g_form methods you've used?
    • Keywords: setValue(), setMandatory(), addInfoMessage(), setDisplay().
  • What are the benefits of addEncodedQuery()?
    • Keywords: Combining multiple conditions, easily generated from list filters, performance.
  • When would you use updateMultiple() over looping with individual update() calls?
    • Keywords: Efficiency, performance, applies same change to many records.
  • What's the purpose of setWorkflow(false) and when would you use it?
    • Keywords: Bypassing business rules/workflows, data migrations, careful use.

Conclusion

Congratulations! You've navigated the essential landscape of ServiceNow's Glide APIs. From the server-side power of GlideRecord, which enables you to command database operations, to the client-side finesse of GlideForm, which allows you to sculpt the user interface, these APIs are the bedrock of effective ServiceNow customization.

Remember, theory is just the beginning. The real mastery comes from consistent practice. Head into your developer instance, open up "Script - Background" or create some Client Scripts, and start experimenting. Break things, fix them, and understand why they behave the way they do. The more you code, the more intuitive these powerful tools will become.

Keep exploring, keep building, and keep leveraging the incredible capabilities of the ServiceNow platform!


Scroll to Top