Style Guide
SSJS coding style conventions — variable naming, indentation, function organization, and patterns for consistent, readable code.
Consistent code style makes SSJS easier to read, review, and debug. These conventions reflect community norms and practical experience.
Variables
Always use var. Never use let or const — they throw runtime errors in SSJS.
// CORRECT
var subscriberKey = Platform.Variable.GetValue("@subscriberKey");
var isLoggedIn = false;
var MAX_ROWS = 500;
// WRONG — will throw runtime errors (do not uncomment)
// let subscriberKey = "value";
// const MAX_ROWS = 500;
Use descriptive names. Avoid single-letter variables outside of short loops.
// OK for loops
for (var i = 0; i < rows.length; i++) { /* ... */ }
// Not OK for meaningful data
var x = Platform.Request.GetFormField("email"); // unclear
var email = Platform.Request.GetFormField("email"); // clear
Show test script
<script runat="server">
/*
* Chapter: Variables
* Proves:
* 1. var works; MAX_ROWS convention.
* 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 subscriberKey = "sk";
var isLoggedIn = false;
var MAX_ROWS = 500;
assert("var", subscriberKey, "sk");
assert("MAX_ROWS", MAX_ROWS, 500);
assert("bool", isLoggedIn ? "true" : "false", "false");
</script>
Functions
Use function declarations for utilities, function expressions for callbacks (when available).
// Named function declaration — preferred for utilities
function validateEmail(email) {
return Platform.Function.IsEmailAddress(email);
}
Group related functions. Use the Revealing Module Pattern for namespacing:
var FormHandler = (function() {
function validate(data) {
if (!data.email) return false;
if (!Platform.Function.IsEmailAddress(data.email)) return false;
return true;
}
function process(data) {
Platform.Function.InsertData("Leads",
["Email", "Name", "Source"],
[data.email, data.name, data.source]
);
}
return { validate: validate, process: process };
})();
Show test script
<script runat="server">
/*
* Chapter: Functions
* Proves:
* 1. IsEmailAddress via declaration; module pattern return.
* 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 validateEmail(email) { return Platform.Function.IsEmailAddress(email); }
assert("validateEmail", validateEmail("a@b.com") ? "true" : "false", "true");
var FormHandler = (function () {
function validate(data) { return !!(data && data.email); }
return { validate: validate };
})();
assert("module validate", FormHandler.validate({ email: "x" }) ? "true" : "false", "true");
</script>
Script Block Organization
Organize your script block in this order:
<script runat="server">
// 1. Platform.Load (if using Core)
Platform.Load("core", "1.1.5");
// 2. Constants and configuration
var PAGE_ID = 12345;
var DEBUG = Platform.Request.GetQueryStringParameter("debug") === "1";
// 3. Read request data once — String() converts the CLR value so === works
var method = String(Platform.Request.Method);
var rawBody = (method === "POST") ? Platform.Request.GetPostData() : "";
// 4. Helper functions
function getConfig(key) {
return Platform.Function.Lookup("AppConfig", "value", "key", key);
}
// 5. Main logic
if (method === "GET") {
// handle GET
} else if (method === "POST") {
// handle POST
}
</script>
Show test script
<script runat="server">
/*
* Chapter: Script Block Organization
* Proves:
* 1. Method coerce + Load order pattern.
* 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");
var method = String(Platform.Request.Method);
assert("method GET", method, "GET");
</script>
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Variables | camelCase | subscriberKey, isActive |
| Constants | UPPER_SNAKE_CASE | MAX_ROWS, API_BASE_URL |
| Functions | camelCase | formatDate(), validateInput() |
| Objects/modules | PascalCase | FormHandler, DataUtils |
| DE column access | Match DE column name exactly | rows[i].SubscriberKey |
Show test script
<script runat="server">
/*
* Chapter: Naming Conventions
* Proves:
* 1. camelCase + _prefix helper.
* 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 _formatName(first, last) { return first + " " + last; }
assert("_formatName", _formatName("A", "B"), "A B");
</script>
Indentation and Formatting
Use 4-space indentation (or 2-space consistently). Always use braces for if/for/while:
// GOOD
if (condition) {
doSomething();
}
// BAD — no braces invite bugs
if (condition)
doSomething();
Show test script
<script runat="server">
/*
* Chapter: Indentation and Formatting
* Proves:
* 1. NON-ASSERTABLE: style preference.
* One-var-per-line still valid.
* 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 a = 1;
var b = 2;
assert("sum", a + b, 3);
</script>
Comments
Write comments that explain why, not what:
// GOOD — explains non-obvious intent
// GetPostData() can only be called once per request — read immediately
var rawBody = Platform.Request.GetPostData();
// BAD — just narrates the code
// Get the post data
var rawBody = Platform.Request.GetPostData();
Document known bugs and workarounds:
// + "" keeps a non-string argument valid — ParseJSON throws on an object/array
var data = Platform.Function.ParseJSON(responseBody + "");
Show test script
<script runat="server">
/*
* Chapter: Comments
* Proves:
* 1. Comments do not affect runtime.
* 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); }
}
// explanatory
assert("runs", 1, 1);
</script>
Platform.Function Aliases
When using InsertData, UpdateData, UpsertData, DeleteData prefer the full names for clarity:
// Preferred — function intent is obvious
Platform.Function.InsertData("DE", ["col"], ["val"]);
Platform.Function.UpsertData("DE", ["key"], ["val"], ["col"], ["val"]);
// Also valid, just less descriptive
Platform.Function.InsertDE("DE", ["col"], ["val"]);
Show test script
<script runat="server">
/*
* Chapter: Platform.Function Aliases
* Proves:
* 1. Platform.Function.Now without alias; Core Now after 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); }
}
var n1 = Platform.Function.Now();
assert("Platform Now", n1 !== null && n1 !== undefined ? "ok" : "bad", "ok");
Platform.Load("core", "1.1.5");
assert("bare Now typeof", typeOfThunk(function () { return typeof Now; }), "function");
</script>
Error Handling
Always wrap external calls in try/catch:
var result = null;
try {
var resp = req.send();
result = Platform.Function.ParseJSON(String(resp.content) + "");
} catch(e) {
// Log and handle gracefully
logError("http_request", e.message);
Write(Platform.Function.Stringify({ status: 502, statusMessage: "Bad Gateway", error: "External API unavailable" }));
return;
}
Show test script
<script runat="server">
/*
* Chapter: Error Handling
* Proves:
* 1. try/catch with call-form Error message.
* 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 msg = "";
try { throw Error("fail"); } catch (e) { msg = "" + e.message; }
assert("msg", msg, "fail");
</script>