🛡️ Do you want to get rid of ads? Register here — and experience the blog undisturbed. The only things you’ll need are cookies and coffee.

background shape
background shape

Master queryDef in Adobe Campaign

If you need to retrieve records from the database in Adobe Campaign Classic using JavaScript, queryDef is one of the key tools you’ll rely on. It’s a static SOAP method commonly used in workflows and web applications. What’s often overlooked, though, is that there are actually three different ways to use it. In this post, we’ll walk through each approach, highlight their differences, and help you figure out which one makes the most sense for your use case.

Different ways to execute query with queryDef

This method allows you to search data within a specific schema. It requires authentication (a valid login) and an XML document that defines the query you want to run. The response is also in XML format, returning results based on the schema you queried.

In the next section, we’ll look at the different ways you can use this method within Adobe Campaign.

xtk.queryDef.create()

The “older” version uses XML for creating the query definitions. You can take a look at the official data oriented adobe campaign API documentation

Below is a list of available options you can use with queryDef to control how data is queried, filtered and returned:

<queryDef schema="schema_key" operation="operation_type">
  <select>
    <node expr="expression1"/>
    <node expr="expression2"/>
  </select>
  <where>
    <condition expr="expression1"/>
    <condition expr="expression2"/>
  </where>
  <orderBy>
    <node expr="expression1"/>
    <node expr="expression2"/>
   
  </orderBy>
  <groupBy>
    <node expr="expression1"/>
    <node expr="expression2"/>
    
  </groupBy>
  <having>
    <condition expr="expression1"/>
    <condition expr="expression2"/>
 
  </having>
</queryDef>

The operation attribute defines how data should be retrieved, with the following options:

  • get – Fetches a single record. An error is returned if the record isn’t found.
  • getIfExists – Also fetches one record, but returns an empty result instead of an error if nothing is found.
  • select – Retrieves multiple records using a cursor. Returns an empty set if no matches exist.
  • count – Returns the number of records that match the query.

Return values in queryDef

When using queryDef, the structure of the returned data isn’t always the same — it depends on two key factors: the operation you specify (get, select, count, etc.) and the fields or expressions defined in the <select> clause. These determine whether you get a single record, multiple results, or just a count value — and in what XML structure they’re returned.

Below are some examples to illustrate how each operation affects the format of the output.

get

Returns a single record. If no match is found, an error is thrown. It is good to use try catch clause or use operation getIfExists

var query = xtk.queryDef.create(
  <queryDef schema="xtk:workflow" operation="get">
    <select>
      <node expr="@internalName" />
      <node expr="[folder/@name]" />
    </select>
    <where>
      <condition expr="[folder/@name] = 'Folder3208'"/>
    </where>
    <orderBy>
      <node expr="@internalName" sortDesc="false"/>
    </orderBy>
  </queryDef>);
var result = query.ExecuteQuery();

logInfo( result.toXMLString());
//19/06/2025 08:31:42	js	<workflow  internalName="WKF56353"><folder name="Workflows - MSZ"/></workflow>

logInfo(result.@internalName);
//19/06/2025 08:31:42	js	WKF56353

logInfo(result.folder.@name);
//19/06/2025 08:43:58	js	Folder3208
<workflow  internalName="WKF56353"><folder name="Workflows - MSZ"/></workflow>
select

Returns a list of matching records. Each result is a child node under the schema’s root:

var query = xtk.queryDef.create(
  <queryDef schema="xtk:workflow" operation="select">
    <select>
      <node expr="@internalName" />
      <node expr="[folder/@label]" />
    </select>
    <where>
      <condition expr="[folder/@name] = 'Folder3208'"/>
    </where>
    <orderBy>
      <node expr="@internalName" sortDesc="false"/>
    </orderBy>
  </queryDef>);
var result = query.ExecuteQuery(),w;

for each (w in result.workflow)
  logInfo(w.@internalName)
//19/06/2025 08:40:56	js	WKF56353
//19/06/2025 08:40:56	js	WKF56354
//19/06/2025 08:40:56	js	WKF56355
<workflow-collection>   
  <workflow folderLabel="Workflows - MSZ" workflowName="WKF56353"/> 
  <workflow folderLabel="Workflows - MSZ" workflowName="WKF56354"/> 
  <workflow folderLabel="Workflows - MSZ" workflowName="WKF56355"/> 
  ...
</workflow-collection>
count

Returns the number of matching records. The value is wrapped in a <count> element:

var query = xtk.queryDef.create(
  <queryDef schema="xtk:workflow" operation="count">
    <where>
      <condition expr="[folder/@name] = 'Folder3208'"/>
    </where>
  </queryDef>);
var result = query.ExecuteQuery();

logInfo( result.toXMLString());
//19/06/2025 08:49:40	js	<workflow count="79"/>
logInfo(result.@count)
//19/06/2025 08:49:40	js	79

Subqueries

Additionally you can use subqueries in the where conditions:

<condition setOperator="AND">
  <subQuery schema="xtk:recipient">
    <select>
      <node expr="[@recipient-id]"/>
    </select>
    <where>
      <condition setOperator="OR">
        <condition expr="[@status]='active'"/>
        <condition expr="[@status]='pending'"/>
      </condition>
    </where>
  </subQuery>
  <condition setOperator="AND">
    <condition expr="[@age]&gt;=18"/>
    <condition expr="[@country]='USA'"/>
  </condition>
</condition>

The function offers a high level of versatility, allowing you to define and execute queries similar to those created in the query editor.

TIP:

A useful tip is to first create your query in the query editor and then extract the XML source code of the query section. This XML code can be directly copied into the queryDef XML structure, enabling you to effortlessly create and test complex queries. This streamlined approach empowers you to leverage the full potential of queryDef and efficiently handle various query scenarios.

var query = xtk.queryDef.create(
  <queryDef schema="nms:deliveryLog" operation="select">
    <select>
      <node expr="@id"/>
      <node expr="@eventName"/>
      <node expr="@eventDate"/>
      <node expr="@messageId"/>
    </select>
    <where>
      <condition expr="@eventDate&gt;=date('2022-01-01')" />
    </where>
    <orderBy>
      <node expr="@eventDate" sortDesc="true" />
    </orderBy>
  </queryDef>
);

var res = query.ExecuteQuery(),log;

for each (log in res.deliveryLog) {
  var logId = log.@id;
  var eventName = log.@eventName;
  var eventDate = log.@eventDate;
  var messageId = log[@messageId];
  
  logInfo("Log ID: " + logId);
  logInfo("Event Name: " + eventName);
  logInfo("Event Date: " + eventDate);
}

In the provided code snippet, you may observe the usage of a special function called ‘for each’ which is specific to Adobe Campaign. This function allows you to iterate and traverse over data structures, such as result sets, in a convenient manner.

NLWS.xtkQueryDef()

As the programming world gradually shifts away from XML and embraces JSON, it’s worth noting that the new implementation of xtkQueryDef in Adobe Campaign Classic allows you to work with JXON (JSON/XML Object Notation). This means you can leverage the flexibility and simplicity of JSON while still utilizing the power of xtkQueryDef. Embracing JXON provides a modern approach to querying data, aligning with the industry trend and offering enhanced capabilities for data manipulation.

JXON xtkQueryDef function, being based on the same features as XML one, empowers you to create virtually any query you would with the XML queryDef.

var query = NLWS.xtkQueryDef.create({
  queryDef: {
    schema: "nms:recipient",
    operation: "select",
    select: {
      node: [
        { expr: "@id" },
        { expr: "@firstName" },
        { expr: "@lastName" }
      ]
    },
    where: {
      condition: [
        { expr: "[@country]='USA'" },
        { expr: "[@age]>=18" }
      ]
    },
    orderBy: {
      node: { expr: "@lastName", sortDesc: "false" }
    }
  }
});

var res = query.ExecuteQuery();
var profiles = res.getElementsByTagName("recipient");

for each (var profile in profiles) {
  var firstName = profile.getAttribute("firstName");
  var lastName = profile.getAttribute("lastName");
  logInfo("Profile: " + firstName + " " + lastName);
}

NL.QueryDef()

Last but certainly not least, there is another variant of the queryDef function that I find particularly fascinating. While delving into the core JavaScript libraries of Adobe Campaign, I came across an undocumented function that instantly captured my interest. What sets this humble function apart is its ability to return the query results in JSON format, allowing you to effortlessly process and manipulate the data using native JavaScript functions. Despite the absence of official documentation, this variant offers the same powerful features as the previously mentioned queryDef functions, while providing the added advantage of JSON compatibility.

You can find the entire implementation under JavaScirpt libraries

  • xtk:queryDef.js

Getting Started

To begin using the queryDef.js library, you need to include it in your project and ensure that its dependencies are also included. The library requires the following dependencies:

  loadLibrary('xtk:shared/nl.js');
  NL.require('xtk:queryDef.js');

Query Definition

To create a query, you need to instantiate the NL.QueryDef object. The constructor takes several parameters that define the query:

  • schema: The schema on which the query will be executed.
  • settings: Additional settings for the query (optional).
  • selectExpr: An array of select expressions.
  • whereExpr: An array of where conditions.
  • orderByExpr: An array of order by expressions.
  • conditionLinkExpr: An array of condition links.

Here’s an simple example of how to create a query definition and process results:

var selectExpr = [{expr: "@status"}, {expr:"[operation/@label]", alias:"@campaignLabel"}],
    whereExpr  = {expr: "@internalName = 'DM150'"},
    nlQueryDef = new NL.QueryDef("nms:delivery", selectExpr, whereExpr),
    result = nlQueryDef.create().execute().data;
if (result.length>0){
	result.forEach(function(e,i){//you can also use good old for(;;)
    	logInfo(e.status)
        logInfo(e.campaignLabel)
        
    });
}

The constructor takes several parameters that define the query:

  • schema: The schema on which the query will be executed.
  • settings: Additional settings for the query (optional). You can see the defaults used in the library below.
    var _defaults = {
      lineCount:    200,                          // The number of lines to retrieve
      operation:    NL.QueryDef.OPERATION_SELECT, // Operation type
      expandParam:  true,                         // True to handle enabledIf conditions in the query
      startLine:    0,                            // Start offset
      firstRows:    true                          // True to force indexes
    };

NL.QueryDef.OPERATION_SELECT is only available in the intialized QueryDef Object. If you are settings the operation use following options: “select”, “get”, “count”. If you leave it out operation “select” is used.

  • selectExpr: An array of select expressions.
  • whereExpr: An array of where conditions.
  • orderByExpr: An array of order by expressions.
  • conditionLinkExpr: An array of condition links.

When selecting linked records in certain scenarios, if we don’t use an ‘alias’, the attribute will be returned as an XML string. This means that instead of directly accessing the attribute value, we would need to parse and handle the XML structure to extract the desired information.

To avoid the complexity of dealing with XML, we can employ an ‘alias’. By using an ‘alias’, the attribute value will be returned directly as a regular data type (such as a string, number, or boolean), making it easier to work with and manipulate in our code.

Using an ‘alias’ simplifies the process of accessing and utilizing linked record attributes, as we can directly access the attribute value without having to handle XML parsing and manipulation.

On example below you can see the use of all arguments for the function.

loadLibrary("xtk:shared/nl.js");
NL.require("xtk:queryDef.js");

var nlQueryDef = new NL.QueryDef(
"nms:delivery",
{
lineCount: 10000,
operation: 'select', // select, get, count
//expandParam: false,
//startLine: 50,
//firstRows: false
},//settings
[
  { expr: "@status" },
  { expr: "@label" },
],//select
[],//where, [{ expr: "@internalName = 'DM150'" }]
[{ expr: "@internalName", sortDesc: true }]//order by
);

var result = nlQueryDef.create().execute().data;
logInfo(JSON.stringify(result))
if(result.length>0) {
  result.forEach(function(e,i){
    logInfo("Delivery status: " + e.status);
     logInfo("Delivery label: " + e.label);
  });
}

When going over this library you may notice it is using the NLWS.xtkQueryDef SOAP function. This means all the features from it can be used.

Comparison xtk.queryDef VS NLWS.xtkQueryDef VS NL.QueryDef

While the XML-based xtk.queryDef is still widely used, it’s generally considered a legacy approach. Adobe recommends using NLWS.xtkQueryDef, which takes a JXON (JSON-like) object instead of raw XML. Although there’s no immediate end-of-life for the XML method, aligning with the JXON version is a smart move for long-term compatibility.

That said, there’s a third — often overlooked — method: NL.QueryDef. Unlike the others, it accepts and returns pure JSON, making it easier to work with in JavaScript-heavy use cases. Importantly, it’s not a limited or reduced version. NL.QueryDef is a fully capable wrapper around the same queryDef engine, meaning it supports all operations (select, get, count, joins, aliases, groupBy, etc.) — just in a cleaner JSON format.

Ultimately, the choice between these methods comes down to:

  • your preference for XML vs. JSON
  • your team’s coding style
  • and the complexity of your use case

Both xtk.queryDef and NLWS.xtkQueryDef return XML responses. Adobe Campaign provides strong support for working with XML via E4X and helper functions. But if you want end-to-end JSON with no XML parsing, NL.QueryDef is your go-to option — especially for modern JSSPs, integrations, or lightweight workflow scripts.

MethodInput TypeOutput FormatSupportedEase of UseNotes
xtk.queryDefXMLXML✅ Fully⚙️ MediumLegacy but reliable
NLWS.xtkQueryDefJXON (JSON-like)XMLâś… Recommendedâś… PreferredModern approach
NL.QueryDefJSONJSON⚠️ Not documented but my personal best🚀 EasiestModern approach not using JXON

Use Cases & Best Practices

Now that we’ve covered the different ways to use queryDef, let’s look at how it applies in real-world scenarios — and how to use it effectively in production.

This section highlights common use cases, practical examples, and proven best practices to help you write cleaner, safer, and more performant q

đź”’ This content is for Premium Subsribers only.

Please log in to preview content. Log in or Register

You must log in and have a Premium Subscriber account to preview the content. When upgrading, please provide the same email address as for your WordPress account; otherwise, we will not be able to link your Premium membership. Please also provide your Discord username or contact me directly to get access to the Discord community once your subscription is purchased. You can subscribe even before your account is created; the subscription will be linked to your WordPress email address.

Premium Subscriber

1.99 € / Month

  • Access to exclusive blog content
  • In-depth articles not available online
  • Insights from industry experts
  • Free Discord community invite
  • Unlimited questions in Solution Station
  • Limited seats at this price

Oh hey there đź‘‹
I’ve got something special for you—free scripts every month!

Sign up now to receive monthly premium Salesforce Marketing Cloud script examples straight to your inbox.

We don’t spam! Read our privacy policy for more info.

Share With Others

3 Comments on “Master queryDef in Adobe Campaign”

  • Pablo Fernandez

    says:

    Thank you for this!

    Reply

    • Hey, long time no see. You are most welcome hope you find things in here usefull in case you are still in adobe campaign world.

      Reply

  • Hey Brother, good work keep it up!

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

MarTech consultant

Marcel Szimonisz

Marcel Szimonisz

I specialize in solving problems, automating processes, and driving innovation through major marketing automation platforms—particularly Salesforce Marketing Cloud and Adobe Campaign.

Related posts