Debugging SSJS is challenging: there is no browser DevTools, no console, and SFMC’s native error messages are often unhelpful. This page covers proven techniques for finding and fixing SSJS issues.

1. Write() Debugging

The simplest approach — output values directly to the page:

var value = Platform.Function.Lookup("Config", "apiKey", "key", "main");
Write("DEBUG apiKey: " + value + "<br>");

Structured Debug Output

function debug(label, value) {
    Write("<pre style='background:#111;color:#0f0;padding:8px;font-size:12px'>"
        + label + ": " + Platform.Function.Stringify(value)
        + "</pre>");
}

debug("requestMethod", Platform.Request.Method);
debug("queryParam id", Platform.Request.GetQueryStringParameter("id"));
debug("lookupResult", Platform.Function.LookupRows("Orders", "Status", "pending"));

Conditional Debug Mode

var DEBUG = Platform.Request.GetQueryStringParameter("debug") === "1";

function debugWrite(msg, data) {
    if (DEBUG) {
        Write("<pre>" + msg + ": " + Platform.Function.Stringify(data) + "</pre>");
    }
}

// Access: /page?debug=1
debugWrite("payload", requestBody);

Show test script
<script runat="server">
/*
 * Chapter: Write() Debugging
 * Proves:
 *   1. debug helper Stringify works.
 *   2. DEBUG flag from query param is boolean via ===.
 *   3. Write available after Core load.
 * 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); }
}
Platform.Load("core", "1.1.5");
function debug(label, value) {
    return label + ": " + Platform.Function.Stringify(value);
}
assert("debug stringify", debug("n", 1).indexOf("1") >= 0 ? "true" : "false", "true");
var DEBUG = Platform.Request.GetQueryStringParameter("debug") === "1";
assert("DEBUG default false", DEBUG ? "true" : "false", "false");
assert("Write typeof", typeOfThunk(function () { return typeof Write; }), "function");
</script>

2. Error Page Analysis

When SFMC shows a generic error page ("Sorry, we encountered a problem"), the error is logged in SFMC’s native logs. To read them:

  • Email Studio → CloudPages → (select page) → Activity — shows page execution errors
  • Automation Studio → Activity — shows script activity errors in automations

However, these logs are often truncated. Prefer logging to a DE (see Error Logging).


Show test script
<script runat="server">
/*
 * Chapter: Error Page Analysis
 * Proves:
 *   1. NON-ASSERTABLE: SFMC Activity log UI.
 *   Recorded as operational guidance.
 * 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); }
}
assert("guidance-only", "documented", "documented");
</script>

3. Testing Execution Contexts

Different execution contexts have different constraints. Test in the right context:

Context URL Pattern Notes
CloudPage pub.s10.exacttarget.com/... Full Platform.Request access
JSON Resource pub.s10.exacttarget.com/...-json Set Content-Type application/json
Automation No URL Use Email Studio Activity logs
Email Preview in SFMC Limited subset of functions

Show test script
<script runat="server">
/*
 * Chapter: Testing Execution Contexts
 * Proves:
 *   1. CloudPage GET has Request.Method GET.
 * 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); }
}
assert("Method GET", "" + Platform.Request.Method, "GET");
</script>

4. ParseJSON Error Diagnosis

The most common SSJS error is TypeError: Cannot read property 'X' of undefined on a ParseJSON result. The root cause is usually a null or undefined HTTP response.

var rawBody = Platform.Request.GetPostData();
Write("RAW BODY: [" + rawBody + "]<br>"); // is it empty?

var parsed = Platform.Function.ParseJSON(rawBody + ""); // + "" keeps the argument a string
Write("PARSED TYPE: " + typeof parsed + "<br>");
Write("PARSED VALUE: " + Platform.Function.Stringify(parsed) + "<br>");

Show test script
<script runat="server">
/*
 * Chapter: ParseJSON Error Diagnosis
 * Proves:
 *   1. ParseJSON of empty string + check.
 *   2. typeof parsed is usable.
 * 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 rawBody = "";
var parsed = Platform.Function.ParseJSON(rawBody + "");
assert("empty JSON parse nullish", parsed === null || parsed === undefined ? "nullish" : "other", "nullish");
var ok = Platform.Function.ParseJSON('{"x":1}');
assert("parsed x", ok.x, 1);
</script>

5. Try/Catch with Write

Wrap suspicious code in try/catch and output the error:

try {
    var result = someRiskyOperation();
    Write("Success: " + Platform.Function.Stringify(result));
} catch(e) {
    Write("<pre style='color:red'>");
    // String(e) — there is no .stack in this engine, and .message is undefined
    // for new Error(/* ... */). String(e) always yields something usable.
    Write("Error: " + String(e) + "\n");
    Write("</pre>");
}

Show test script
<script runat="server">
/*
 * Chapter: Try/Catch with Write
 * Proves:
 *   1. String(e) recovers new Error message.
 *   2. catch runs.
 * 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 out = "";
try { throw new Error("boom"); } catch (e) { out = String(e); }
assert("String(e) has boom", out.indexOf("boom") >= 0 ? "true" : "false", "true");
</script>

6. Diagnosing Blank Pages

A blank CloudPage with no output is almost always a silent runtime error. Common causes:

  • Platform.Load not called before Core library use
  • ParseJSON handed an object or array argument, or called with the wrong number of arguments
  • switch statement with default not executing (see Known Bugs)
  • DataExtension.Init() called with the display Name instead of the External Key, so reads see no rows

Diagnosis pattern:

// Use Platform.Response.Write before Core load — bare Write needs Platform.Load
Platform.Response.Write("1: Script started<br>");
Platform.Load("core", "1.1.5");
Write("2: Core loaded<br>");

var de = DataExtension.Init("MyDE");
Write("3: DE initialized<br>");

var rows = de.Rows.Retrieve({
    Property: "Active",
    SimpleOperator: "equals",
    Value: "true"
});
Write("4: Retrieved " + rows.length + " rows<br>");

Show test script
<script runat="server">
/*
 * Chapter: Diagnosing Blank Pages
 * Proves:
 *   1. Platform.Response.Write works before Core load.
 *   2. Write after Core load is function.
 * 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); }
}
Platform.Response.Write("PASS pre-load marker -> [ok]\n");
Platform.Load("core", "1.1.5");
assert("Write after Load", typeOfThunk(function () { return typeof Write; }), "function");
</script>

7. Automation Studio Debugging

Since automations have no URL, add a DE-based log:

function logStep(step, message) {
    try {
        Platform.Function.InsertData(
            "AutomationLog",
            ["RunId", "Step", "Message", "Timestamp"],
            [runId, step, message, Platform.Function.Now()]
        );
    } catch(e) {}
}

logStep("start", "Automation script started");
// ... code ...
logStep("de_init", "DE rows: " + rows.length);
// ... code ...
logStep("complete", "Finished successfully");

Show test script
<script runat="server">
/*
 * Chapter: Automation Studio Debugging
 * Proves:
 *   1. InsertData typeof exists (DE write NON-ASSERTABLE without fixture).
 * 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); }
}
assert("InsertData typeof", typeof Platform.Function.InsertData, "clrmethodinfo");
</script>

8. Email Context Debugging

In email sends, use AMPscript variable output for debugging — SSJS Write() still works:

Write("<!-- DEBUG: email=" + emailAddr + " subKey=" + subscriberKey + " -->");

Check the “View Email” preview in Content Builder to see the rendered HTML including debug comments.


Show test script
<script runat="server">
/*
 * Chapter: Email Context Debugging
 * Proves:
 *   1. NON-ASSERTABLE: email send context.
 *   CloudPage can still Write HTML comments.
 * 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); }
}
Platform.Load("core", "1.1.5");
assert("Write available", typeOfThunk(function () { return typeof Write; }), "function");
</script>

9. Silent Error Patterns

Some SSJS operations fail silently (no error, no output). Always validate return values:

// InsertData returns the new row count — check it
var inserted = Platform.Function.InsertData("Log",
    ["Event", "Timestamp"],
    ["pageview", Platform.Function.Now()]
);
if (inserted === 0) {
    Write("Warning: InsertData returned 0 rows inserted");
}

// Lookup returns a genuine null on no match, and a throw-on-coercion CLR null
// for an empty field — String() first, then test the string
var val = String(Platform.Function.Lookup("DE", "Field", "Key", "value"));
if (val === "null" || val === "") {
    Write("No record found");
}
Show test script
<script runat="server">
/*
 * Chapter: Silent Error Patterns
 * Proves:
 *   1. String(null) guard pattern used after Lookup.
 *   2. InsertData typeof (DE write NON-ASSERTABLE without fixture).
 * 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 email = null;
var value = String(email);
assert("String(null) is null", value, "null");
assert("empty-or-null guard", value === "" || value === "null" ? "true" : "false", "true");
assert("InsertData typeof", typeof Platform.Function.InsertData, "clrmethodinfo");
</script>

See Also