Debugging
Practical techniques for debugging SSJS code — from basic Write() tracing to structured error logging and silent error detection.
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);
Always guard debug output behind a parameter check or a DE flag. Never leave DEBUG = true in production code.
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 |
| 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.Loadnot called before Core library useParseJSONhanded an object or array argument, or called with the wrong number of argumentsswitchstatement withdefaultnot 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>