App Users are automatically generated when installing packages from the AppExchange or creating API integrations. Over time, these users can accumulate. This article provides a Server-Side JavaScript (SSJS) solution to automate the auditing of active App Users in Marketing Cloud.
The script dynamically evaluates API responses to exclude inactive users, GUID-based usernames, and system-generated roles. It also automatically creates the required Data Extension if it does not exist and clears previous data upon each run to provide a fresh, accurate audit.
Note: Internal Apps (not visibile in setup), or MobilePush Apps (Mobile Push Admin) may appear in the results and are managed strictly by the system and do not require auditing or manual intervention. Additionally, if an installed package in Setup does not have an Expiration Date (displays as "N/A"), it may not generate a corresponding App User.
Installed packages that had a name updated will not reflect in the app user name, use applicationid to match these packages
The script automatically checks for a Data Extension with the CustomerKey AppUser_LoginAudit. If it is not found, the script generates it automatically using the exact schema below.
In Marketing Cloud, navigate to Journey Builder > Automation Studio.
Click the Activities tab, then click Create Activity.
Select Script and click Next.
Enter a name for the activity (e.g., Audit App Users SSJS) and click Next.
Paste the following SSJS code into the editor.
<script runat="server">
Platform.Load("Core", "1.1.1");
try {
var targetDEKey = "AppUser_LoginAudit";
var targetDEName = "AppUser_LoginAudit";
var prox = new Script.Util.WSProxy();
// --- DEBUG TOGGLE ---
// Set to true: Writes ALL API users to the DE, bypassing all exclusion rules
// Set to false: Normal operation (filters out inactive, GUIDs, and System-created apps)
var debugMode = false;
// --- 1. CHECK, CLEAR, OR CREATE DATA EXTENSION ---
var deRetrieve = prox.retrieve("DataExtension", ["CustomerKey"], {
Property: "CustomerKey",
SimpleOperator: "equals",
Value: targetDEKey
});
if (deRetrieve && deRetrieve.Results && deRetrieve.Results.length > 0) {
var clearAction = prox.performItem("DataExtension", { CustomerKey: targetDEKey }, "ClearData");
} else {
var newDE = {
CustomerKey: targetDEKey,
Name: targetDEName,
IsSendable: false,
Fields: [
{ Name: "UserID", FieldType: "Text", MaxLength: 100, IsPrimaryKey: true, IsRequired: true },
{ Name: "UserName", FieldType: "Text", MaxLength: 254 },
{ Name: "LastLogin", FieldType: "Text", MaxLength: 100 },
{ Name: "ApplicationID", FieldType: "Text", MaxLength: 100 },
{ Name: "CreatedBy", FieldType: "Text", MaxLength: 254 },
{ Name: "DefaultBusinessUnit", FieldType: "Text", MaxLength: 50 },
{ Name: "AuditDate", FieldType: "Date", IsRequired: true, DefaultValue: "getdate()" }
]
};
var createRes = prox.createItem("DataExtension", newDE);
}
// --- 2. DEFINE RETRIEVE PARAMETERS ---
var cols = ["ID", "Name", "LastSuccessfulLogin", "CustomerKey", "Roles", "DefaultBusinessUnit", "ActiveFlag", "IsAPIUser", "UserID"];
// Filter to look for API Users
var filter = {
Property: "IsAPIUser",
SimpleOperator: "equals",
Value: "true"
};
var moreData = true;
var reqID = null;
var guidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
// Cache to store User IDs and their resolved UserNames to minimize API calls
var userCache = {};
// --- 3. RETRIEVE AND PROCESS ACCOUNT USERS ---
while (moreData) {
moreData = false;
var res = reqID == null ? prox.retrieve("AccountUser", cols, filter) : prox.getNextBatch("AccountUser", reqID);
if (res != null && res.Status == "OK") {
for (var i = 0; i < res.Results.length; i++) {
var user = res.Results[i];
var userName = user.Name || "Unknown";
var isActive = user.ActiveFlag;
var objectUserId = user.UserID || "";
var userId = String(user.ID);
var lastLogin = user.LastSuccessfulLogin || "Never Logged In";
var custkey = user.CustomerKey;
var defaultBU = user.DefaultBusinessUnit ? String(user.DefaultBusinessUnit) : "";
var createdById = "";
if (user.Roles) {
var firstRole = user.Roles.length > 0 ? user.Roles[0] : null;
if (firstRole && firstRole.Client && firstRole.Client.CreatedBy != null) {
createdById = String(firstRole.Client.CreatedBy);
}
}
// Check validations
var isGuidName = guidRegex.test(userName);
var isObjectUserIdGuid = guidRegex.test(objectUserId);
var isCreatedByZero = (createdById === "0");
var isInactive = (String(isActive).toLowerCase() === "false");
// --- EVALUATE CONDITIONS BASED ON DEBUG MODE ---
var passesAllChecks = false;
if (debugMode) {
// If debugMode is true, write ALL returned users to the DE
passesAllChecks = true;
} else {
// Normal operation: Enforce all validation rules
passesAllChecks = (!isGuidName && !isInactive && isObjectUserIdGuid && !isCreatedByZero);
}
if (passesAllChecks) {
var createdByName = createdById;
// --- 4. TRANSLATE CREATEDBY ID TO USERNAME ---
if (createdById !== "0" && createdById !== "") {
if (userCache[createdById]) {
createdByName = userCache[createdById];
} else {
var creatorRes = prox.retrieve("AccountUser", ["Name"], {
Property: "ID",
SimpleOperator: "equals",
Value: createdById
});
if (creatorRes && creatorRes.Status == "OK" && creatorRes.Results.length > 0) {
createdByName = creatorRes.Results[0].Name;
userCache[createdById] = createdByName;
} else {
userCache[createdById] = createdById;
}
}
} else if (createdById === "0") {
createdByName = "System"; // Maps CreatedBy: 0 to "System" if debugMode allowed it through
}
// --- 5. INSERT RECORD ---
var insertProps = [
{ Name: "UserID", Value: userId },
{ Name: "UserName", Value: userName },
{ Name: "LastLogin", Value: lastLogin },
{ Name: "ApplicationID", Value: custkey},
{ Name: "CreatedBy", Value: createdByName },
{ Name: "DefaultBusinessUnit", Value: defaultBU }
];
var insertResult = prox.createItem("DataExtensionObject", {
CustomerKey: targetDEKey,
Properties: insertProps
});
}
}
if (res.HasMoreRows) {
moreData = true;
reqID = res.RequestID;
}
}
}
} catch (error) {
// Left empty for Automation Studio
}
</script>
Script Exclusion Logic: To maintain a clean audit, the script actively excludes specific records. It bypasses users whose names consist entirely of system-generated GUIDs, UserID whose values consist entirely of system-generated GUIDs users designated with an inactive status (<ActiveFlag>false</ActiveFlag>), and system-generated roles identified by a creation value of zero (<CreatedBy>0</CreatedBy>).
Debug Mode: If troubleshooting is required to identify missing users, you can utilize the script's built-in debug mode. Locate the variable var debugMode = false; in the script and update the value to true. This action bypasses the CreatedBy == 0 restriction, allowing system-generated roles to write to the Data Extension for inspection. Revert this variable to false when troubleshooting is complete.
Data Overwrites: The script executes a ClearData command on the target Data Extension upon each run. Do not manually add records or append custom fields to the AppUser_LoginAudit Data Extension, as manual changes will be overwritten or cause script execution errors.
Run the above SSJS activity to query the AccountUser object and log the login details of any matching users within the DE AppUser_LoginAudit.
This script natively includes WSProxy's getNextBatch pagination method. Even if you have thousands of users that match your search string, the script will safely page through them in batches of 2500 until all matching users have been processed and logged.
005317493

We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.