ServiceNow Catalog Development Mastery
Complex Variable Structures
Beyond Single Questions
Standard variables are great for collecting single pieces of information, like a user's name or a desired software title. But what happens when you need to gather sets of related data? Imagine an onboarding request for not one, but twenty new employees. Or a request to open a dozen different firewall ports, each with its own protocol and destination IP. Creating individual variables for each item would create a clunky, unmanageable form.
This is where ServiceNow's more advanced variable structures come into play. They allow you to collect structured, multi-row data within a single request, creating a clean user experience while providing powerful, machine-readable data on the backend. We'll focus on two primary tools for this: the Multi-Row Variable Set (MRVS) and the List Collector.
The Multi-Row Variable Set
A Multi-Row Variable Set, or MRVS as it's commonly known, acts as a mini-table on your catalog item form. You define the columns by adding standard variables to the set, and the user can then add as many rows as they need. This is ideal for scenarios where you need to collect multiple instances of the same type of information.
While powerful, an MRVS has limitations on which variable types it can contain. The goal is to keep the data structured and simple. You can't, for example, nest a List Collector inside an MRVS. The same goes for UI-heavy types like UI Macros or custom variables with complex DOM manipulation.
Key Takeaway: The MRVS is your go-to for collecting new, user-entered data in a tabular format.
The most crucial architectural detail to understand is how MRVS data is stored. On the backend, the entire set of rows and columns is saved as a single JSON string array within the MRVS variable itself. This is efficient, but it means you can't query the individual cells directly from a list view. To access the data, you'll need to parse this JSON in your scripts.
The List Collector
The List Collector, often called a slushbucket, serves a different purpose. Instead of collecting new information, it's designed for selecting multiple existing records from a table. Think of adding multiple users from the User [sys_user] table to a group or selecting several configuration items that a change request will affect.
Its classic two-paned interface is highly effective in the platform UI, but can be less intuitive in the Service Portal. Unlike an MRVS, the List Collector stores its value as a comma-separated list of sys_ids of the selected records.
A key attribute for making a List Collector dynamic is ref_qual_elements. This powerful feature allows you to filter the available records in the left-hand list based on the value of another variable on the form. For example, you could have a variable for 'Department', and once the user selects a department, the List Collector could update to show only users from that department. This is achieved by passing the changed variable's sys_id to a script include referenced in the reference qualifier, which then returns an updated filter query.
MRVS vs. List Collector
Choosing between an MRVS and a List Collector comes down to one primary question: Are you collecting new, freeform data, or are you selecting from existing records?
| Feature | Multi-Row Variable Set (MRVS) | List Collector |
|---|---|---|
| Primary Use | Collect new, multi-row data | Select multiple existing records |
| Data Source | User input | A ServiceNow table |
| Data Storage | JSON string array | Comma-separated sys_ids |
| Portal UI | Excellent, modern table interface | Good, but less intuitive than MRVS |
| Platform UI | Functional | Excellent, classic slushbucket |
| Example | Listing new servers to build | Adding existing users to a group |
Scripting and Best Practices
When you need to process the data from an MRVS in a script (like in a Business Rule or Workflow), you must first parse the JSON string. The JSON.parse() method in JavaScript converts the string into a navigable array of objects. Each object in the array represents a row, and its properties correspond to the variables in your set.
// Example: In a Business Rule on the sc_req_item table
(function executeRule(current, previous /*null when async*/) {
// Get the MRVS value (a JSON string)
var mrvsData = current.variables.name_of_your_mrvs;
// Parse the string into a JavaScript array of objects
var rows = JSON.parse(mrvsData);
// Loop through each row submitted by the user
rows.forEach(function(row) {
// Access data from each cell by its variable name
var sourceIP = row.source_ip_variable;
var port = row.port_variable;
gs.info('Processing firewall rule for IP: ' + sourceIP + ' on port: ' + port);
// You can now create records, trigger events, etc.
});
})(current, previous);
Finally, a quick note on ordering. When designing complex forms, managing the Order field on variables can become tedious. A common best practice is to number your variables in increments of 10 (10, 20, 30...). This leaves you plenty of room to insert new variables between existing ones later without having to renumber everything.
You are building a service catalog item for requesting new firewall ports. A user might need to request one port or dozens, each with its own protocol, source IP, and destination IP. Which variable type is best suited for this task?
How is the data submitted through a Multi-Row Variable Set (MRVS) stored on the backend?