Exporting Service Catalog Items in One Step

//Exporting Service Catalog Items in One Step

Exporting Service Catalog Items in One Step


his post comes in response to a request I received from some of my colleagues at ServiceNow to be able to export a catalog item definition to XML and transport it between instances. I’ve written before about how you can quckly export and import data between ServiceNow instances using the XML export/import context menus. While this works great for a lot of situations, it doesn’t work so great for data that resides in multiple tables, but really makes sense to be exported as a single unit. The components that make up a Catalog item definition are actually stored in over 21 separate tables! It is possible to export a catalog item but you have to do at least 21 separate exports to do it. In this post, I’ll show you how you can set up a UI action to export information from all 21+ tables that make up a catalog item definition…in one click!

Export Full Catalog Item

21+ tables worth of information…in ONE click!

The catalog item export will work for any table extending the ‘sc_cat_item’ table…catalog items, record producers, content items, and order guides! It DOES NOT export associated workflows, delivery plans, or wizard definitions. Workflows can be exported by following the instructions in the Graphical Workflow Export article. The following table information is included.

  • Variables (including question choices)
  • Variable sets (including associated variables, client scripts, and UI policies
  • Client scripts
  • UI policies
  • Additional categories
  • Approved by user and group
  • Available for/Not Available for lists
  • Order guide rule base

The first step is to set up a ‘Processor’ record to handle the export. ServiceNow actually has some built-in code to handle exports like this. You just have to know how to leverage it. Here’s how you would set up a processor for the catalog item export.

‘ExportCatalogItem’ Processor
Name: ExportCatalogItem
Type: Script
Path: export_catalog_item

(function process(g_request, g_response, g_processor) {
    var sysid = g_request.getParameter('sysparm_sys_id');
    gs.log('** Exporting Catalog Item ' + sysid);
    //Name all the related lists
    var exporter = new ExportWithRelatedLists('sc_cat_item', sysid);
    exporter.addRelatedList('item_option_new', 'cat_item');
    exporter.addRelatedList('catalog_script_client', 'cat_item');
    exporter.addRelatedList('catalog_ui_policy', 'catalog_item');
    exporter.addRelatedList('sc_cat_item_category', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_catalog', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_user_criteria_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_user_criteria_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_group_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_group_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_company_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_company_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_dept_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_dept_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_location_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_location_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_user_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_user_no_mtom', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_app_group', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_app_user', 'sc_cat_item');
    exporter.addRelatedList('sc_cat_item_guide_items', 'guide');
    exporter.addRelatedList('pc_vendor_cat_item', 'product_catalog_item');
    var catalogID = '';
    var categoryID = '';
    var vsrelID = '';
    var vsID = '';
    var vID = '';
    var qcID = '';
    var uipID = '';
    var actID = '';
    var csID = '';
    //Query for Catalogs
    var catalog = new GlideRecord('sc_cat_item_catalog');
    catalog.addQuery('sc_cat_item', sysid);
        //Get Catalog IDs
        catalogID = catalogID + ',' + catalog.sc_catalog.sys_id.toString();
    //Query for Categories
    var category = new GlideRecord('sc_cat_item_category');
    category.addQuery('sc_cat_item', sysid);
        //Get Category IDs
        categoryID = categoryID + ',' + category.sc_category.sys_id.toString();
    //Query for variables to get question choices
    var item = new GlideRecord('item_option_new');
    item.addQuery('cat_item', sysid);
        //Query for question choices
        var qc = new GlideRecord('question_choice');
        qc.addQuery('question', item.sys_id.toString());
            //Add the variable question sys_id to the variable question string
            qcID = qcID + ',' + qc.sys_id.toString();
    //Query for ui catalog ui policies to get policy actions
    var catpol = new GlideRecord('catalog_ui_policy');
    catpol.addQuery('catalog_item', sysid);
        //Query for ui policy actions
        var uipact = new GlideRecord('catalog_ui_policy_action');
        uipact.addQuery('ui_policy', catpol.sys_id.toString());
            //Add the ui policy action sys_id to the ui policy action string
            actID = actID + ',' + uipact.sys_id.toString();
    //Query for variable set relationships
    var vsrel = new GlideRecord('io_set_item');
    vsrel.addQuery('sc_cat_item', sysid);
        //Add the item set relationship sys_id to the item set string
        vsrelID = vsrelID + ',' + vsrel.sys_id.toString();
        //Get the variable set
        var vs = vsrel.variable_set.getRefRecord();
            //Add the variable set sys_id to the variable set string
            vsID = vsID + ',' + vs.sys_id.toString();
            //Query for variables in the set
            var v = new GlideRecord('item_option_new');
            v.addQuery('variable_set', vs.sys_id);
                //Add the variable sys_id to the variable string
                vID = vID + ',' + v.sys_id.toString();
                //Query for variable question choices
                var vqc = new GlideRecord('question_choice');
                vqc.addQuery('question', v.sys_id.toString());
                    //Add the variable question sys_id to the variable question string
                    qcID = qcID + ',' + vqc.sys_id.toString();
            //Query for ui policies in the set
            var uip = new GlideRecord('catalog_ui_policy');
            uip.addQuery('variable_set', vs.sys_id.toString());
                //Add the ui policy sys_id to the ui policy string
                uipID = uipID + ',' + uip.sys_id.toString();
                //Query for ui policy actions
                var uipa = new GlideRecord('catalog_ui_policy_action');
                uipa.addQuery('ui_policy', uip.sys_id.toString());
                    //Add the ui policy action sys_id to the ui policy action string
                    actID = actID + ',' + uipa.sys_id.toString();
            //Query for client scripts in the set
            var cs = new GlideRecord('catalog_script_client');
            cs.addQuery('variable_set', vs.sys_id.toString());
                //Add the client script sys_id to the client script string
                csID = csID + ',' + cs.sys_id.toString();
    exporter.addQuerySet('sc_catalog', 'sys_idIN' + catalogID);
    exporter.addQuerySet('sc_category', 'sys_idIN' + categoryID);
    exporter.addQuerySet('io_set_item', 'sys_idIN' + vsrelID);
    exporter.addQuerySet('item_option_new_set', 'sys_idIN' + vsID);
    exporter.addQuerySet('item_option_new', 'sys_idIN' + vID);
    exporter.addQuerySet('question_choice', 'sys_idIN' + qcID);
    exporter.addQuerySet('catalog_ui_policy', 'sys_idIN' + uipID);
    exporter.addQuerySet('catalog_ui_policy_action', 'sys_idIN' + actID);
    exporter.addQuerySet('catalog_script_client', 'sys_idIN' + csID);
})(g_request, g_response, g_processor);

Once you have your processor set up, you just need to call it. The processor above is called by its path name ‘export_catalog_item’ followed by ‘.do’. It also needs to know what to export so you need to pass it the sys_id of the top-level record that you want to export…in this case, the sys_id of the ‘Catalog item’ record.

‘Export Catalog Item’ UI Action
Name: Export Catalog Item
Table: Catalog Item (sc_cat_item)
Order: 200
Form link: True
Show insert: False
Show update: True
Client True
Hint: Export catalog item definition to .xml file
Onclick callExportCatalogItem();
Condition: gs.hasRole(“admin”)

function callExportCatalogItem() {
   var url = new GlideURL('export_catalog_item.do');
   url.addParam('sysparm_sys_id', gel("sys_uniqueValue").value);
   var frame = top.gsft_main;
   if (!frame){
      frame = top;

   frame.location = url.getURL();

Once you’re done with this, you should have an ‘Export Catalog Item’ UI action context menu item on your ‘Catalog Item’ forms that you can use to transport catalog items, record producers, and order guides in the event that they don’t get captured in an update set.

By | 2018-07-09T14:59:55-06:00 January 26th, 2012|Categories: System Definition|Tags: , , , , , |44 Comments

About the Author:


  1. ND January 26, 2012 at 1:45 pm - Reply

    Hi Mark,

    This is cool stuff.

    Can we do same for form as well. Can we export all fields of form from one environment and import it to other environment. So that we don’t need to create fields again and again for each environment ( Other than Update Set). I tried using existing export/import but it is not working, I can’t see the fields when I try to personalize variable in form at new environment.


    • Mark Stanger January 26, 2012 at 2:43 pm - Reply

      It’s probably possible to do for forms as well, but it’s best to use update sets to manage that process.

  2. ND January 26, 2012 at 3:02 pm - Reply

    Hi Mark,

    Thanks for reply.

    Yes, I too agree update sets is best method. But there are some situation when we didn’t enabled the update sets and added the field to form. Now I want to build the application on Dev environment and don’t want to do labor work again.

    Could you please guide a bit on how to make same for form I will try once for testing. Also why normal import and explort is not working?


    • Mark Stanger January 26, 2012 at 3:18 pm - Reply

      The normal import isn’t working because a form is made up of several different pieces. I don’t think there’s a way to export and import the dictionary entries but you might be able to do the form, view, related lists, and form sections by combining the solution in this article with the tables described here.

  3. Abhiram Bharadwaj Diddigi January 26, 2012 at 8:51 pm - Reply

    Very good post,of great help.

  4. John Gilaspy February 22, 2012 at 1:25 pm - Reply

    I’ve been using the Export Workflow function for awhile, and was wondering the following would work to export a UI Policy with its associated Actions:

    //Get the sys_id of the UI Policy
    var sysID = g_request.getParameter(‘sysparm_sys_id’);
    var actID = ”;

    //Query for UI Policy Actions
    var act = new GlideRecord(‘sys_ui_policy_action’);
    act.addQuery(‘ui_policy’, sysID);
    while(act.next()) { actID = actID + ‘,’ + act.sys_id.toString(); }

    //Export UI Policy info to XML
    var exporter = new ExportWithRelatedLists(‘sys_ui_policy’, sysID);
    exporter.addRelatedList(‘sys_ui_policy_action’, ‘sys_ui_policy’);

    if(sysID != ”) { exporter.addQuerySet(‘sys_ui_policy’, ‘sys_id=’ + sysID); }

    if(actID != ”) { exporter.addQuerySet(‘sys_variable_value’, ‘document_keyIN’ + actID); }


    • Mark Stanger February 22, 2012 at 2:27 pm - Reply

      It looks like it should work. Have you tried it out yet?

  5. John Gilaspy February 23, 2012 at 7:31 am - Reply

    No, but I the problem is with the UI Action. I have it set up similar to the old “Export Workflow to XML” UI Action, but I keep getting an “action is undefined” error. If I define action, then I get a “current is undefined error”. Not sure why I get the error with this UI Action, and not the XML one.

  6. John Gilaspy February 23, 2012 at 7:41 am - Reply

    I figured out the errors, but I’m still not getting the right results. The resulting XML file has the UI Policy, but all the UI Policy Actions regardless of Policy. I’ll figure this out one way or another!
    Thanks, Mark!

    • Mark Stanger February 23, 2012 at 7:50 am - Reply

      I didn’t have a chance to test this yesterday but this should work in your processor record to export a UI policy.

      var sysid = g_request.getParameter('sysparm_sys_id');
      gs.log('** Exporting UI Policy ' + sysid);

      var exporter = new ExportWithRelatedLists('sys_ui_policy', sysid);
      exporter.addRelatedList('sys_ui_policy_action', 'ui_policy');
  7. John Gilaspy February 23, 2012 at 8:04 am - Reply

    Unfortunately, everything I try results in every UI Policy Action being included in the resulting XML file. I’ll keep working on it and post what I figure out.

    • Mark Stanger February 23, 2012 at 8:05 am - Reply

      You’re probably not passing the sys_id in correctly from the UI action then. If you post your UI action code here I can take a look.

  8. John Gilaspy February 23, 2012 at 8:09 am - Reply

    Here’s the UI Action I’m using:

    function callExportUIPolicy() {
    var url = new GlideURL(‘export_ui_policy.do’);
    url.addParam(‘sysparm_sys_id’, gel(“sys_uniqueValue”).value);
    var frame = top.gsft_main;
    if (!frame){ frame = top; }
    frame.location = url.getURL();

    Question … fourth line of code in your processor above, should the last string be “sys_ui_policy” ?

    • Mark Stanger February 23, 2012 at 8:19 am - Reply

      That line of code is correct and the code you’ve given me looks okay. I just set this up in demo07 and it tested out fine. Take a look at the UI action and processor record there.

  9. John Gilaspy February 23, 2012 at 8:29 am - Reply

    Will do. Thanks!

  10. John Gilaspy February 24, 2012 at 9:44 am - Reply

    That worked perfectly. I was able to take this and produce the same functionality for exporting Form Sections with Section Elements:

    var sysid = g_request.getParameter(‘sysparm_sys_id’);
    gs.log(‘** Exporting Form Section ‘ + sysid);
    var exporter = new ExportWithRelatedLists(‘sys_ui_section’, sysid);
    exporter.addRelatedList(‘sys_ui_element’, ‘sys_ui_section.sys_id’);

    • Mark Stanger February 24, 2012 at 9:54 am - Reply

      Cool. Thanks for sharing.

  11. John Caruso March 13, 2012 at 6:08 pm - Reply


    I’m trying this out as is on both my instance and a demo instance and I’m noticing that the xml exported includes more items than those related to the specific catalog item. For example on demo16.service-now.com I exported the Install Software catalog item. In the xml I see:

    <question display_value="Is this a replacement for a lost iPhone?">0a3edcd70a0a0b7b0030701f75c2cd1b</question>

    It appears that it’s pulling in *all* questions and not just those defined for the variables of Install Software.

    Is this behavior expected?



    • Mark Stanger March 13, 2012 at 9:10 pm - Reply

      Good catch! It looks like there’s an oddity with the way that the tables are related in this specific case, which causes all of the question choices to be included. I’ve coded a workaround and included it in the Processor script above. Give that a try and let me know how it goes.

      • John Caruso March 14, 2012 at 6:40 pm - Reply


        It’s improved, but I’m still getting items unrelated to the request for these tables:



        • Mark Stanger March 14, 2012 at 10:04 pm - Reply

          I didn’t notice issues with these when I tested on the the Install Software item on demo16. What item can I reproduce these new issues on? Does the item have a variable set?

          • John Caruso March 15, 2012 at 11:29 am - Reply


            It appears to be when an item includes a variable set. On demo16, I added common_comments vs to Add SAN Storage, and now see the unrelated items in the exported xml. I also created a new catalog item: AAA Export Test that is basically empty expect for a (newly created) variable set and see the same issue.


            ps. On demo16 I’d tried to personalize the Catalog Items list (and another list) and both times it showed columns from User table instead. I assume someone tweaked something on this demo instance to cause this behavior, but thought I’d point out anyway…

            • Mark Stanger March 20, 2012 at 7:19 pm

              Okay John, sorry for the delay. I’ve been swamped with a bunch of other projects. I think I’ve got this resolved now though. Give the updated processor code a try and let me know how it goes.

  12. John Gilaspy March 15, 2012 at 7:37 am - Reply

    I did notice when I was having the same issue, was that how I expected the processor to be scripted was actually wrong, in that the fourth line should not have the “sys_” in the second table name. When I removed the “sys_”, I stopped getting all the actions/options, etc, and only got the ones expected for the record in question.


  13. John Caruso March 22, 2012 at 6:43 pm - Reply


    Everything seems to be working well now. Thank you so much for taking the time to fix things.

    I’ve learned quite a bit by reviewing your code and the exported XML.


    • Mark Stanger March 22, 2012 at 7:53 pm - Reply

      Glad to hear it! Thanks for your patience and feedback. I learned a few new things along the way too :).

  14. Justin February 21, 2014 at 11:29 am - Reply

    Hey Mark,

    Thanks for digging in, figuring this stuff out, and then putting the cookies on the lower shelf for the rest of us. Keep up the good work!


  15. Tony Balestreri May 14, 2015 at 5:10 pm - Reply

    Hey Mark,

    Thanks for this! This will save me a lot of time.

    I am currently working on a project of merging 2 separate instances into one. Have you developed something from transfer the actual data from requests (including variables and current workflow stages etc.)?

    Thanks again,

    • Mark Stanger May 14, 2015 at 5:52 pm - Reply

      Thanks Tony. Migrating legacy ticket data is a whole different ballgame unfortunately. As a general rule, I completely discourage it unless it absolutely has to be done. I don’t think that an in-flight migration of service requests is a realistic expectation due to the complexities of the associated workflows. I’d suggest working everything to completion in the legacy system or re-ordering it in the new system rather than attempting to move it over.

  16. Ryan Cox October 26, 2015 at 1:04 pm - Reply


    Is there a way to get this to work from the maintain items list? We do not want to give our catalog developers access to update sets (for multiple reasons) and I wanted to see if this would be a good way for my admins to move multiple catalog items at once.

    • Mark Stanger October 26, 2015 at 1:07 pm - Reply

      I don’t think these export options work unless you have the full ‘admin’ role unfortunately.

  17. Julian Poyntz March 22, 2016 at 6:57 am - Reply

    If you need to export multiple items, you can use this UI Action

    Name Export Catalog Items
    Active Yes
    client Yes
    List Choice Yes
    onclick callExportCatalogItems();
    Condition gs.hasRole(‘admin’)


    function callExportCatalogItems() {

        selSysIds = gel('dbc95612d701210050f5edcb9e61032f').getAttribute('gsft_allow');
        if (selSysIds == null || selSysIds == '')
            selSysIds = g_list.getChecked();

            var arrIDs = selSysIds.split(',');
        if (arrIDs.length &gt; 1)
            var strMsg = 'You are going to export ' + arrIDs.length + ' Items\n';
            strMsg += 'You will see a prompt for each item as it is exported, please click on OK.\n';
            strMsg += 'Without this prompt it will only export one record and not all the records you have selected';
        for (var i=0;i&lt;arrIDs.length;i++)
            var url = new GlideURL(&#039;export_catalog_item.do&#039;);
            alert(&#039;Preparing for item : &#039; + arrIDs[i] + &#039;\nPlease wait until the record has downloaded before you click on OK&#039;);
            var frame = top.gsft_main;
            if (!frame)
                frame = top;

            frame.location = url.getURL();
    • Mark Stanger March 24, 2016 at 5:39 am - Reply


      • Julian Poyntz March 31, 2016 at 7:56 am - Reply

        And if you then want to make all these nice XML files into a single importable XML file, then you can use this hastily VBS routine to do the merger and it will create a combined file called “merged.xml”
        The VBS needs to be in the same folder as the exported XML files

        Const ForReading = 1
        Const ForWriting = 2
        Set objFSO = CreateObject(“Scripting.FileSystemObject”)
        Set outFile = objFSO.CreateTextFile(“merged.xml”, ForWriting)
        intCount = 0
        For Each objFile In objFSO.GetFolder(“.”).Files
        If (UCase(objFile.Name) “MERGED.XML” AND UCase(objFSO.GetExtensionName(objFile.Name)) = “XML”) Then
        Set inFile = objFSO.OpenTextFile(objFile, ForReading)
        Do Until inFile.AtEndOfStream
        strLine = inFile.ReadLine
        if (intCount < 2 OR (Left(strLine,5) “<?xml" AND strLine “” AND Left(strLine, 9) “<unload u")) Then
        outFile.WriteLine strLine
        intCount = intCount + 1
        End if
        End If
        outFile.WriteLine "”

  18. Mary B March 25, 2016 at 1:06 pm - Reply

    Is there a way to pipe this output so that it is a PDF format instead of XML? Thanks. (I’m trying to make a PDF export of all information related to a closed RITM) Thanks for any help.

  19. Marshall Day March 6, 2017 at 5:44 pm - Reply

    Thank you for this amazing aid in transferring catalog items via xml. I am new to Service-Now so my depth of knowledge is growing but not anywhere close to where you are but I did want to ask a question in regards to the scripting. I am testing the processor and UI Action and it appears to work as advertised but I find that certain things do not transfer over and I am wondering if the solution is simply adding additional code to what you already have or am I missing something. My file does not transfer the icon, the picture, or the catalog that it is assigned to in the parent environment.

    Any assistance would be greatly appreciated.

    Marshall Day

    • Mark Stanger March 7, 2017 at 4:19 pm - Reply

      Sorry for the delay in getting back to you on this. It’s been a pretty busy day and I had to make a couple of adjustments to this to fix some things. There isn’t a single, simple answer for how you add additional information because each table can be slightly different. If you’re just dealing with basic information in a related list you can just personalize the list control to get the list ID information necessary to add to the ‘addRelatedList’ section in the script.

      I’ve fixed the catalog issue and added a couple of other tables that weren’t being included. Images unfortunately cannot be included because ServiceNow excludes those in XML exports like this. If you have any other tables that you’re noticing should be included please let me know and I’ll get the script here updated again.

      • Marshall Day March 8, 2017 at 5:17 pm - Reply

        I reloaded the new code and everything works great…except that image part! j/k I was wondering if you had an opportunity to check out Julian Poyntz’s code for exporting multiple catalog items? I took a look and I did see some issues in the posted copy but wanted to know if you had an opinion on it.

        Again, thank you for the great assist in your code from above. You have saved me a lot of time.


        • Mark Stanger March 9, 2017 at 11:43 am - Reply

          I just checked Julian’s code out again but it doesn’t seem to be working. Julian, maybe if you’re listening you could check it out and let us know what the issue might be? Here’s the code I’m using. Seems to iterate through and pause at the alert correctly but only initiates a download of the last item.

          function callExportCatalogItems() {
              selSysIds = gel('dbc95612d701210050f5edcb9e61032f').getAttribute('gsft_allow');
              if (selSysIds == null || selSysIds == '') {
                  selSysIds = g_list.getChecked();
              var arrIDs = selSysIds.split(',');
              if (arrIDs.length > 1) {
                  var strMsg = 'You are going to export ' + arrIDs.length + ' Items\n';
                  strMsg += 'You will see a prompt for each item as it is exported, please click on OK.\n';
                  strMsg += 'Without this prompt it will only export one record and not all the records you have selected';
              for (var i=0; i < arrIDs.length; i++) {
                  var url = new GlideURL('export_catalog_item.do');
                  alert('Preparing for item : ' + arrIDs[i] + '\nPlease wait until the record has downloaded before you click on OK.');
                  var frame = top.gsft_main;
                  if (!frame)
                      frame = top;
                  frame.location = url.getURL();
  20. Dilip Kumar DJ May 29, 2017 at 8:12 am - Reply

    Hi Mark,
    Really helpful article and thanks for sharing this.

    Is there any way by which the active flag of the exported catalog item to be made false.

    Thank you.

    Dilip Kumar

  21. Avi Goli August 8, 2017 at 5:20 pm - Reply


    Once I have the xml hierarchy as string, can I import it in my instance via script?
    var url = new GlideURL(‘upload.do’);
    url.addParam(‘sysparm_referring_url’, ….
    url.addParam(‘sysparm_target’, g_list.getTableName());

    I would like to reuse ServiceNow API (in any list view to import xml file) – but to do via script..
    Where my xml record hierarchy stored in a variavle as string …

    Any idea as how to do this?


  22. Dennis Foster August 31, 2018 at 7:49 am - Reply

    Hi Mark – I’ve visited your pages many times and have benefited from you code, especially this one. Just thought I’d say thanks!


Leave A Comment