Variables
var declarations, scope, hoisting, and the rules for using variables in SFMC SSJS.
Declaration
SSJS uses only var for variable declarations. let and const are not supported and will cause a runtime error.
var name = "Jane";
var count = 42;
var active = true;
var data = null;
var nothing; // undefined
Show test script
<script runat="server">
/*
* Chapter: Declaration
* Proves:
* 1. var accepts string, number, boolean, null, and uninitialized values.
* 2. Uninitialized var is undefined.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
var name = "Jane";
var count = 42;
var active = true;
var data = null;
var nothing;
assert("string value", name, "Jane");
assert("number value", count, 42);
assert("boolean value", active, true);
assert("null value is null", data === null ? "null" : "other", "null");
assert("uninitialized is undefined", nothing === undefined ? "undefined" : "other", "undefined");
assert("uninitialized typeof undefined", "" + (typeof nothing), "undefined");
</script>
Scope
Variables declared with var have function scope, not block scope. A variable declared inside an if block or for loop is accessible outside that block.
if (true) {
var x = 10; // function-scoped, not block-scoped
}
Write(x); // 10 — works fine
for (var i = 0; i < 3; i++) {
// body
}
Write(i); // 3 — i is still accessible here
In SSJS, the “global scope” within a single <script runat="server"> block is the top-level scope. Variables declared at the top level of any script block are shared across all script blocks on the same page.
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Scope
* Proves:
* 1. var inside if is visible outside the block.
* 2. for-loop index remains visible after the loop.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
if (true) {
var x = 10;
}
assert("var inside if is visible outside", x, 10);
for (var i = 0; i < 3; i++) { /* body */ }
assert("for index visible after loop", i, 3);
</script>
Hoisting
Function declarations are hoisted to the top of their containing scope. Variable declarations (not their values) are also hoisted.
// Function declarations are fully hoisted
greet(); // Works — even called before declaration
function greet() {
Write("Hello!");
}
// Variable declarations are hoisted, but not their values
Write(y); // undefined (not an error, just undefined)
var y = 5;
Write(y); // 5
Hoisting across script blocks is NOT guaranteed. Always place function declarations in the first script block on a page.
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Hoisting
* Proves:
* 1. Function declarations are callable before their source line.
* 2. var is hoisted as undefined before assignment, then holds the value.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
var greetOk = false;
try { greet(); greetOk = true; } catch (ex) { greetOk = false; }
function greet() { /* hoisted */ }
assert("function declaration callable before line", greetOk ? "true" : "false", "true");
assert("var before assignment is undefined", y === undefined ? "undefined" : "other", "undefined");
var y = 5;
assert("var after assignment is 5", y, 5);
</script>
Naming Conventions
SSJS follows standard JavaScript naming conventions. The SFMC community style guide recommends:
camelCasefor local variables:subscriberKey,emailAddress,rowCountPascalCaseis reserved for platform objects:Platform,DataExtension,WSProxy- Prefix private/internal functions with
_:_formatDate,_validateEmail - Constants (by convention only, no
const):var MAX_ROWS = 200;(uppercase + underscores)
var subscriberKey = Platform.Request.GetQueryStringParameter("sk");
var emailAddress = "";
var rowCount = 0;
var MAX_RETRIES = 3;
function _formatName(first, last) {
return first + " " + last;
}
Show test script
<script runat="server">
/*
* Chapter: Naming Conventions
* Proves:
* 1. camelCase locals and UPPER_SNAKE convention constants are valid identifiers.
* 2. Underscore-prefixed helpers are callable.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
var subscriberKey = "sk";
var emailAddress = "";
var rowCount = 0;
var MAX_RETRIES = 3;
function _formatName(first, last) { return first + " " + last; }
assert("camelCase local", subscriberKey, "sk");
assert("empty string emailAddress", emailAddress, "");
assert("rowCount zero", rowCount, 0);
assert("MAX_RETRIES constant-by-convention", MAX_RETRIES, 3);
assert("_formatName helper", _formatName("Jane", "Smith"), "Jane Smith");
</script>
Multiple Variables
Declare multiple variables with separate var statements (one per variable is most readable and lint-friendly):
// Preferred: one var per line
var first = "Jane";
var last = "Smith";
var email = "jane@example.com";
// Also valid: comma-separated (harder to diff)
var a = 1, b = 2, c = 3;
Show test script
<script runat="server">
/*
* Chapter: Multiple Variables
* Proves:
* 1. One-var-per-line and comma-separated declarations both work.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
var first = "Jane";
var last = "Smith";
var email = "jane@example.com";
var a = 1, b = 2, c = 3;
assert("first", first, "Jane");
assert("last", last, "Smith");
assert("email", email, "jane@example.com");
assert("comma a", a, 1);
assert("comma b", b, 2);
assert("comma c", c, 3);
</script>
Common Pitfalls
Forgetting var creates a global:
function doSomething() {
result = "oops"; // ⚠️ No var — creates/overwrites global
}
Always use var inside functions to keep scope contained.
Variable leaking across iterations:
for (var i = 0; i < rows.length; i++) {
var rowData = processRow(rows[i]); // rowData is re-declared each iteration
// This works in SSJS but wouldn't in strict block-scoped languages
}
// rowData and i still accessible here
Show test script
<script runat="server">
/*
* Chapter: Common Pitfalls
* Proves:
* 1. Assignment without var creates/overwrites a top-level binding.
* 2. Loop var leak: i and rowData remain visible after the loop.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function doSomething() {
result = "oops";
}
doSomething();
assert("assignment without var creates top-level", result, "oops");
var rows = [1, 2];
for (var i = 0; i < rows.length; i++) {
var rowData = rows[i];
}
assert("loop index leaked", i, 2);
assert("loop var rowData leaked", rowData, 2);
</script>