Practical patterns for working with Script.Util.WSProxy in production SSJS scripts.


Paginated Retrieval of All Records

proxy.retrieve() returns up to ~2,500 rows per call. For full pagination, use proxy.retrieve() for the first call and proxy.getNextBatch() for subsequent pages, looping while HasMoreRows is true:

var proxy = new Script.Util.WSProxy();
var objectType = "DataExtensionObject[MyDE_Key]";
var cols = ["SubscriberKey", "Email", "Score"];
var filter = { Property: "Score", SimpleOperator: "greaterThan", Value: "50" };
var reqID = null;
var allRows = [];
var moreData = true;

while (moreData) {
    moreData = false;
    var result = reqID == null
        ? proxy.retrieve(objectType, cols, filter)
        : proxy.getNextBatch(objectType, reqID);

    if (result != null) {
        moreData = result.HasMoreRows;
        reqID = result.RequestID;
        if (result.Results) {
            for (var i = 0; i < result.Results.length; i++) {
                allRows.push(result.Results[i]);
            }
        }
    }
}
Write("Total rows: " + allRows.length);

Show test script
<script runat="server">
/*
 * Chapter: Paginated Retrieval of All Records
 * Proves:
 *   1. WSProxy retrieve/getNextBatch typeof.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var proxy = new Script.Util.WSProxy();
assert("retrieve", typeof proxy.retrieve, "clrmethodinfo");
assert("getNextBatch", typeof proxy.getNextBatch, "clrmethodinfo");
</script>

Bulk Upsert to a Data Extension

Upsert multiple rows in one call using SaveAction: "UpdateAdd":

var proxy = new Script.Util.WSProxy();
var rows = [
    { Properties: { Property: [
        { Name: "SubscriberKey", Value: "sub_001" },
        { Name: "Score", Value: "90" }
    ]}},
    { Properties: { Property: [
        { Name: "SubscriberKey", Value: "sub_002" },
        { Name: "Score", Value: "75" }
    ]}}
];

var result = proxy.updateBatch(
    "DataExtensionObject[ScoringDE_Key]",
    rows,
    [{ SaveAction: "UpdateAdd" }]
);
if (result.Status !== "OK") {
    Write("Batch upsert failed: " + result.Results[0].StatusMessage);
}

Show test script
<script runat="server">
/*
 * Chapter: Bulk Upsert to a Data Extension
 * Proves:
 *   1. updateBatch typeof.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var proxy = new Script.Util.WSProxy();
assert("updateBatch", typeof proxy.updateBatch, "clrmethodinfo");
</script>

Multi-BU Iteration

Run the same operation across multiple child Business Units from a parent account:

var proxy = new Script.Util.WSProxy();
var businessUnits = [
    { name: "US", mid: 111111 },
    { name: "EU", mid: 222222 },
    { name: "APAC", mid: 333333 }
];

for (var i = 0; i < businessUnits.length; i++) {
    proxy.setClientId({ ID: businessUnits[i].mid });

    var result = proxy.retrieve("DataExtension", ["Name", "CustomerKey"]);
    Write(businessUnits[i].name + ": " + result.Results.length + " DEs<br>");
}

// Always reset after cross-BU operations
proxy.resetClientIds();

Show test script
<script runat="server">
/*
 * Chapter: Multi-BU Iteration
 * Proves:
 *   1. setClientId typeof.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var proxy = new Script.Util.WSProxy();
assert("setClientId", typeof proxy.setClientId, "clrmethodinfo");
</script>

Error-Safe Loop Pattern

Always check result.Status before accessing result.Results:

var proxy = new Script.Util.WSProxy();
var items = [
    { SubscriberKey: "sub_001", Status: "Active" },
    { SubscriberKey: "sub_002", Status: "Unsubscribed" }
];

for (var i = 0; i < items.length; i++) {
    var result = proxy.updateItem("Subscriber", items[i]);
    if (result.Status !== "OK") {
        var msg = result.Results && result.Results[0]
            ? result.Results[0].StatusMessage
            : "Unknown error";
        Platform.Function.InsertData(
            "ErrorLog",
            ["Key", "Error", "Timestamp"],
            [items[i].SubscriberKey, msg, Platform.Function.Now()]
        );
    }
}

Show test script
<script runat="server">
/*
 * Chapter: Error-Safe Loop Pattern
 * Proves:
 *   1. Per-item try/catch continues.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var ok = 0, fail = 0, items = [1, 2, 3];
for (var i = 0; i < items.length; i++) {
    try {
        if (items[i] === 2) throw Error("boom");
        ok++;
    } catch (e) { fail++; }
}
assert("ok", ok, 2);
assert("fail", fail, 1);
</script>

Chunked Batch Processing

Split a large array into chunks to stay within SFMC’s per-request limits:

var proxy = new Script.Util.WSProxy();
var allRows = []; // ... your full dataset
var chunkSize = 50;

for (var c = 0; c < allRows.length; c += chunkSize) {
    var chunk = allRows.slice(c, c + chunkSize);
    var result = proxy.createBatch("DataExtensionObject[MyDE_Key]", chunk);
    if (result.Status !== "OK") {
        Write("Chunk " + (c / chunkSize) + " failed.<br>");
    }
}
Show test script
<script runat="server">
/*
 * Chapter: Chunked Batch Processing
 * Proves:
 *   1. Chunk array into batches of N.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
function chunk(arr, size) {
    var out = [], i;
    for (i = 0; i < arr.length; i += size) {
        out.push(arr.slice(i, i + size));
    }
    return out;
}
var parts = chunk([1, 2, 3, 4, 5], 2);
assert("chunks", parts.length, 3);
assert("first len", parts[0].length, 2);
assert("last len", parts[2].length, 1);
</script>

See Also